diff --git a/.github/workflows/create-deploy-pr.yml b/.github/workflows/create-deploy-pr.yml
new file mode 100644
index 00000000000..7027e598bb5
--- /dev/null
+++ b/.github/workflows/create-deploy-pr.yml
@@ -0,0 +1,32 @@
+name: Create deploy PR
+on:
+ workflow_dispatch:
+ inputs:
+ deploy_type:
+ description: 'Type of deploy'
+ required: true
+ type: choice
+ options:
+ - Normal
+ - Patch
+ source:
+ description: 'Source branch/SHA (If blank, the current SHA running on staging will be used)'
+ required: false
+ type: string
+permissions:
+ pull-requests: write
+ contents: write
+jobs:
+ create-pr:
+ name: Create PR
+ runs-on: ubuntu-latest
+ env:
+ GH_TOKEN: ${{ github.token }}
+ PATCH: ${{ inputs.deploy_type == 'Patch' && 1 || 0 }}
+ SOURCE: ${{ inputs.source }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # Get all commits
+ - uses: ruby/setup-ruby@v1
+ - run: scripts/create-deploy-pr
diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml
new file mode 100644
index 00000000000..9f9bd2fd323
--- /dev/null
+++ b/.github/workflows/create-release.yml
@@ -0,0 +1,18 @@
+name: Create release
+run-name: "Create release based on ${{ github.event.pull_request.title }}"
+on:
+ pull_request:
+ types:
+ - closed
+ branches:
+ - 'stages/prod'
+jobs:
+ create-release:
+ name: Create release after PR merge
+ if: github.event.pull_request.merged == true
+ runs-on: ubuntu-latest
+ env:
+ GH_TOKEN: ${{ github.token }}
+ steps:
+ - uses: actions/checkout@v4
+ - run: scripts/create-release ${{ github.event.pull_request.number }}
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index be562533971..7d2664d1461 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,7 +8,7 @@ variables:
FF_SCRIPT_SECTIONS: 'true'
JUNIT_OUTPUT: 'true'
ECR_REGISTRY: '${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com'
- IDP_CI_SHA: 'sha256:908cb207c214016f3e366b2ebbe89c2077cfe1d40f3b82ad8d79e58e0cec720b'
+ IDP_CI_SHA: 'sha256:756a1d450b422720dee36cb9a6217687bcad1e40b780219d360989861ce94212'
PKI_IMAGE_TAG: 'main'
DASHBOARD_IMAGE_TAG: 'main'
@@ -163,17 +163,6 @@ check_changelog:
exit 0
fi
-check_content_freeze:
- stage: test
- script: |-
- echo "Content change is not allowed during content freeze"
- exit 1
- rules:
- - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "external_pull_request_event" || $CI_PIPELINE_SOURCE == "web"'
- changes:
- compare_to: 'refs/heads/main'
- paths:
- - config/locales/**/en.yml
specs:
stage: test
needs:
diff --git a/.ruby-version b/.ruby-version
index be94e6f53db..15a27998172 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-3.2.2
+3.3.0
diff --git a/Gemfile b/Gemfile
index 4b37b6aafd6..42c6e235359 100644
--- a/Gemfile
+++ b/Gemfile
@@ -16,11 +16,14 @@ gem 'aws-sdk-sns'
gem 'aws-sdk-sqs'
gem 'barby', '~> 0.6.8'
gem 'base32-crockford'
+gem 'base64'
+gem 'bigdecimal'
gem 'bootsnap', '~> 1.0', require: false
gem 'browser'
gem 'caxlsx', require: false
gem 'concurrent-ruby'
gem 'connection_pool'
+gem 'csv'
gem 'cssbundling-rails'
gem 'devise', '~> 4.8'
gem 'dotiw', '>= 4.0.1'
@@ -101,7 +104,7 @@ group :development, :test do
gem 'i18n-tasks', '~> 1.0'
gem 'knapsack'
gem 'listen'
- gem 'nokogiri', '~> 1.14.0'
+ gem 'nokogiri', '~> 1.16.0'
gem 'pg_query', require: false
gem 'pry-byebug'
gem 'pry-doc'
diff --git a/Gemfile.lock b/Gemfile.lock
index 3e41c25e321..d13d559d42c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -253,6 +253,7 @@ GEM
addressable
cssbundling-rails (1.0.0)
railties (>= 6.0.0)
+ csv (3.2.8)
date (3.3.4)
dead_end (4.0.0)
derailed_benchmarks (2.1.2)
@@ -355,14 +356,14 @@ GEM
rainbow (>= 2.2.2, < 4.0)
terminal-table (>= 1.5.1)
ice_nine (0.11.2)
- io-console (0.6.0)
- irb (1.9.1)
+ io-console (0.7.1)
+ irb (1.11.0)
rdoc
reline (>= 0.3.8)
jmespath (1.6.2)
jsbundling-rails (1.1.2)
railties (>= 6.0.0)
- json (2.7.0)
+ json (2.7.1)
jwe (0.4.0)
jwt (2.7.1)
knapsack (4.0.0)
@@ -427,15 +428,15 @@ GEM
net-ssh (6.1.0)
newrelic_rpm (9.7.0)
nio4r (2.7.0)
- nokogiri (1.14.5)
- mini_portile2 (~> 2.8.0)
+ nokogiri (1.16.0)
+ mini_portile2 (~> 2.8.2)
racc (~> 1.4)
openssl (3.0.2)
openssl-signature_algorithm (1.2.1)
openssl (> 2.0, < 3.1)
orm_adapter (0.5.0)
parallel (1.23.0)
- parser (3.2.2.4)
+ parser (3.3.0.0)
ast (~> 2.4.1)
racc
pg (1.5.4)
@@ -463,12 +464,12 @@ GEM
pry-byebug (3.10.1)
byebug (~> 11.0)
pry (>= 0.13, < 0.15)
- pry-doc (1.4.0)
+ pry-doc (1.5.0)
pry (~> 0.11)
yard (~> 0.9.11)
pry-rails (0.3.9)
pry (>= 0.10.4)
- psych (5.1.1.1)
+ psych (5.1.2)
stringio
public_suffix (5.0.3)
puma (6.4.2)
@@ -481,7 +482,7 @@ GEM
rack-cors (2.0.1)
rack (>= 2.0.0)
rack-headers_filter (0.0.1)
- rack-mini-profiler (3.1.1)
+ rack-mini-profiler (3.3.0)
rack (>= 1.2.0)
rack-proxy (0.7.7)
rack
@@ -537,7 +538,7 @@ GEM
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
- rdoc (6.6.0)
+ rdoc (6.6.2)
psych (>= 4.0.0)
redacted_struct (1.1.0)
redcarpet (3.6.0)
@@ -546,7 +547,7 @@ GEM
redis-client (0.14.1)
connection_pool
regexp_parser (2.8.2)
- reline (0.4.0)
+ reline (0.4.1)
io-console (~> 0.5)
request_store (1.5.1)
rack (>= 1.4)
@@ -658,7 +659,7 @@ GEM
unf (~> 0.1.4)
smart_properties (1.17.0)
stringex (2.8.5)
- stringio (3.0.9)
+ stringio (3.1.0)
strong_migrations (1.6.4)
activerecord (>= 5.2)
subprocess (1.5.5)
@@ -744,7 +745,9 @@ DEPENDENCIES
axe-core-rspec (~> 4.2)
barby (~> 0.6.8)
base32-crockford
+ base64
better_errors (>= 2.5.1)
+ bigdecimal
bootsnap (~> 1.0)
brakeman
browser
@@ -755,6 +758,7 @@ DEPENDENCIES
concurrent-ruby
connection_pool
cssbundling-rails
+ csv
derailed_benchmarks
devise (~> 4.8)
dotiw (>= 4.0.1)
@@ -788,7 +792,7 @@ DEPENDENCIES
multiset
net-sftp
newrelic_rpm (~> 9.0)
- nokogiri (~> 1.14.0)
+ nokogiri (~> 1.16.0)
pg
pg_query
phonelib
@@ -852,7 +856,7 @@ DEPENDENCIES
zxcvbn (= 0.1.9)
RUBY VERSION
- ruby 3.2.2p53
+ ruby 3.3.0p0
BUNDLED WITH
2.4.20
diff --git a/app/controllers/concerns/idv/document_capture_concern.rb b/app/controllers/concerns/idv/document_capture_concern.rb
index aa0fda3665f..302bc509d58 100644
--- a/app/controllers/concerns/idv/document_capture_concern.rb
+++ b/app/controllers/concerns/idv/document_capture_concern.rb
@@ -32,22 +32,15 @@ def failure(message, extra = nil)
# @param [DocAuth::Response,
# DocumentCaptureSessionResult] response
def extract_pii_from_doc(user, response, store_in_session: false)
- pii_from_doc = response.pii_from_doc.merge(
- uuid: user.uuid,
- phone: user.phone_configurations.take&.phone,
- uuid_prefix: ServiceProvider.find_by(issuer: sp_session[:issuer])&.app_id,
- )
-
if defined?(idv_session) # hybrid mobile does not have idv_session
idv_session.had_barcode_read_failure = response.attention_with_barcode?
if store_in_session
- idv_session.pii_from_doc ||= {}
- idv_session.pii_from_doc.merge!(pii_from_doc)
+ idv_session.pii_from_doc = response.pii_from_doc
idv_session.selfie_check_performed = response.selfie_check_performed
end
end
- track_document_issuing_state(user, pii_from_doc[:state])
+ track_document_issuing_state(user, response.pii_from_doc[:state])
end
def stored_result
diff --git a/app/controllers/concerns/idv/phone_otp_sendable.rb b/app/controllers/concerns/idv/phone_otp_sendable.rb
index 0d6c5c77b4f..31c5804f667 100644
--- a/app/controllers/concerns/idv/phone_otp_sendable.rb
+++ b/app/controllers/concerns/idv/phone_otp_sendable.rb
@@ -2,10 +2,6 @@ module Idv
module PhoneOtpSendable
extend ActiveSupport::Concern
- included do
- before_action :handle_locked_out_user
- end
-
def send_phone_confirmation_otp
send_phone_confirmation_otp_service.call
end
diff --git a/app/controllers/concerns/idv/verify_info_concern.rb b/app/controllers/concerns/idv/verify_info_concern.rb
index be0e05b49eb..43dbe2b92c7 100644
--- a/app/controllers/concerns/idv/verify_info_concern.rb
+++ b/app/controllers/concerns/idv/verify_info_concern.rb
@@ -23,11 +23,14 @@ def shared_update
idv_session.verify_info_step_document_capture_session_uuid = document_capture_session.uuid
# proof_resolution job expects these values
- pii[:uuid_prefix] = ServiceProvider.find_by(issuer: sp_session[:issuer])&.app_id
- pii[:ssn] = idv_session.ssn
- Idv::Agent.new(pii).proof_resolution(
+ agent_pii = pii.merge(
+ uuid: current_user.uuid,
+ uuid_prefix: ServiceProvider.find_by(issuer: sp_session[:issuer])&.app_id,
+ ssn: idv_session.ssn,
+ )
+ Idv::Agent.new(agent_pii).proof_resolution(
document_capture_session,
- should_proof_state_id: aamva_state?(pii),
+ should_proof_state_id: aamva_state?,
trace_id: amzn_trace_id,
user_id: current_user.id,
threatmetrix_session_id: idv_session.threatmetrix_session_id,
@@ -44,10 +47,8 @@ def ipp_enrollment_in_progress?
current_user.has_in_person_enrollment?
end
- def aamva_state?(pii)
- IdentityConfig.store.aamva_supported_jurisdictions.include?(
- pii['state_id_jurisdiction'],
- )
+ def aamva_state?
+ IdentityConfig.store.aamva_supported_jurisdictions.include?(pii['state_id_jurisdiction'])
end
def resolution_rate_limiter
diff --git a/app/forms/idv/api_image_upload_form.rb b/app/forms/idv/api_image_upload_form.rb
index 438794746d4..2eb3c2536d2 100644
--- a/app/forms/idv/api_image_upload_form.rb
+++ b/app/forms/idv/api_image_upload_form.rb
@@ -454,11 +454,18 @@ def store_failed_images(client_response, doc_pii_response)
failed_back_fingerprint = nil unless errors_hash[:back]&.present?
end
document_capture_session.
- store_failed_auth_image_fingerprint(failed_front_fingerprint, failed_back_fingerprint)
+ store_failed_auth_data(
+ front_image_fingerprint: failed_front_fingerprint,
+ back_image_fingerprint: failed_back_fingerprint,
+ doc_auth_success: client_response.doc_auth_success?,
+ selfie_success: client_response.selfie_success,
+ )
elsif doc_pii_response && !doc_pii_response.success?
- document_capture_session.store_failed_auth_image_fingerprint(
- extra_attributes[:front_image_fingerprint],
- extra_attributes[:back_image_fingerprint],
+ document_capture_session.store_failed_auth_data(
+ front_image_fingerprint: extra_attributes[:front_image_fingerprint],
+ back_image_fingerprint: extra_attributes[:back_image_fingerprint],
+ doc_auth_success: client_response.doc_auth_success?,
+ selfie_success: client_response.selfie_success,
)
end
# retrieve updated data from session
diff --git a/app/javascript/packages/document-capture/components/acuant-capture.scss b/app/javascript/packages/document-capture/components/acuant-capture.scss
index 2ed7922a445..451142b77d3 100644
--- a/app/javascript/packages/document-capture/components/acuant-capture.scss
+++ b/app/javascript/packages/document-capture/components/acuant-capture.scss
@@ -37,4 +37,12 @@
.document-capture-file-image--loading {
@extend %pad-common-id-card;
}
+ // Styles for the text that appears over the selfie capture screen to help users position their face for a good photo
+ .document-capture-selfie-feedback {
+ left: 50%;
+ top: 10%;
+ position: fixed;
+ transform: translateX(-50%);
+ z-index: 11;
+ }
}
diff --git a/app/javascript/packages/document-capture/components/acuant-capture.tsx b/app/javascript/packages/document-capture/components/acuant-capture.tsx
index 8e61ba6e2b8..a9f22662c2b 100644
--- a/app/javascript/packages/document-capture/components/acuant-capture.tsx
+++ b/app/javascript/packages/document-capture/components/acuant-capture.tsx
@@ -337,6 +337,7 @@ function AcuantCapture(
const [attempt, incrementAttempt] = useCounter(1);
const [acuantFailureCookie, setAcuantFailureCookie, refreshAcuantFailureCookie] =
useCookie('AcuantCameraHasFailed');
+ const [imageCaptureText, setImageCaptureText] = useState('');
// There's some pretty significant changes to this component when it's used for
// selfie capture vs document image capture. This controls those changes.
const selfieCapture = name === 'selfie';
@@ -653,6 +654,10 @@ function AcuantCapture(
});
}
+ function onImageCaptureFeedback(text: string) {
+ setImageCaptureText(text);
+ }
+
return (
{isCapturingEnvironment && !selfieCapture && (
@@ -678,11 +683,13 @@ function AcuantCapture(
onImageCaptureFailure={onSelfieCaptureFailure}
onImageCaptureOpen={onSelfieCaptureOpen}
onImageCaptureClose={onSelfieCaptureClosed}
+ onImageCaptureFeedback={onImageCaptureFeedback}
>
setIsCapturingEnvironment(false)}
+ imageCaptureText={imageCaptureText}
/>
)}
diff --git a/app/javascript/packages/document-capture/components/acuant-selfie-camera.tsx b/app/javascript/packages/document-capture/components/acuant-selfie-camera.tsx
index e031aef7008..29eb473aa2e 100644
--- a/app/javascript/packages/document-capture/components/acuant-selfie-camera.tsx
+++ b/app/javascript/packages/document-capture/components/acuant-selfie-camera.tsx
@@ -1,5 +1,6 @@
import { useContext, useEffect } from 'react';
import type { ReactNode } from 'react';
+import { t } from '@18f/identity-i18n';
import AcuantContext from '../context/acuant';
declare global {
@@ -43,6 +44,11 @@ interface AcuantSelfieCameraContextProps {
* when the fullscreen selfie capture page has been closed
*/
onImageCaptureClose: () => void;
+ /**
+ * Capture hint text from onDetection callback, tells the user
+ * why the acuant sdk cannot capture a selfie.
+ */
+ onImageCaptureFeedback: (text: string) => void;
/**
* React children node
*/
@@ -63,8 +69,6 @@ interface FaceCaptureCallback {
interface FaceDetectionStates {
FACE_NOT_FOUND: string;
TOO_MANY_FACES: string;
- FACE_ANGLE_TOO_LARGE: string;
- PROBABILITY_TOO_SMALL: string;
FACE_TOO_SMALL: string;
FACE_CLOSE_TO_BORDER: string;
}
@@ -74,6 +78,7 @@ function AcuantSelfieCamera({
onImageCaptureFailure = () => {},
onImageCaptureOpen = () => {},
onImageCaptureClose = () => {},
+ onImageCaptureFeedback = () => {},
children,
}: AcuantSelfieCameraContextProps) {
const { isReady, setIsActive } = useContext(AcuantContext);
@@ -85,7 +90,8 @@ function AcuantSelfieCamera({
// Until then, no actions are executed and the user sees only the camera stream.
// You can opt to display an alert before the callback is triggered.
},
- onDetection: () => {
+ onDetection: (text) => {
+ onImageCaptureFeedback(text);
// Triggered when the face does not pass the scan. The UI element
// should be updated here to provide guidence to the user
},
@@ -104,6 +110,7 @@ function AcuantSelfieCamera({
},
onPhotoTaken: () => {
// The photo has been taken and it's showing a preview with a button to accept or retake the image.
+ onImageCaptureFeedback('');
},
onPhotoRetake: () => {
// Triggered when retake button is tapped
@@ -115,12 +122,10 @@ function AcuantSelfieCamera({
};
const faceDetectionStates = {
- FACE_NOT_FOUND: 'FACE NOT FOUND',
- TOO_MANY_FACES: 'TOO MANY FACES',
- FACE_ANGLE_TOO_LARGE: 'FACE ANGLE TOO LARGE',
- PROBABILITY_TOO_SMALL: 'PROBABILITY TOO SMALL',
- FACE_TOO_SMALL: 'FACE TOO SMALL',
- FACE_CLOSE_TO_BORDER: 'TOO CLOSE TO THE FRAME',
+ FACE_NOT_FOUND: t('doc_auth.info.selfie_capture_status.face_not_found'),
+ TOO_MANY_FACES: t('doc_auth.info.selfie_capture_status.too_many_faces'),
+ FACE_TOO_SMALL: t('doc_auth.info.selfie_capture_status.face_too_small'),
+ FACE_CLOSE_TO_BORDER: t('doc_auth.info.selfie_capture_status.face_close_to_border'),
};
const cleanupSelfieCamera = () => {
window.AcuantPassiveLiveness.end();
diff --git a/app/javascript/packages/document-capture/components/acuant-selfie-capture-canvas.jsx b/app/javascript/packages/document-capture/components/acuant-selfie-capture-canvas.jsx
index 6091567aabf..d89bed1d406 100644
--- a/app/javascript/packages/document-capture/components/acuant-selfie-capture-canvas.jsx
+++ b/app/javascript/packages/document-capture/components/acuant-selfie-capture-canvas.jsx
@@ -17,13 +17,20 @@ function FullScreenLoadingSpinner({ fullScreenRef, onRequestClose, fullScreenLab
);
}
-function AcuantSelfieCaptureCanvas({ fullScreenRef, onRequestClose, fullScreenLabel }) {
+function AcuantSelfieCaptureCanvas({
+ fullScreenRef,
+ onRequestClose,
+ fullScreenLabel,
+ imageCaptureText,
+}) {
const { isReady } = useContext(AcuantContext);
// The Acuant SDK script AcuantPassiveLiveness attaches to whatever element has
// this id. It then uses that element as the root for the full screen selfie capture
const acuantCaptureContainerId = 'acuant-face-capture-container';
return isReady ? (
-
+
) : (
{
- trackEvent('IdV: docauth not ready link clicked');
- forceRedirect(
- addSearchParams(spName ? failureToProofURL : accountURL, {
- step: currentStep,
- location: 'not_ready',
- }),
- navigate,
- );
- };
-
- return (
- <>
- {t('doc_auth.not_ready.header')}
-
- {spName
- ? t('doc_auth.not_ready.content_sp', {
- sp_name: spName,
- app_name: appName,
- })
- : t('doc_auth.not_ready.content_nosp', {
- app_name: appName,
- })}
-
-
- {spName
- ? t('doc_auth.not_ready.button_sp', { app_name: appName, sp_name: spName })
- : t('doc_auth.not_ready.button_nosp')}
-
- >
- );
-}
-
-export default DocumentCaptureNotReady;
diff --git a/app/javascript/packages/document-capture/components/document-capture-review-issues.tsx b/app/javascript/packages/document-capture/components/document-capture-review-issues.tsx
index ab1ba1f82fb..84c74fbcba0 100644
--- a/app/javascript/packages/document-capture/components/document-capture-review-issues.tsx
+++ b/app/javascript/packages/document-capture/components/document-capture-review-issues.tsx
@@ -6,7 +6,6 @@ import { useI18n } from '@18f/identity-react-i18n';
import type { FormStepComponentProps } from '@18f/identity-form-steps';
import UnknownError from './unknown-error';
import TipList from './tip-list';
-import DocumentCaptureNotReady from './document-capture-not-ready';
import { FeatureFlagContext } from '../context';
import DocumentCaptureAbandon from './document-capture-abandon';
import {
@@ -36,8 +35,7 @@ function DocumentCaptureReviewIssues({
hasDismissed,
}: DocumentCaptureReviewIssuesProps) {
const { t } = useI18n();
- const { notReadySectionEnabled, exitQuestionSectionEnabled, selfieCaptureEnabled } =
- useContext(FeatureFlagContext);
+ const { exitQuestionSectionEnabled, selfieCaptureEnabled } = useContext(FeatureFlagContext);
const defaultSideProps = {
registerField,
@@ -74,7 +72,6 @@ function DocumentCaptureReviewIssues({
)}
- {notReadySectionEnabled && }
{exitQuestionSectionEnabled && }
>
diff --git a/app/javascript/packages/document-capture/components/documents-step.tsx b/app/javascript/packages/document-capture/components/documents-step.tsx
index 8bb2d7fd0e5..2b78b104426 100644
--- a/app/javascript/packages/document-capture/components/documents-step.tsx
+++ b/app/javascript/packages/document-capture/components/documents-step.tsx
@@ -12,7 +12,6 @@ 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';
import DocumentCaptureAbandon from './document-capture-abandon';
@@ -110,8 +109,7 @@ function DocumentsStep({
const { isMobile } = useContext(DeviceContext);
const { isLastStep } = useContext(FormStepsContext);
const { flowPath } = useContext(UploadContext);
- const { notReadySectionEnabled, exitQuestionSectionEnabled, selfieCaptureEnabled } =
- useContext(FeatureFlagContext);
+ const { exitQuestionSectionEnabled, selfieCaptureEnabled } = useContext(FeatureFlagContext);
const pageHeaderText = selfieCaptureEnabled
? t('doc_auth.headings.document_capture_with_selfie')
@@ -142,7 +140,6 @@ function DocumentsStep({
)}
{isLastStep ? : }
- {notReadySectionEnabled && }
{exitQuestionSectionEnabled && }
>
diff --git a/app/javascript/packages/document-capture/context/feature-flag.tsx b/app/javascript/packages/document-capture/context/feature-flag.tsx
index 8e9ad79ef83..ecd39bed17d 100644
--- a/app/javascript/packages/document-capture/context/feature-flag.tsx
+++ b/app/javascript/packages/document-capture/context/feature-flag.tsx
@@ -1,11 +1,6 @@
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;
/**
* Specify whether to show exit optional questions on doc capture screen.
*/
@@ -17,7 +12,6 @@ export interface FeatureFlagContextProps {
}
const FeatureFlagContext = createContext({
- notReadySectionEnabled: false,
exitQuestionSectionEnabled: false,
selfieCaptureEnabled: false,
});
diff --git a/app/javascript/packs/document-capture.tsx b/app/javascript/packs/document-capture.tsx
index 93b4230e66b..01e37996959 100644
--- a/app/javascript/packs/document-capture.tsx
+++ b/app/javascript/packs/document-capture.tsx
@@ -37,7 +37,6 @@ interface AppRootData {
securityAndPrivacyHowItWorksUrl: string;
skipDocAuth: string;
howToVerifyURL: string;
- uiNotReadySectionEnabled: string;
uiExitQuestionSectionEnabled: string;
}
@@ -105,7 +104,6 @@ const {
usStatesTerritories = '',
skipDocAuth,
howToVerifyUrl,
- uiNotReadySectionEnabled = '',
uiExitQuestionSectionEnabled = '',
} = appRoot.dataset as DOMStringMap & AppRootData;
@@ -189,7 +187,6 @@ const App = composeComponents(
FeatureFlagContext.Provider,
{
value: {
- notReadySectionEnabled: String(uiNotReadySectionEnabled) === 'true',
exitQuestionSectionEnabled: String(uiExitQuestionSectionEnabled) === 'true',
selfieCaptureEnabled: getSelfieCaptureEnabled(),
},
diff --git a/app/jobs/reports/irs_weekly_summary_report.rb b/app/jobs/reports/irs_weekly_summary_report.rb
deleted file mode 100644
index cae28a2c1cd..00000000000
--- a/app/jobs/reports/irs_weekly_summary_report.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-require 'csv'
-
-module Reports
- class IrsWeeklySummaryReport < BaseReport
- attr_reader :report_date
- REPORT_NAME = 'irs-weekly-summary-report'
-
- def perform(report_date)
- @name = REPORT_NAME
- @report_date = report_date
-
- email = IdentityConfig.store.system_demand_report_email
- ReportMailer.system_demand_report(
- email: email,
- data: generate_csv,
- name: REPORT_NAME,
- ).deliver_now
-
- # save report has a predefined bucket where things get saved
- # upload_file_to_s3_bucket can be used to define specific buckets (ie. public/private)
- save_report(
- REPORT_NAME,
- generate_csv,
- extension: 'csv',
- )
- end
-
- private
-
- # The total number of users registered with Login.gov
- def query_system_demand
- User.where('created_at <= ?', report_date.beginning_of_day).count
- end
-
- def generate_csv
- CSV.generate do |csv|
- csv << [
- 'Data Requested',
- 'Total Count',
- ]
- csv << [
- 'System Demand',
- query_system_demand,
- ]
- end
- end
- end
-end
diff --git a/app/mailers/report_mailer.rb b/app/mailers/report_mailer.rb
index 47a32155ce6..bfe7f2d475c 100644
--- a/app/mailers/report_mailer.rb
+++ b/app/mailers/report_mailer.rb
@@ -16,12 +16,6 @@ def deleted_user_accounts_report(email:, name:, issuers:, data:)
mail(to: email, subject: t('report_mailer.deleted_accounts_report.subject'))
end
- def system_demand_report(email:, data:, name:)
- @name = name
- attachments['system_demand.csv'] = data
- mail(to: email, subject: t('report_mailer.system_demand_report.subject'))
- end
-
def warn_error(email:, error:, env: Rails.env)
@error = error
mail(to: email, subject: "[#{env}] identity-idp error: #{error.class.name}")
diff --git a/app/models/document_capture_session.rb b/app/models/document_capture_session.rb
index 867ec34fe68..d318fffe386 100644
--- a/app/models/document_capture_session.rb
+++ b/app/models/document_capture_session.rb
@@ -17,6 +17,9 @@ def store_result_from_response(doc_auth_response)
session_result.captured_at = Time.zone.now
session_result.attention_with_barcode = doc_auth_response.attention_with_barcode?
session_result.selfie_check_performed = doc_auth_response.selfie_check_performed?
+ session_result.doc_auth_success = doc_auth_response.doc_auth_success?
+ # nil(selfie not required) or true/false
+ session_result.selfie_success = doc_auth_response.selfie_success
EncryptedRedisStructStorage.store(
session_result,
expires_in: IdentityConfig.store.doc_capture_request_valid_for_minutes.minutes.seconds.to_i,
@@ -25,12 +28,15 @@ def store_result_from_response(doc_auth_response)
save!
end
- def store_failed_auth_image_fingerprint(front_image_fingerprint, back_image_fingerprint)
+ def store_failed_auth_data(front_image_fingerprint:, back_image_fingerprint:, doc_auth_success:,
+ selfie_success:)
session_result = load_result || DocumentCaptureSessionResult.new(
id: generate_result_id,
)
session_result.success = false
session_result.captured_at = Time.zone.now
+ session_result.doc_auth_success = doc_auth_success
+ session_result.selfie_success = selfie_success
session_result.add_failed_front_image!(front_image_fingerprint) if front_image_fingerprint
session_result.add_failed_back_image!(back_image_fingerprint) if back_image_fingerprint
EncryptedRedisStructStorage.store(
diff --git a/app/services/doc_auth/acuant/responses/get_results_response.rb b/app/services/doc_auth/acuant/responses/get_results_response.rb
index 1b32808bdcc..eb6ca35d34b 100644
--- a/app/services/doc_auth/acuant/responses/get_results_response.rb
+++ b/app/services/doc_auth/acuant/responses/get_results_response.rb
@@ -43,6 +43,10 @@ def attention_with_barcode?
end
end
+ def doc_auth_success?
+ passed_result?
+ end
+
private
attr_reader :http_response
@@ -67,6 +71,8 @@ def create_response_info
tamper_result: tamper_result_code&.name,
classification_info: classification_info,
address_line2_present: !pii_from_doc[:address2].blank?,
+ doc_auth_success: doc_auth_success?,
+ selfie_success: nil,
}
end
diff --git a/app/services/doc_auth/lexis_nexis/responses/true_id_response.rb b/app/services/doc_auth/lexis_nexis/responses/true_id_response.rb
index c9c3c2244c6..0bdf08488c2 100644
--- a/app/services/doc_auth/lexis_nexis/responses/true_id_response.rb
+++ b/app/services/doc_auth/lexis_nexis/responses/true_id_response.rb
@@ -142,6 +142,23 @@ def billed?
!!doc_auth_result
end
+ def doc_auth_success?
+ transaction_status_passed? &&
+ true_id_product.present? &&
+ product_status_passed? &&
+ doc_auth_result_passed?
+ end
+
+ # @return [Boolean, nil]
+ # When selfie result is missing, return nil
+ # Otherwise:
+ # return true if selfie check result == 'Pass'
+ # return false
+ def selfie_success
+ return selfie_result if selfie_result.nil?
+ selfie_result == 'Pass'
+ end
+
private
def conversation_id
@@ -209,7 +226,7 @@ def create_response_info
processed_alerts: alerts,
alert_failure_count: alerts[:failed]&.count.to_i,
log_alert_results: log_alert_formatter.log_alerts(alerts),
- portrait_match_results: true_id_product[:PORTRAIT_MATCH_RESULT],
+ portrait_match_results: true_id_product&.dig(:PORTRAIT_MATCH_RESULT),
image_metrics: parse_image_metrics,
address_line2_present: !pii_from_doc[:address2].blank?,
classification_info: classification_info,
@@ -232,6 +249,10 @@ def all_passed?
doc_auth_result_passed?
end
+ def selfie_result
+ response_info&.dig(:portrait_match_results, :FaceMatchResult)
+ end
+
def product_status_passed?
product_status == 'pass'
end
@@ -294,6 +315,7 @@ def parsed_alerts
return @new_alerts if defined?(@new_alerts)
@new_alerts = { passed: [], failed: [] }
+ return @new_alerts unless true_id_product&.dig(:AUTHENTICATION_RESULT).present?
all_alerts = true_id_product[:AUTHENTICATION_RESULT].select do |key|
key.start_with?('Alert_')
end
@@ -334,7 +356,7 @@ def combine_alert_data(all_alerts, alert_name, region_details)
def parse_image_metrics
image_metrics = {}
-
+ return image_metrics unless true_id_product&.dig(:ParameterDetails).present?
true_id_product[:ParameterDetails].each do |detail|
next unless detail[:Group] == 'IMAGE_METRICS_RESULT'
diff --git a/app/services/doc_auth/mock/result_response.rb b/app/services/doc_auth/mock/result_response.rb
index cbe64c22810..fd3d6524655 100644
--- a/app/services/doc_auth/mock/result_response.rb
+++ b/app/services/doc_auth/mock/result_response.rb
@@ -130,6 +130,15 @@ def self.create_network_error_response
)
end
+ def doc_auth_success?
+ doc_auth_result_from_uploaded_file == 'Passed' || errors.blank?
+ end
+
+ def selfie_success
+ return nil if portrait_match_results&.dig(:FaceMatchResult).nil?
+ portrait_match_results[:FaceMatchResult] == 'Pass'
+ end
+
private
def parsed_alerts
diff --git a/app/services/doc_auth/response.rb b/app/services/doc_auth/response.rb
index 3b6ea9d0532..f002df6c97f 100644
--- a/app/services/doc_auth/response.rb
+++ b/app/services/doc_auth/response.rb
@@ -56,6 +56,8 @@ def to_h
exception: exception,
attention_with_barcode: attention_with_barcode?,
doc_type_supported: doc_type_supported?,
+ doc_auth_success: doc_auth_success?,
+ selfie_success: selfie_success,
}.merge(extra)
end
@@ -77,5 +79,14 @@ def network_error?
def selfie_check_performed?
@selfie_check_performed
end
+
+ def selfie_success
+ # to be implemented by concrete subclass
+ end
+
+ def doc_auth_success?
+ # to be implemented by concrete subclass
+ false
+ end
end
end
diff --git a/app/services/document_capture_session_result.rb b/app/services/document_capture_session_result.rb
index 1acb7f10458..9b9f0f560bd 100644
--- a/app/services/document_capture_session_result.rb
+++ b/app/services/document_capture_session_result.rb
@@ -10,9 +10,11 @@
:failed_back_image_fingerprints,
:captured_at,
:selfie_check_performed,
+ :doc_auth_success, :selfie_success,
keyword_init: true,
allowed_members: [:id, :success, :attention_with_barcode, :failed_front_image_fingerprints,
- :failed_back_image_fingerprints, :captured_at, :selfie_check_performed],
+ :failed_back_image_fingerprints, :captured_at, :selfie_check_performed,
+ :doc_auth_success, :selfie_success]
) do
def self.redis_key_prefix
'dcs:result'
diff --git a/app/views/idv/in_person/ssn/show.html.erb b/app/views/idv/in_person/ssn/show.html.erb
deleted file mode 100644
index 519a01a623b..00000000000
--- a/app/views/idv/in_person/ssn/show.html.erb
+++ /dev/null
@@ -1,88 +0,0 @@
-<%#
-Renders a page asking the user to enter their SSN or update their SSN if they had previously entered it.
-
-locals:
-* updating_ssn: true if the user is updating their SSN instead of providing it for the first time. This
- will render a different page heading and different navigation buttons in the page footer
-* threatmetrix_session_id: A session identifier needed by the ThreatMetrix tool
-* threatmetrix_javascript_urls:: URLs to add to script tags to load the ThreatMetrix javascript
-* threatmetrix_iframe_url: A URL to add to the page for Threatmetrix
-%>
-
-<% content_for(:pre_flash_content) do %>
- <%= render StepIndicatorComponent.new(
- steps: Idv::Flows::InPersonFlow::STEP_INDICATOR_STEPS,
- current_step: :verify_info,
- locale_scope: 'idv',
- class: 'margin-x-neg-2 margin-top-neg-4 tablet:margin-x-neg-6 tablet:margin-top-neg-4',
- ) %>
-<% end %>
-
-<% self.title = t('titles.doc_auth.ssn') %>
-
-<% if updating_ssn %>
- <%= render PageHeadingComponent.new.with_content(t('doc_auth.headings.ssn_update')) %>
-<% else %>
- <%= render PageHeadingComponent.new.with_content(t('doc_auth.headings.ssn')) %>
-<% end %>
-
-
- <%= t('doc_auth.info.ssn') %>
- <%= new_tab_link_to(MarketingSite.security_and_privacy_practices_url, class: 'display-inline') do %>
- <%= t('doc_auth.info.learn_more') %>
- <% end %>
-
-
-<% if FeatureManagement.proofing_device_profiling_collecting_enabled? %>
- <% if threatmetrix_session_id.present? %>
- <% threatmetrix_javascript_urls.each do |threatmetrix_javascript_url| %>
- <%= javascript_include_tag threatmetrix_javascript_url, nonce: true %>
- <% end %>
-
- <%= content_tag(
- :iframe,
- '',
- src: threatmetrix_iframe_url,
- style: 'width: 100px; height: 100px; border: 0; position: absolute; top: -5000px;',
- ) %>
-
- <% end %>
-<% end %>
-
-<% if IdentityConfig.store.proofer_mock_fallback %>
-
-
-
- <%= t('doc_auth.instructions.test_ssn') %>
-
-
-
-<% end %>
-
-<%= simple_form_for(
- Idv::SsnFormatForm.new(current_user, nil),
- url: url_for,
- method: :put,
- html: { autocomplete: 'off' },
- ) do |f| %>
-
- <%= render 'shared/ssn_field', f: f %>
-
-
-<%= @error_message %>
-
-<%= f.submit class: 'display-block margin-y-5' do %>
- <% if updating_ssn %>
- <%= t('forms.buttons.submit.update') %>
- <% else %>
- <%= t('forms.buttons.continue') %>
- <% end %>
- <% end %>
-<% end %>
-
-<% if updating_ssn %>
- <%= render 'idv/shared/back', fallback_path: idv_in_person_verify_info_path %>
-<% else %>
- <%= render 'idv/doc_auth/cancel', step: 'ssn' %>
-<% end %>
-
diff --git a/app/views/idv/shared/_document_capture.html.erb b/app/views/idv/shared/_document_capture.html.erb
index abf399a02bd..ee6d416e0eb 100644
--- a/app/views/idv/shared/_document_capture.html.erb
+++ b/app/views/idv/shared/_document_capture.html.erb
@@ -39,7 +39,6 @@
doc_auth_selfie_capture: FeatureManagement.idv_allow_selfie_check? && doc_auth_selfie_capture,
skip_doc_auth: skip_doc_auth,
how_to_verify_url: idv_how_to_verify_url,
- ui_not_ready_section_enabled: IdentityConfig.store.doc_auth_not_ready_section_enabled,
ui_exit_question_section_enabled: IdentityConfig.store.doc_auth_exit_question_section_enabled,
} %>
<%= simple_form_for(
diff --git a/app/views/report_mailer/system_demand_report.html.erb b/app/views/report_mailer/system_demand_report.html.erb
deleted file mode 100644
index 1a58e539b16..00000000000
--- a/app/views/report_mailer/system_demand_report.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= t('report_mailer.system_demand_report.name') %>: <%= @name %>
diff --git a/config/application.yml.default b/config/application.yml.default
index 45bd6da339a..85d074759e5 100644
--- a/config/application.yml.default
+++ b/config/application.yml.default
@@ -87,7 +87,6 @@ doc_auth_exit_question_section_enabled: false
doc_auth_max_attempts: 5
doc_auth_max_capture_attempts_before_native_camera: 3
doc_auth_max_submission_attempts_before_native_camera: 3
-doc_auth_not_ready_section_enabled: false
doc_auth_selfie_capture_enabled: false
doc_auth_sdk_capture_orientation: '{"horizontal": 100, "vertical": 0}'
doc_auth_supported_country_codes: '["US", "GU", "VI", "AS", "MP", "PR", "USA" ,"GUM", "VIR", "ASM", "MNP", "PRI"]'
@@ -314,7 +313,6 @@ sp_handoff_bounce_max_seconds: 2
show_unsupported_passkey_platform_authentication_setup: false
show_user_attribute_deprecation_warnings: false
otp_min_attempts_remaining_warning_count: 3
-system_demand_report_email: 'foo@bar.com'
sp_issuer_user_counts_report_configs: '[]'
team_ada_email: ''
team_all_login_emails: '[]'
@@ -369,6 +367,7 @@ development:
attribute_encryption_key_queue: '[{ "key": "11111111111111111111111111111111" }, { "key": "22222222222222222222222222222222" }]'
aws_logo_bucket: ''
component_previews_enabled: true
+ component_previews_embed_frame_ancestors: '["http://localhost:4000"]'
dashboard_api_token: test_token
dashboard_url: http://localhost:3001/api/service_providers
database_host: ''
@@ -383,7 +382,6 @@ development:
database_worker_jobs_host: ''
database_worker_jobs_password: ''
doc_auth_exit_question_section_enabled: false
- doc_auth_not_ready_section_enabled: false
doc_auth_selfie_capture_enabled: false
doc_auth_vendor: 'mock'
doc_auth_vendor_randomize: false
diff --git a/config/initializers/job_configurations.rb b/config/initializers/job_configurations.rb
index 29d3b053ffd..f6d21134c13 100644
--- a/config/initializers/job_configurations.rb
+++ b/config/initializers/job_configurations.rb
@@ -146,12 +146,6 @@
class: 'ThreatMetrixJsVerificationJob',
cron: cron_1h,
},
- # Weekly IRS report returning system demand
- irs_weekly_summary_report: {
- class: 'Reports::IrsWeeklySummaryReport',
- cron: cron_1w,
- args: -> { [Time.zone.now] },
- },
# Reject profiles that have been in fraud_review_pending for 30 days
fraud_rejection: {
class: 'FraudRejectionDailyJob',
diff --git a/config/locales/doc_auth/en.yml b/config/locales/doc_auth/en.yml
index 6334869c8bc..3bc08d364c2 100644
--- a/config/locales/doc_auth/en.yml
+++ b/config/locales/doc_auth/en.yml
@@ -220,6 +220,11 @@ en:
secure_account: We’ll encrypt your account with your password. Encryption means
your data is protected and only you will be able to access or change
your information.
+ selfie_capture_status:
+ face_close_to_border: TOO CLOSE TO THE FRAME
+ face_not_found: FACE NOT FOUND
+ face_too_small: FACE TOO SMALL
+ too_many_faces: TOO MANY FACES
ssn: We need your Social Security number to verify your name, date of birth and
address.
tag: Recommended
@@ -263,15 +268,6 @@ en:
- 'Verify by mail : We’ll mail a letter to your home
address. This takes 5 to 10 days .'
welcome: 'You will need your:'
- not_ready:
- button_nosp: Cancel and return to your profile
- button_sp: Exit %{app_name} and return to %{sp_name}
- content_nosp: If you exit %{app_name} now, you will not have verified your
- identity. You can return later to finish this process.
- content_sp: If you exit %{app_name} now and return to %{sp_name}, you will not
- have verified your identity. You can return later to finish this
- process.
- header: Not ready to add photos?
tips:
document_capture_hint: Must be a JPG or PNG
document_capture_id_text1: Use a dark background
diff --git a/config/locales/doc_auth/es.yml b/config/locales/doc_auth/es.yml
index ac12ab0e65f..9aa65026e33 100644
--- a/config/locales/doc_auth/es.yml
+++ b/config/locales/doc_auth/es.yml
@@ -258,6 +258,11 @@ es:
secure_account: Vamos a encriptar su cuenta con su contraseña. La encriptación
significa que sus datos están protegidos y solo usted podrá acceder o
modificar su información.
+ selfie_capture_status:
+ face_close_to_border: DEMASIADO CERCA DEL MARCO
+ face_not_found: NO SE ENCONTRÓ LA CARA
+ face_too_small: LA CARA ES DEMASIADO CHICA
+ too_many_faces: HAY DEMASIADAS CARAS
ssn: Necesitamos su número de Seguro Social para validar su nombre, fecha de
nacimiento y dirección.
tag: Recomendado
@@ -306,15 +311,6 @@ es:
- 'Verificar por correo : Le enviaremos una carta a su
domicilio. Esto tarda entre 5 y 10 días .'
welcome: 'Necesitará su:'
- not_ready:
- button_nosp: Cancelar y volver a su perfil
- button_sp: Salir de %{app_name} y volver a %{sp_name}
- content_nosp: Si sale ahora de %{app_name}, no habrá verificado su identidad.
- Puede volver más tarde para completar este proceso.
- content_sp: Si sale ahora de %{app_name} y regresa a %{sp_name}, no habrá
- verificado su identidad. Puede volver más tarde para completar este
- proceso.
- header: ¿No está listo para enviar las fotos?
tips:
document_capture_hint: Debe ser un JPG o PNG
document_capture_id_text1: Use un fondo oscuro
diff --git a/config/locales/doc_auth/fr.yml b/config/locales/doc_auth/fr.yml
index 846a3de8e9e..f4111c0fe0b 100644
--- a/config/locales/doc_auth/fr.yml
+++ b/config/locales/doc_auth/fr.yml
@@ -267,6 +267,11 @@ fr:
secure_account: Nous chiffrerons votre compte avec votre mot de passe. Le
chiffrage signifie que vos données sont protégées et que vous êtes le/la
seul(e) à pouvoir accéder à vos informations ou les modifier.
+ selfie_capture_status:
+ face_close_to_border: TROP PRÈS DU CADRE
+ face_not_found: VISAGE NON TROUVÉ
+ face_too_small: VISAGE TROP PETIT
+ too_many_faces: TROP DE VISAGES
ssn: Nous avons besoin de votre numéro de sécurité sociale pour vérifier votre
nom, date de naissance et adresse.
tag: Recommandation
@@ -318,15 +323,6 @@ fr:
lettre à votre adresse personnelle. Cela prend 5 à 10
jours .'
welcome: 'Vous aurez besoin de votre:'
- not_ready:
- button_nosp: Annuler et revenir à votre profil
- button_sp: Quittez %{app_name} et retournez à %{sp_name}
- content_nosp: Si vous quittez %{app_name}, votre identité n’aura pas été
- vérifiée. Vous pourrez revenir plus tard pour terminer ce processus.
- content_sp: Si vous quittez %{app_name} maintenant et revenez sur %{sp_name},
- votre identité n’aura pas été vérifiée. Vous pourrez revenir plus tard
- pour terminer ce processus.
- header: Vous n’êtes pas prêt à ajouter des photos?
tips:
document_capture_hint: Doit être un JPG ou PNG
document_capture_id_text1: Utilisez un fond sombre
diff --git a/config/locales/report_mailer/en.yml b/config/locales/report_mailer/en.yml
index 25737426436..f71e93c2ebb 100644
--- a/config/locales/report_mailer/en.yml
+++ b/config/locales/report_mailer/en.yml
@@ -5,6 +5,3 @@ en:
issuers: Issuers
name: Name
subject: Deleted accounts report
- system_demand_report:
- name: Name
- subject: System demand report
diff --git a/config/locales/report_mailer/es.yml b/config/locales/report_mailer/es.yml
index 05922400d5e..328c1e881b4 100644
--- a/config/locales/report_mailer/es.yml
+++ b/config/locales/report_mailer/es.yml
@@ -5,6 +5,3 @@ es:
issuers: Emisores
name: Nombre
subject: Informe de cuentas eliminadas
- system_demand_report:
- name: Nombre
- subject: Informe de demanda del sistema
diff --git a/config/locales/report_mailer/fr.yml b/config/locales/report_mailer/fr.yml
index 098761d4ace..4764291a220 100644
--- a/config/locales/report_mailer/fr.yml
+++ b/config/locales/report_mailer/fr.yml
@@ -5,6 +5,3 @@ fr:
issuers: Émetteurs
name: Nom
subject: Rapport sur les comptes supprimés
- system_demand_report:
- name: Nom
- subject: Rapport de demande du système
diff --git a/dockerfiles/idp_ci.Dockerfile b/dockerfiles/idp_ci.Dockerfile
index 2b020e22469..dca69ddb1fe 100644
--- a/dockerfiles/idp_ci.Dockerfile
+++ b/dockerfiles/idp_ci.Dockerfile
@@ -1,4 +1,4 @@
-FROM public.ecr.aws/docker/library/ruby:3.2.2-bullseye
+FROM public.ecr.aws/docker/library/ruby:3.3.0-bullseye
ENV NODE_MAJOR 20
diff --git a/dockerfiles/idp_review_app.Dockerfile b/dockerfiles/idp_review_app.Dockerfile
index a163885d665..839115cc1d1 100644
--- a/dockerfiles/idp_review_app.Dockerfile
+++ b/dockerfiles/idp_review_app.Dockerfile
@@ -1,4 +1,4 @@
-FROM ruby:3.2.2-slim
+FROM ruby:3.3.0-slim
# Set environment variables
ARG ARG_CI_ENVIRONMENT_SLUG="placeholder"
diff --git a/lib/identity_config.rb b/lib/identity_config.rb
index 161caa0b550..41ce1602b58 100644
--- a/lib/identity_config.rb
+++ b/lib/identity_config.rb
@@ -182,7 +182,6 @@ def self.build_store(config_map)
config.add(:doc_auth_error_glare_threshold, type: :integer)
config.add(:doc_auth_error_sharpness_threshold, type: :integer)
config.add(:doc_auth_exit_question_section_enabled, type: :boolean)
- config.add(:doc_auth_not_ready_section_enabled, type: :boolean)
config.add(:doc_auth_max_attempts, 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)
@@ -452,7 +451,6 @@ def self.build_store(config_map)
config.add(:sp_handoff_bounce_max_seconds, type: :integer)
config.add(:sp_issuer_user_counts_report_configs, type: :json)
config.add(:state_tracking_enabled, type: :boolean)
- config.add(:system_demand_report_email, type: :string)
config.add(:team_ada_email, type: :string)
config.add(:team_all_login_emails, type: :json)
config.add(:team_daily_reports_emails, type: :json)
diff --git a/scripts/create-deploy-pr b/scripts/create-deploy-pr
new file mode 100755
index 00000000000..a8623193067
--- /dev/null
+++ b/scripts/create-deploy-pr
@@ -0,0 +1,112 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+ORIGIN=${ORIGIN:-origin}
+SOURCE=${SOURCE:-}
+DEPLOY_BRANCH=stages/prod
+PATCH=${PATCH:-}
+DRY_RUN=${DRY_RUN:-0}
+CHANGELOG_FILE=${CHANGELOG_FILE:-.rc-changelog.md}
+
+function get_last_rc {
+ GH_OUTPUT=$(gh release list --exclude-drafts --exclude-pre-releases --limit 1 || true)
+ if [ -z "$GH_OUTPUT" ]; then
+ echo "Failed to get latest released" >&2
+ exit 1
+ fi
+
+ LAST_RC=$(echo "$GH_OUTPUT" | grep -E --only-matching 'RC [0-9]+(\.[0-9]+)?' | sed 's/RC //')
+ if [ -z "$LAST_RC" ]; then
+ echo 0
+ else
+ echo "$LAST_RC"
+ fi
+}
+
+function get_next_rc {
+ LAST_RC="$1"; shift
+ MAJOR=$(echo "$LAST_RC" | sed -E 's/\.[0-9]+//')
+ MINOR=$(echo "$LAST_RC" | sed -E 's/[0-9]+(\.|$)//')
+
+ if [ "$PATCH" == "1" ]; then
+ # Doing a patch, so increment minor version by 1
+ if [ -z "$MINOR" ]; then
+ MINOR=0
+ fi
+
+ MINOR=$((MINOR + 1))
+ else
+ # Not doing a patch, clear minor and increment major
+ MAJOR=$((MAJOR + 1))
+ MINOR=0
+ fi
+
+ if [ "$MINOR" == "0" ]; then
+ echo "$MAJOR"
+ else
+ echo "$MAJOR.$MINOR"
+ fi
+}
+
+function get_staging_sha {
+ curl --silent https://idp.staging.login.gov/api/deploy.json | jq -r .git_sha
+}
+
+if [ -z "${CI:-}" ]; then
+ echo "This script is meant to be run in a continuous integration environment."
+ exit 1
+fi
+
+if [ -z "${GH_TOKEN:-}" ] && [ "$DRY_RUN" == "0" ]; then
+ echo "You must set the GH_TOKEN environment variable."
+ exit 1
+fi
+
+RC_BRANCH=stages/rc-$(date +'%Y-%m-%d')
+if git rev-parse "$ORIGIN/$RC_BRANCH" > /dev/null 2>&1; then
+ echo "RC branch $RC_BRANCH already exists. Delete that branch and re-run this workflow to create a PR." >&2
+ exit 1
+fi
+
+LAST_RC=$(get_last_rc)
+NEXT_RC=$(get_next_rc "$LAST_RC")
+echo "Last RC was ${LAST_RC}. The next RC will be ${NEXT_RC}."
+
+if [ -z "$SOURCE" ]; then
+ SHA=$(get_staging_sha)
+ echo "Staging currently running ${SHA}"
+else
+ SHA=$(git rev-parse "$SOURCE" || true)
+ if [ -z $SHA ]; then
+ echo "Invalid source: '$SOURCE'"
+ exit 17
+ elif [ "$SOURCE" == "$SHA" ]; then
+ echo "Using $SHA as the source"
+ else
+ echo "Using '$SOURCE' ($SHA) as the source"
+ fi
+fi
+
+echo "Building changelog..."
+scripts/changelog_check.rb -s "$SHA" -b "${ORIGIN}/${DEPLOY_BRANCH}" > "$CHANGELOG_FILE"
+
+if [[ $DRY_RUN -eq 0 ]]; then
+ echo "Pushing $RC_BRANCH to origin..."
+ git push $ORIGIN "$SHA:refs/heads/$RC_BRANCH"
+
+ # Create PR
+ echo "Creating PR..."
+ gh pr create \
+ --title "Deploy RC ${NEXT_RC} to Production" \
+ --label 'status - promotion' \
+ --base "$DEPLOY_BRANCH" \
+ --head "$RC_BRANCH" \
+ --body-file "$CHANGELOG_FILE"
+else
+ echo "Dry run. Not creating PR."
+fi
+
+echo "# Changelog"
+cat "$CHANGELOG_FILE" && rm "$CHANGELOG_FILE"
+
diff --git a/scripts/create-release b/scripts/create-release
new file mode 100755
index 00000000000..92ef2a73e42
--- /dev/null
+++ b/scripts/create-release
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+DEPLOY_BRANCH=stages/prod
+PR_JSON_FILE=${PR_JSON_FILE:-.pr.json}
+CHANGELOG_FILE=${CHANGELOG_FILE:-.changelog.md}
+
+USAGE="
+${0} [PULL_REQUEST_NUMBER]
+
+Creates a new release based on the given PR having been merged.
+"
+
+if [ $# -eq 0 ]; then
+ echo $USAGE
+ exit 1
+fi
+
+PR="$1"; shift
+
+if [ -z "${CI:-}" ]; then
+ echo "This script is meant to be run in a continuous integration environment."
+ exit 1
+fi
+
+if [ -z "${GH_TOKEN:-}" ]; then
+ echo "You must set the GH_TOKEN environment variable."
+ exit 1
+fi
+
+
+echo "Getting PR ${PR} data..."
+gh pr list \
+ --json number,title,body \
+ --base "$DEPLOY_BRANCH" \
+ --state merged \
+ jq ".[] | select(.number == ${PR})" > "$PR_JSON_FILE"
+
+if [ ! -s "$PR_JSON_FILE" ]; then
+ echo "PR $PR not found."
+ exit 9
+fi
+
+RC=$(jq --raw-output '.title' < "$PR_JSON_FILE" | sed -E 's/Deploy RC (.+) to .*/\1/')
+jq --raw-output '.body' < "$PR_JSON_FILE" > "$CHANGELOG_FILE"
+TITLE="RC $RC"
+
+echo "Checking for existing release '$TITLE'..."
+EXISTING_RELEASE=$(gh release list --exclude-drafts | (grep "$TITLE" || true))
+
+if [ ! -z "$EXISTING_RELEASE" ]; then
+ echo "❌ Release already exists: $TITLE" >&2
+ exit 10
+else
+ echo "No existing release found."
+fi
+
+TAG=$(date -u +'%Y-%m-%dT%H%M%S')
+
+echo "Creating release $TITLE with tag $TAG..."
+gh release create \
+ "$TAG" \
+ --latest \
+ --target "$GITHUB_SHA" \
+ --title "$TITLE" \
+ --notes-file "$CHANGELOG_FILE"
diff --git a/spec/components/previews/login_button_component_preview.rb b/spec/components/previews/login_button_component_preview.rb
index bef99f6f771..ff36acdc8cd 100644
--- a/spec/components/previews/login_button_component_preview.rb
+++ b/spec/components/previews/login_button_component_preview.rb
@@ -5,9 +5,8 @@ def default
end
# @!endgroup
-
- # @param big toggle
- # @param color select [~,primary,primary-darker,primary-lighter]
+ # @param big toggle "Change button size"
+ # @param color select [primary,primary-darker,primary-lighter] "Select button color"
def workbench(
big: false,
color: 'primary'
diff --git a/spec/controllers/idv/image_uploads_controller_spec.rb b/spec/controllers/idv/image_uploads_controller_spec.rb
index e1b61ae7e99..d762b5fc16e 100644
--- a/spec/controllers/idv/image_uploads_controller_spec.rb
+++ b/spec/controllers/idv/image_uploads_controller_spec.rb
@@ -434,6 +434,8 @@
front_image_fingerprint: an_instance_of(String),
back_image_fingerprint: an_instance_of(String),
doc_type_supported: boolean,
+ doc_auth_success: boolean,
+ selfie_success: nil,
)
expect(@analytics).to receive(:track_event).with(
@@ -610,6 +612,8 @@
front_image_fingerprint: an_instance_of(String),
back_image_fingerprint: an_instance_of(String),
doc_type_supported: boolean,
+ doc_auth_success: boolean,
+ selfie_success: nil,
)
expect(@analytics).to receive(:track_event).with(
@@ -699,6 +703,8 @@
front_image_fingerprint: an_instance_of(String),
back_image_fingerprint: an_instance_of(String),
doc_type_supported: boolean,
+ doc_auth_success: boolean,
+ selfie_success: nil,
)
expect(@analytics).to receive(:track_event).with(
@@ -788,6 +794,8 @@
front_image_fingerprint: an_instance_of(String),
back_image_fingerprint: an_instance_of(String),
doc_type_supported: boolean,
+ doc_auth_success: boolean,
+ selfie_success: nil,
)
expect(@analytics).to receive(:track_event).with(
@@ -874,6 +882,8 @@
front_image_fingerprint: an_instance_of(String),
back_image_fingerprint: an_instance_of(String),
doc_type_supported: boolean,
+ doc_auth_success: boolean,
+ selfie_success: nil,
)
expect(@analytics).to receive(:track_event).with(
@@ -985,6 +995,8 @@
front_image_fingerprint: an_instance_of(String),
back_image_fingerprint: an_instance_of(String),
doc_type_supported: boolean,
+ doc_auth_success: boolean,
+ selfie_success: nil,
)
action
@@ -1055,6 +1067,8 @@
front_image_fingerprint: an_instance_of(String),
back_image_fingerprint: an_instance_of(String),
doc_type_supported: boolean,
+ doc_auth_success: boolean,
+ selfie_success: nil,
)
action
diff --git a/spec/controllers/idv/in_person/verify_info_controller_spec.rb b/spec/controllers/idv/in_person/verify_info_controller_spec.rb
index f09de384860..55f9ead4f99 100644
--- a/spec/controllers/idv/in_person/verify_info_controller_spec.rb
+++ b/spec/controllers/idv/in_person/verify_info_controller_spec.rb
@@ -293,9 +293,12 @@
end
it 'captures state id address fields in the pii' do
- expect(Idv::Agent).to receive(:new).
- with(Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS.merge(uuid_prefix: nil)).
- and_call_original
+ expect(Idv::Agent).to receive(:new).with(
+ Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS.merge(
+ uuid_prefix: nil,
+ uuid: user.uuid,
+ ),
+ ).and_call_original
put :update
end
end
diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb
index 88b138fbc1b..21f325de2bf 100644
--- a/spec/controllers/idv/verify_info_controller_spec.rb
+++ b/spec/controllers/idv/verify_info_controller_spec.rb
@@ -391,9 +391,14 @@
sp_session = { issuer: sp.issuer }
allow(controller).to receive(:sp_session).and_return(sp_session)
- put :update
+ expect(Idv::Agent).to receive(:new).with(
+ hash_including(
+ uuid_prefix: app_id,
+ uuid: user.uuid,
+ ),
+ ).and_call_original
- expect(subject.idv_session.pii_from_doc[:uuid_prefix]).to eq app_id
+ put :update
end
it 'updates DocAuthLog verify_submit_count' do
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 5a625a1ef05..8c9c2d09ec2 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -10,6 +10,7 @@
confirmed_at { Time.zone.now }
confirmation_token { nil }
confirmation_sent_at { 5.minutes.ago }
+ registered_at { Time.zone.now }
end
created_at { Time.zone.now }
@@ -179,8 +180,8 @@
trait :fully_registered do
with_phone
- after :create do |user|
- user.create_registration_log(registered_at: Time.zone.now)
+ after :create do |user, evaluator|
+ user.create_registration_log(registered_at: evaluator.registered_at)
end
end
diff --git a/spec/features/idv/doc_auth/document_capture_spec.rb b/spec/features/idv/doc_auth/document_capture_spec.rb
index 8d4786ce09e..6c08f36450b 100644
--- a/spec/features/idv/doc_auth/document_capture_spec.rb
+++ b/spec/features/idv/doc_auth/document_capture_spec.rb
@@ -10,13 +10,10 @@
let(:user) { user_with_2fa }
let(:fake_analytics) { FakeAnalytics.new }
let(:sp_name) { 'Test SP' }
- let(:enable_not_ready) { true }
let(:enable_exit_question) { true }
before do
allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics)
allow_any_instance_of(ServiceProviderSession).to receive(:sp_name).and_return(sp_name)
- allow(IdentityConfig.store).to receive(:doc_auth_not_ready_section_enabled).
- and_return(enable_not_ready)
allow(IdentityConfig.store).to receive(:doc_auth_exit_question_section_enabled).
and_return(enable_exit_question)
visit_idp_from_oidc_sp_with_ial2
@@ -156,17 +153,6 @@
)
expect(current_url).to start_with('http://localhost:7654/auth/result?error=access_denied')
end
-
- context 'not ready section' do
- it 'renders not ready section when enabled' do
- expect(page).to have_content(
- I18n.t(
- 'doc_auth.not_ready.content_sp', sp_name: sp_name,
- app_name: APP_NAME
- ),
- )
- end
- end
end
context 'standard mobile flow' do
diff --git a/spec/features/idv/doc_auth/verify_info_step_spec.rb b/spec/features/idv/doc_auth/verify_info_step_spec.rb
index 9eafb35778f..533a9c96fd0 100644
--- a/spec/features/idv/doc_auth/verify_info_step_spec.rb
+++ b/spec/features/idv/doc_auth/verify_info_step_spec.rb
@@ -181,13 +181,15 @@
visit idv_verify_info_url
expect(page).to have_current_path(idv_session_errors_failure_path)
+ # Manual expiration is needed because Redis timestamp doesn't always match ruby timestamp
+ RateLimiter.new(user: user, rate_limit_type: :idv_resolution).reset!
travel_to(IdentityConfig.store.idv_attempt_window_in_hours.hours.from_now + 1) do
sign_in_and_2fa_user(user)
complete_doc_auth_steps_before_verify_step
complete_verify_step
expect(page).to have_current_path(idv_phone_path)
- expect(RateLimiter.new(user: user, rate_limit_type: :idv_resolution)).to be_limited
+ expect(RateLimiter.new(user: user, rate_limit_type: :idv_resolution)).to_not be_limited
end
end
@@ -232,12 +234,15 @@
visit idv_verify_info_url
expect(page).to have_current_path(idv_session_errors_ssn_failure_path)
+ # Manual expiration is needed because Redis timestamp doesn't always match ruby timestamp
+ RateLimiter.new(user: user, rate_limit_type: :idv_resolution).reset!
travel_to(IdentityConfig.store.idv_attempt_window_in_hours.hours.from_now + 1) do
sign_in_and_2fa_user(user)
complete_doc_auth_steps_before_verify_step
complete_verify_step
expect(page).to have_current_path(idv_phone_path)
+ expect(RateLimiter.new(user: user, rate_limit_type: :idv_resolution)).to_not be_limited
end
end
diff --git a/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_success_with_liveness.json b/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_success_with_liveness.json
new file mode 100644
index 00000000000..d158c84de08
--- /dev/null
+++ b/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_success_with_liveness.json
@@ -0,0 +1,1100 @@
+{
+ "Status": {
+ "ConversationId": "70000300394121",
+ "RequestId": "614507871",
+ "TransactionStatus": "passed",
+ "TransactionReasonCode": {
+ "Code": "trueid_pass",
+ "Description": "TRUEID PASS"
+ },
+ "Reference": "ca6e36c4-8a55-4831-aa8a-38d78b7c80e3"
+ },
+ "Products": [
+ {
+ "ProductType": "TrueID",
+ "ExecutedStepName": "True_ID_Step",
+ "ProductConfigurationName": "GSA2.V3.TrueID.CROP.PT.test",
+ "ProductStatus": "pass",
+ "ParameterDetails": [
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocumentName",
+ "Values": [{ "Value": "Maryland (MD) Driver's License - STAR" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocAuthResult",
+ "Values": [{ "Value": "Passed" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocAuthTamperResult",
+ "Values": [{ "Value": "Passed" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocAuthTamperSensitivity",
+ "Values": [{ "Value": "Normal" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocIssuerCode",
+ "Values": [{ "Value": "MD" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocIssuerName",
+ "Values": [{ "Value": "Maryland" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocIssuerType",
+ "Values": [{ "Value": "StateProvince" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocClassCode",
+ "Values": [{ "Value": "DriversLicense" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocClass",
+ "Values": [{ "Value": "DriversLicense" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocClassName",
+ "Values": [{ "Value": "Drivers License" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocIsGeneric",
+ "Values": [{ "Value": "false" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocIssue",
+ "Values": [{ "Value": "2016" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocIssueType",
+ "Values": [{ "Value": "Driver's License - STAR" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DocSize",
+ "Values": [{ "Value": "ID1" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "ClassificationMode",
+ "Values": [{ "Value": "Automatic" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "OrientationChanged",
+ "Values": [{ "Value": "true" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "PresentationChanged",
+ "Values": [{ "Value": "false" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "Side",
+ "Values": [{ "Value": "Front" }, { "Value": "Back" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "GlareMetric",
+ "Values": [{ "Value": "100" }, { "Value": "100" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "SharpnessMetric",
+ "Values": [{ "Value": "65" }, { "Value": "65" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "IsTampered",
+ "Values": [{ "Value": "0" }, { "Value": "0" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "IsCropped",
+ "Values": [{ "Value": "1" }, { "Value": "1" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "HorizontalResolution",
+ "Values": [{ "Value": "600" }, { "Value": "600" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "VerticalResolution",
+ "Values": [{ "Value": "600" }, { "Value": "600" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "Light",
+ "Values": [{ "Value": "White" }, { "Value": "White" }]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "MimeType",
+ "Values": [
+ { "Value": "image/vnd.ms-photo" },
+ { "Value": "image/vnd.ms-photo" }
+ ]
+ },
+ {
+ "Group": "IMAGE_METRICS_RESULT",
+ "Name": "ImageMetrics_Id",
+ "Values": [
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "637aa4c6-eeb3-453f-899e-a56effcf3747" }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "FullName",
+ "Values": [{ "Value": "DAVID LICENSE SAMPLE" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Sex",
+ "Values": [{ "Value": "Male" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Age",
+ "Values": [{ "Value": "33" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DOB_Year",
+ "Values": [{ "Value": "1985" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DOB_Month",
+ "Values": [{ "Value": "7" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "DOB_Day",
+ "Values": [{ "Value": "1" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "ExpirationDate_Year",
+ "Values": [{ "Value": "2099" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "ExpirationDate_Month",
+ "Values": [{ "Value": "10" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "ExpirationDate_Day",
+ "Values": [{ "Value": "15" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_1_AlertName",
+ "Values": [{ "Value": "Document Expired" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_2_AlertName",
+ "Values": [{ "Value": "Visible Pattern" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_3_AlertName",
+ "Values": [{ "Value": "Document Tampering Detection" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_4_AlertName",
+ "Values": [{ "Value": "2D Barcode Content" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_5_AlertName",
+ "Values": [{ "Value": "2D Barcode Read" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_6_AlertName",
+ "Values": [{ "Value": "Barcode Encoding" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_7_AlertName",
+ "Values": [{ "Value": "Birth Date Crosscheck" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_8_AlertName",
+ "Values": [{ "Value": "Birth Date Valid" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_9_AlertName",
+ "Values": [{ "Value": "Document Classification" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_10_AlertName",
+ "Values": [{ "Value": "Document Crosscheck Aggregation" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_11_AlertName",
+ "Values": [{ "Value": "Document Number Crosscheck" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_12_AlertName",
+ "Values": [{ "Value": "Document Tampering Detection" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_13_AlertName",
+ "Values": [{ "Value": "Document Tampering Detection" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_14_AlertName",
+ "Values": [{ "Value": "Document Tampering Detection" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_15_AlertName",
+ "Values": [{ "Value": "Expiration Date Crosscheck" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_16_AlertName",
+ "Values": [{ "Value": "Expiration Date Valid" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_17_AlertName",
+ "Values": [{ "Value": "Full Name Crosscheck" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_18_AlertName",
+ "Values": [{ "Value": "Issue Date Crosscheck" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_19_AlertName",
+ "Values": [{ "Value": "Issue Date Valid" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_20_AlertName",
+ "Values": [{ "Value": "Series Expired" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_21_AlertName",
+ "Values": [{ "Value": "Sex Crosscheck" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_22_AlertName",
+ "Values": [{ "Value": "Visible Pattern" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_23_AlertName",
+ "Values": [{ "Value": "Visible Pattern" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_24_AlertName",
+ "Values": [{ "Value": "Visible Pattern" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_3_Model",
+ "Values": [{ "Value": "Text Tampering Detection V1.3.1 (Beta)" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_12_Model",
+ "Values": [{ "Value": "Photo Tampering Detection V2.4" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_13_Model",
+ "Values": [{ "Value": "Text Tampering Detection V1.2.1" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_14_Model",
+ "Values": [{ "Value": "Physical Document Presence V2.5" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_1_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Checked if the document is expired."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_2_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified the presence of a pattern on the visible image."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_3_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Examines a document for evidence of tampering"
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_4_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Checked the contents of the two-dimensional barcode on the document."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_5_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Attention",
+ "Detail": "Verified that the two-dimensional barcode on the document was read successfully."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_6_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified the format of the barcode."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_7_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Compare the machine-readable birth date field to the human-readable birth date field."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_8_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified that the birth date is valid."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_9_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified that the type of document is supported and is able to be fully authenticated."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_10_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Compared the machine-readable fields to the human-readable fields."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_11_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Compare the machine-readable document number field to the human-readable document number field."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_12_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Examines a document for evidence of tampering"
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_13_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Examines a document for evidence of tampering"
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_14_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Examines a document for evidence of tampering"
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_15_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Compare the machine-readable expiration date field to the human-readable expiration date field."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_16_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified that the expiration date is valid."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_17_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Compare the machine-readable full name field to the human-readable full name field."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_18_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Compare the machine-readable issue date field to the human-readable issue date field."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_19_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified that the issue date is valid."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_20_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified whether the document type is still in circulation."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_21_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Compare the machine-readable sex field to the human-readable sex field."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_22_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified the presence of a pattern on the visible image."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_23_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified the presence of a pattern on the visible image."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_24_AuthenticationResult",
+ "Values": [
+ {
+ "Value": "Passed",
+ "Detail": "Verified the presence of a pattern on the visible image."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_1_Disposition",
+ "Values": [{ "Value": "The document has expired" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_2_Disposition",
+ "Values": [{ "Value": "A visible pattern was not found" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_3_Disposition",
+ "Values": [
+ {
+ "Value": "Evidence suggests that the document may have been tampered with."
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_4_Disposition",
+ "Values": [{ "Value": "The 2D barcode is formatted correctly" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_5_Disposition",
+ "Values": [{ "Value": "The 2D barcode was read successfully" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_6_Disposition",
+ "Values": [
+ {
+ "Value": "The barcode encoding is consistent with the expected encoding for the type"
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_7_Disposition",
+ "Values": [{ "Value": "The birth dates match" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_8_Disposition",
+ "Values": [{ "Value": "The birth date is valid" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_9_Disposition",
+ "Values": [{ "Value": "The document type is supported" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_10_Disposition",
+ "Values": [
+ {
+ "Value": "There are not a large number of differences between electronic and human-readable data sources"
+ }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_11_Disposition",
+ "Values": [{ "Value": "The document numbers match" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_12_Disposition",
+ "Values": [
+ { "Value": "No evidence of document tampering was detected." }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_13_Disposition",
+ "Values": [
+ { "Value": "No evidence of document tampering was detected." }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_14_Disposition",
+ "Values": [
+ { "Value": "No evidence of document tampering was detected." }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_15_Disposition",
+ "Values": [{ "Value": "The expiration dates match" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_16_Disposition",
+ "Values": [{ "Value": "The expiration date is valid" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_17_Disposition",
+ "Values": [{ "Value": "The full names match" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_18_Disposition",
+ "Values": [{ "Value": "The issue dates match" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_19_Disposition",
+ "Values": [{ "Value": "The issue date is valid" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_20_Disposition",
+ "Values": [{ "Value": "The series has not expired" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_21_Disposition",
+ "Values": [{ "Value": "The sexes match" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_22_Disposition",
+ "Values": [{ "Value": "A visible pattern was found" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_23_Disposition",
+ "Values": [{ "Value": "A visible pattern was found" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_24_Disposition",
+ "Values": [{ "Value": "A visible pattern was found" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_2_Regions",
+ "Values": [{ "Value": "Background" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_3_Regions",
+ "Values": [
+ { "Value": "Address" },
+ { "Value": "Birth Date" },
+ { "Value": "Document Number" },
+ { "Value": "Expiration Date" },
+ { "Value": "Full Name" },
+ { "Value": "Issue Date" }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_12_Regions",
+ "Values": [{ "Value": "Photo" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_13_Regions",
+ "Values": [
+ { "Value": "Address" },
+ { "Value": "Birth Date" },
+ { "Value": "Document Number" },
+ { "Value": "Expiration Date" },
+ { "Value": "Full Name" },
+ { "Value": "Issue Date" }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_22_Regions",
+ "Values": [{ "Value": "Expires Label" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_23_Regions",
+ "Values": [{ "Value": "USA" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_24_Regions",
+ "Values": [{ "Value": "Background Upper" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_2_Regions_Reference",
+ "Values": [{ "Value": "faacfb79-d0a1-4a8e-b868-20c604988e84" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_3_Regions_Reference",
+ "Values": [
+ { "Value": "c8be94b6-78ac-4e85-88cb-e17880371e4a" },
+ { "Value": "a8226d92-e62c-42a3-a206-ab7c3e3d9796" },
+ { "Value": "c2e18c41-e3de-46a8-abc7-3412015a6cef" },
+ { "Value": "80f8f290-daa0-47e2-828e-52106bb26f31" },
+ { "Value": "2c74b850-dd89-41bb-a21c-70ae0563ef77" },
+ { "Value": "63bf5053-f81f-493f-aff0-33c07d07a894" }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_12_Regions_Reference",
+ "Values": [{ "Value": "f29b1fe5-6482-4b39-8b4a-d91caf4ecb57" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_13_Regions_Reference",
+ "Values": [
+ { "Value": "c8be94b6-78ac-4e85-88cb-e17880371e4a" },
+ { "Value": "a8226d92-e62c-42a3-a206-ab7c3e3d9796" },
+ { "Value": "c2e18c41-e3de-46a8-abc7-3412015a6cef" },
+ { "Value": "80f8f290-daa0-47e2-828e-52106bb26f31" },
+ { "Value": "2c74b850-dd89-41bb-a21c-70ae0563ef77" },
+ { "Value": "63bf5053-f81f-493f-aff0-33c07d07a894" }
+ ]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_22_Regions_Reference",
+ "Values": [{ "Value": "d55f2c66-f84f-4213-a660-4ff9e5d0fde5" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_23_Regions_Reference",
+ "Values": [{ "Value": "bbf6ba02-ee3f-4b5c-a5d0-2fdb39ac79f7" }]
+ },
+ {
+ "Group": "AUTHENTICATION_RESULT",
+ "Name": "Alert_24_Regions_Reference",
+ "Values": [{ "Value": "20203cb8-f8a4-4a5b-999f-3700f73fe4fe" }]
+ },
+ {
+ "Group": "PORTRAIT_MATCH_RESULT",
+ "Name": "FaceMatchResult",
+ "Values": [{"Value": "Pass"}]
+ },
+ {
+ "Group": "PORTRAIT_MATCH_RESULT",
+ "Name": "FaceMatchScore",
+ "Values": [{"Value": "96"}]
+ },
+ {
+ "Group": "PORTRAIT_MATCH_RESULT",
+ "Name": "FaceStatusCode",
+ "Values": [{"Value": "1"}]
+ },
+ {
+ "Group": "PORTRAIT_MATCH_RESULT",
+ "Name": "FaceErrorMessage",
+ "Values": [{"Value": "Successful. Liveness: Live"}]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_FullName",
+ "Values": [{ "Value": "DAVID LICENSE SAMPLE" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_Surname",
+ "Values": [{ "Value": "SAMPLE" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_GivenName",
+ "Values": [{ "Value": "DAVID LICENSE" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_FirstName",
+ "Values": [{ "Value": "DAVID" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_MiddleName",
+ "Values": [{ "Value": "LICENSE" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_DOB_Year",
+ "Values": [{ "Value": "1986" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_DOB_Month",
+ "Values": [{ "Value": "7" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_DOB_Day",
+ "Values": [{ "Value": "1" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_DocumentClassName",
+ "Values": [{ "Value": "Drivers License" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_DocumentNumber",
+ "Values": [{ "Value": "M555555555555" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_ExpirationDate_Year",
+ "Values": [{ "Value": "2099" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_ExpirationDate_Month",
+ "Values": [{ "Value": "10" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_xpirationDate_Day",
+ "Values": [{ "Value": "15" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_IssuingStateCode",
+ "Values": [{ "Value": "MD" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_IssuingStateName",
+ "Values": [{ "Value": "Maryland" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_CountryCode",
+ "Values": [{ "Value": "USA" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_Address",
+ "Values": [
+ {
+ "Value": "123 ABC AVExE2x80xA8ANYTOWN, MD 12345"
+ }
+ ]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_AddressLine1",
+ "Values": [{ "Value": "123 ABC AVE" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_AddressLine2",
+ "Values": [{ "Value": "APT 3E" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_City",
+ "Values": [{ "Value": "ANYTOWN" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_State",
+ "Values": [{ "Value": "MD" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_PostalCode",
+ "Values": [{ "Value": "12345" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_Height",
+ "Values": [{ "Value": "5' 9\"" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_IssueDate_Year",
+ "Values": [{ "Value": "2016" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_IssueDate_Month",
+ "Values": [{ "Value": "10" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_IssueDate_Day",
+ "Values": [{ "Value": "15" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_LicenseClass",
+ "Values": [{ "Value": "C" }]
+ },
+ {
+ "Group": "IDAUTH_FIELD_DATA",
+ "Name": "Fields_LicenseRestrictions",
+ "Values": [{ "Value": "B" }]
+ },
+ {
+ "Group": "DOCUMENT_REGION",
+ "Name": "DocumentRegion_Id",
+ "Values": [
+ { "Value": "ce2cf0e2-5373-4ec2-84e8-7fe44a01642b" },
+ { "Value": "0b4f4f2b-cbd6-43e9-ac67-55bf2bdd9df5" },
+ { "Value": "c8be94b6-78ac-4e85-88cb-e17880371e4a" },
+ { "Value": "a0fdf00c-071c-4d8e-81af-8af0fc7688b8" },
+ { "Value": "faacfb79-d0a1-4a8e-b868-20c604988e84" },
+ { "Value": "3362ad4b-a36b-487e-826c-c748c7b04e8d" },
+ { "Value": "20203cb8-f8a4-4a5b-999f-3700f73fe4fe" },
+ { "Value": "a8226d92-e62c-42a3-a206-ab7c3e3d9796" },
+ { "Value": "a3e3a625-8b0e-4deb-a91e-86afc55d036b" },
+ { "Value": "cda4092d-9208-4871-bbc1-1b732c299d26" },
+ { "Value": "42770baf-3a4a-4477-9e5f-4400237273fe" },
+ { "Value": "c2e18c41-e3de-46a8-abc7-3412015a6cef" },
+ { "Value": "b36594f2-d19c-48aa-98c2-2b4f8429744f" },
+ { "Value": "80f8f290-daa0-47e2-828e-52106bb26f31" },
+ { "Value": "d55f2c66-f84f-4213-a660-4ff9e5d0fde5" },
+ { "Value": "26ca0c85-01ab-4311-bfd5-d27d2ee975eb" },
+ { "Value": "0af79f76-1542-4391-ad17-a5d6169be57f" },
+ { "Value": "2c74b850-dd89-41bb-a21c-70ae0563ef77" },
+ { "Value": "64e8ee97-2b24-452d-a5af-1627951aa737" },
+ { "Value": "63bf5053-f81f-493f-aff0-33c07d07a894" },
+ { "Value": "f29b1fe5-6482-4b39-8b4a-d91caf4ecb57" },
+ { "Value": "35442fb1-c3bb-4f2f-ad9e-537a8f0e7e0f" },
+ { "Value": "29964031-e072-4204-a9ae-b2b7122bfdc1" },
+ { "Value": "a3bbdf8b-62f5-438f-bf72-091bb2f6f0ff" },
+ { "Value": "42d0c49e-fc7a-45da-8f6d-8896c4b5267f" },
+ { "Value": "5430efcb-523b-4c62-a190-e9aa7eea4ebd" },
+ { "Value": "bbf6ba02-ee3f-4b5c-a5d0-2fdb39ac79f7" },
+ { "Value": "0686341c-0b3f-4544-840e-58822120ef06" }
+ ]
+ },
+ {
+ "Group": "DOCUMENT_REGION",
+ "Name": "DocumentRegion_Key",
+ "Values": [
+ { "Value": "1D Barcode" },
+ { "Value": "2D Barcode" },
+ { "Value": "Address" },
+ { "Value": "Alaska Validator" },
+ { "Value": "Background" },
+ { "Value": "Background Lower" },
+ { "Value": "Background Upper" },
+ { "Value": "Birth Date" },
+ { "Value": "Birth Date" },
+ { "Value": "DOB Label" },
+ { "Value": "DOB Label Text" },
+ { "Value": "Document Number" },
+ { "Value": "Document Type" },
+ { "Value": "Expiration Date" },
+ { "Value": "Expires Label" },
+ { "Value": "Expires Label Position" },
+ { "Value": "Eye Color" },
+ { "Value": "Full Name" },
+ { "Value": "Height" },
+ { "Value": "Issue Date" },
+ { "Value": "Photo" },
+ { "Value": "Photo Printing" },
+ { "Value": "Secondary Photo" },
+ { "Value": "Sex" },
+ { "Value": "Sex Height Labels" },
+ { "Value": "Signature" },
+ { "Value": "USA" },
+ { "Value": "Weight" }
+ ]
+ },
+ {
+ "Group": "DOCUMENT_REGION",
+ "Name": "DocumentRegion_ImageReference",
+ "Values": [
+ { "Value": "637aa4c6-eeb3-453f-899e-a56effcf3747" },
+ { "Value": "637aa4c6-eeb3-453f-899e-a56effcf3747" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "637aa4c6-eeb3-453f-899e-a56effcf3747" },
+ { "Value": "637aa4c6-eeb3-453f-899e-a56effcf3747" },
+ { "Value": "637aa4c6-eeb3-453f-899e-a56effcf3747" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" },
+ { "Value": "8a19313b-5dc6-4113-85f5-42c9829d903e" }
+ ]
+ }
+ ]
+ },
+ {
+ "ProductType": "TrueID_Decision",
+ "ExecutedStepName": "Decision",
+ "ProductConfigurationName": "TRUEID_PASS",
+ "ProductStatus": "pass",
+ "ProductReason": {
+ "Code": "trueid_pass",
+ "Description": "TRUEID PASS"
+ }
+ }
+ ]
+}
diff --git a/spec/forms/idv/api_image_upload_form_spec.rb b/spec/forms/idv/api_image_upload_form_spec.rb
index 98f4099164e..3316727a368 100644
--- a/spec/forms/idv/api_image_upload_form_spec.rb
+++ b/spec/forms/idv/api_image_upload_form_spec.rb
@@ -159,6 +159,8 @@
front_image_fingerprint: an_instance_of(String),
back_image_fingerprint: an_instance_of(String),
doc_type_supported: boolean,
+ doc_auth_success: boolean,
+ selfie_success: anything,
)
end
@@ -482,6 +484,8 @@
allow(client_response).to receive(:success?).and_return(false)
allow(client_response).to receive(:network_error?).and_return(false)
allow(client_response).to receive(:errors).and_return(errors)
+ allow(client_response).to receive(:doc_auth_success?).and_return(false)
+ allow(client_response).to receive(:selfie_success).and_return(nil)
form.send(:validate_form)
capture_result = form.send(:store_failed_images, client_response, doc_pii_response)
expect(capture_result[:front]).not_to be_empty
@@ -494,6 +498,8 @@
allow(client_response).to receive(:success?).and_return(false)
allow(client_response).to receive(:network_error?).and_return(false)
allow(client_response).to receive(:errors).and_return(errors)
+ allow(client_response).to receive(:doc_auth_success?).and_return(false)
+ allow(client_response).to receive(:selfie_success).and_return(nil)
form.send(:validate_form)
capture_result = form.send(:store_failed_images, client_response, doc_pii_response)
expect(capture_result[:front]).not_to be_empty
@@ -506,6 +512,8 @@
allow(client_response).to receive(:success?).and_return(false)
allow(client_response).to receive(:network_error?).and_return(false)
allow(client_response).to receive(:errors).and_return(errors)
+ allow(client_response).to receive(:doc_auth_success?).and_return(false)
+ allow(client_response).to receive(:selfie_success).and_return(nil)
form.send(:validate_form)
capture_result = form.send(:store_failed_images, client_response, doc_pii_response)
expect(capture_result[:front]).not_to be_empty
@@ -533,6 +541,8 @@
allow(client_response).to receive(:success?).and_return(false)
allow(client_response).to receive(:network_error?).and_return(true)
allow(client_response).to receive(:errors).and_return(errors)
+ allow(client_response).to receive(:doc_auth_success?).and_return(false)
+ allow(client_response).to receive(:selfie_success).and_return(nil)
allow(doc_pii_response).to receive(:success?).and_return(false)
form.send(:validate_form)
capture_result = form.send(:store_failed_images, client_response, doc_pii_response)
diff --git a/spec/javascript/packages/document-capture/components/acuant-selfie-camera-spec.jsx b/spec/javascript/packages/document-capture/components/acuant-selfie-camera-spec.jsx
index d392b21026c..b4bd0858b40 100644
--- a/spec/javascript/packages/document-capture/components/acuant-selfie-camera-spec.jsx
+++ b/spec/javascript/packages/document-capture/components/acuant-selfie-camera-spec.jsx
@@ -1,6 +1,7 @@
import { AcuantContextProvider, DeviceContext } from '@18f/identity-document-capture';
import AcuantSelfieCamera from '@18f/identity-document-capture/components/acuant-selfie-camera';
import AcuantSelfieCaptureCanvas from '@18f/identity-document-capture/components/acuant-selfie-capture-canvas';
+import { t } from '@18f/identity-i18n';
import { render, useAcuant } from '../../../support/document-capture';
describe('document-capture/components/acuant-selfie-camera', () => {
@@ -40,12 +41,10 @@ describe('document-capture/components/acuant-selfie-camera', () => {
expect(callbackNames).to.equal(expectedCallbackNames);
expect(window.AcuantPassiveLiveness.start.getCall(0).args[1]).to.deep.equal({
- FACE_NOT_FOUND: 'FACE NOT FOUND',
- TOO_MANY_FACES: 'TOO MANY FACES',
- FACE_ANGLE_TOO_LARGE: 'FACE ANGLE TOO LARGE',
- PROBABILITY_TOO_SMALL: 'PROBABILITY TOO SMALL',
- FACE_TOO_SMALL: 'FACE TOO SMALL',
- FACE_CLOSE_TO_BORDER: 'TOO CLOSE TO THE FRAME',
+ FACE_NOT_FOUND: t('doc_auth.info.selfie_capture_status.face_not_found'),
+ TOO_MANY_FACES: t('doc_auth.info.selfie_capture_status.too_many_faces'),
+ FACE_TOO_SMALL: t('doc_auth.info.selfie_capture_status.face_too_small'),
+ FACE_CLOSE_TO_BORDER: t('doc_auth.info.selfie_capture_status.face_close_to_border'),
});
});
diff --git a/spec/javascript/packages/document-capture/components/document-capture-not-ready-spec.tsx b/spec/javascript/packages/document-capture/components/document-capture-not-ready-spec.tsx
deleted file mode 100644
index d5590131249..00000000000
--- a/spec/javascript/packages/document-capture/components/document-capture-not-ready-spec.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-import sinon from 'sinon';
-
-import { FlowContext } from '@18f/identity-verify-flow';
-import { I18nContext } from '@18f/identity-react-i18n';
-import { I18n } from '@18f/identity-i18n';
-import userEvent from '@testing-library/user-event';
-import type { Navigate } from '@18f/identity-url';
-import {
- AnalyticsContextProvider,
- ServiceProviderContextProvider,
-} from '@18f/identity-document-capture/context';
-import DocumentCaptureNotReady from '@18f/identity-document-capture/components/document-capture-not-ready';
-import { expect } from 'chai';
-import { render } from '../../../support/document-capture';
-
-describe('DocumentCaptureNotReady', () => {
- beforeEach(() => {
- const config = document.createElement('script');
- config.id = 'test-config';
- config.type = 'application/json';
- config.setAttribute('data-config', '');
- config.textContent = JSON.stringify({ appName: 'Login.gov' });
- document.body.append(config);
- });
- const trackEvent = sinon.spy();
- const navigateSpy: Navigate = sinon.spy();
- context('with service provider', () => {
- const spName = 'testSP';
- it('renders, track event and redirect', async () => {
- const { getByRole } = render(
-
- '',
- }}
- >
-
-
-
-
-
-
- ,
- );
- // header
- expect(getByRole('heading', { name: 'header text', level: 2 })).to.be.ok();
-
- // content and exit link
- const exitLink = getByRole('button', { name: 'Exit Login.gov and return to testSP' });
- expect(exitLink).to.be.ok();
- await userEvent.click(exitLink);
- expect(navigateSpy).to.be.called.calledWithMatch(
- /failure-to-proof\?step=document_capture&location=not_ready/,
- );
- expect(trackEvent).to.be.calledWithMatch(/IdV: docauth not ready link clicked/);
- });
- });
-
- context('without service provider', () => {
- it('renders, track event and redirect', async () => {
- const { getByRole } = render(
-
- '',
- }}
- >
-
-
-
-
-
-
- ,
- );
- // header
- expect(getByRole('heading', { name: 'header text', level: 2 })).to.be.ok();
-
- // content and exit link
- const exitLink = getByRole('button', { name: 'Cancel and return to your profile' });
- expect(exitLink).to.be.ok();
- await userEvent.click(exitLink);
- expect(navigateSpy).to.be.called.calledWithMatch(
- /account\?step=document_capture&location=not_ready/,
- );
- expect(trackEvent).to.be.calledWithMatch(/IdV: docauth not ready link clicked/);
- });
- });
-});
diff --git a/spec/javascript/packages/document-capture/components/documents-step-spec.jsx b/spec/javascript/packages/document-capture/components/documents-step-spec.jsx
index 5675099308c..5eb17473141 100644
--- a/spec/javascript/packages/document-capture/components/documents-step-spec.jsx
+++ b/spec/javascript/packages/document-capture/components/documents-step-spec.jsx
@@ -8,7 +8,6 @@ import {
UploadContextProvider,
FailedCaptureAttemptsContextProvider,
FeatureFlagContext,
- InPersonContext,
} from '@18f/identity-document-capture';
import DocumentsStep from '@18f/identity-document-capture/components/documents-step';
import { composeComponents } from '@18f/identity-compose-components';
@@ -90,68 +89,6 @@ describe('document-capture/components/documents-step', () => {
expect(queryByText(notExpectedText)).to.not.exist();
});
- it('renders optional question part and not ready section', () => {
- const App = composeComponents(
- [
- FeatureFlagContext.Provider,
- {
- value: {
- notReadySectionEnabled: true,
- exitQuestionSectionEnabled: true,
- },
- },
- ],
- [
- InPersonContext.Provider,
- {
- value: {
- inPersonURL: '/verify/doc_capture',
- },
- },
- ],
- [DocumentsStep],
- );
- const { getByRole, getByText } = render( );
- expect(getByRole('heading', { name: 'doc_auth.not_ready.header', level: 2 })).to.be.ok();
- expect(getByRole('heading', { name: 'doc_auth.exit_survey.header', level: 2 })).to.be.ok();
- expect(getByText('doc_auth.exit_survey.optional.button')).to.be.ok();
- });
-
- context('not ready section', () => {
- it('is rendered when enabled', () => {
- const App = composeComponents(
- [
- FeatureFlagContext.Provider,
- {
- value: {
- notReadySectionEnabled: true,
- },
- },
- ],
- [DocumentsStep],
- );
- const { getByRole } = render( );
- expect(getByRole('heading', { name: 'doc_auth.not_ready.header', level: 2 })).to.be.ok();
- const button = getByRole('button', { name: 'doc_auth.not_ready.button_nosp' });
- expect(button).to.be.ok();
- });
- it('is not rendered when disabled', () => {
- const App = composeComponents(
- [
- FeatureFlagContext.Provider,
- {
- value: {
- notReadySectionEnabled: false,
- },
- },
- ],
- [DocumentsStep],
- );
- const { queryByRole } = render( );
- expect(queryByRole('heading', { name: 'doc_auth.not_ready.header', level: 2 })).to.be.null();
- });
- });
-
context('selfie capture', () => {
it('renders with front, back, and selfie inputs when featureflag is on', () => {
const App = composeComponents(
diff --git a/spec/javascript/packages/document-capture/context/feature-flag-spec.jsx b/spec/javascript/packages/document-capture/context/feature-flag-spec.jsx
index c1f43c67e50..3516e8b6f0b 100644
--- a/spec/javascript/packages/document-capture/context/feature-flag-spec.jsx
+++ b/spec/javascript/packages/document-capture/context/feature-flag-spec.jsx
@@ -6,12 +6,7 @@ describe('document-capture/context/feature-flag', () => {
it('has expected default properties', () => {
const { result } = renderHook(() => useContext(FeatureFlagContext));
- expect(result.current).to.have.keys([
- 'notReadySectionEnabled',
- 'exitQuestionSectionEnabled',
- 'selfieCaptureEnabled',
- ]);
- expect(result.current.notReadySectionEnabled).to.be.a('boolean');
+ expect(result.current).to.have.keys(['exitQuestionSectionEnabled', 'selfieCaptureEnabled']);
expect(result.current.exitQuestionSectionEnabled).to.be.a('boolean');
expect(result.current.selfieCaptureEnabled).to.be.a('boolean');
});
diff --git a/spec/jobs/reports/irs_weekly_summary_report_spec.rb b/spec/jobs/reports/irs_weekly_summary_report_spec.rb
deleted file mode 100644
index ae3e7c1d47f..00000000000
--- a/spec/jobs/reports/irs_weekly_summary_report_spec.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Reports::IrsWeeklySummaryReport do
- subject(:report) { Reports::IrsWeeklySummaryReport.new }
- let(:report_name) { 'irs-weekly-summary-report' }
- let(:email) { 'foo@bar.com' }
-
- before do
- create_list(:user, 10, { created_at: Date.yesterday })
- end
-
- describe '#perform' do
- it 'sends out a report to the email listed with system demand' do
- allow(IdentityConfig.store).to receive(:system_demand_report_email).and_return(email)
- allow(ReportMailer).to receive(:system_demand_report).and_call_original
-
- report = "Data Requested,Total Count\nSystem Demand,10\n"
- expect(ReportMailer).to receive(:system_demand_report).with(
- email: email, data: report, name: report_name,
- )
-
- subject.perform(Time.zone.now)
- end
-
- it 'uploads a file to S3 based on the report date' do
- csv_data = CSV.parse(subject.perform(Time.zone.now), headers: true)
- expect(csv_data[0]['Total Count']).to eq('10')
- end
- end
-end
diff --git a/spec/models/document_capture_session_spec.rb b/spec/models/document_capture_session_spec.rb
index f140962c30b..f2de75d0462 100644
--- a/spec/models/document_capture_session_spec.rb
+++ b/spec/models/document_capture_session_spec.rb
@@ -89,14 +89,15 @@
end
end
- describe('#store_failed_auth_image_fingerprint') do
+ describe('#store_failed_auth_data') do
it 'stores image finger print' do
record = DocumentCaptureSession.new(result_id: SecureRandom.uuid)
-
- record.store_failed_auth_image_fingerprint(
- 'fingerprint1', nil
+ record.store_failed_auth_data(
+ front_image_fingerprint: 'fingerprint1',
+ back_image_fingerprint: nil,
+ doc_auth_success: false,
+ selfie_success: nil,
)
-
result_id = record.result_id
key = EncryptedRedisStructStorage.key(result_id, type: DocumentCaptureSessionResult)
data = REDIS_POOL.with { |client| client.get(key) }
@@ -105,28 +106,40 @@
expect(result.failed_front_image?('fingerprint1')).to eq(true)
expect(result.failed_front_image?(nil)).to eq(false)
expect(result.failed_back_image?(nil)).to eq(false)
+ expect(result.doc_auth_success).to eq(false)
+ expect(result.selfie_success).to be_nil
end
it 'saves failed image finterprints' do
record = DocumentCaptureSession.new(result_id: SecureRandom.uuid)
- record.store_failed_auth_image_fingerprint(
- 'fingerprint1', nil
+ record.store_failed_auth_data(
+ front_image_fingerprint: 'fingerprint1',
+ back_image_fingerprint: nil,
+ doc_auth_success: false,
+ selfie_success: nil,
)
old_result = record.load_result
- record.store_failed_auth_image_fingerprint(
- 'fingerprint2', 'fingerprint3'
+ record.store_failed_auth_data(
+ front_image_fingerprint: 'fingerprint2',
+ back_image_fingerprint: 'fingerprint3',
+ doc_auth_success: false,
+ selfie_success: nil,
)
new_result = record.load_result
expect(old_result.failed_front_image?('fingerprint1')).to eq(true)
expect(old_result.failed_front_image?('fingerprint2')).to eq(false)
expect(old_result.failed_back_image?('fingerprint3')).to eq(false)
+ expect(old_result.doc_auth_success).to eq(false)
+ expect(old_result.selfie_success).to be_nil
expect(new_result.failed_front_image?('fingerprint1')).to eq(true)
expect(new_result.failed_front_image?('fingerprint2')).to eq(true)
expect(new_result.failed_back_image?('fingerprint3')).to eq(true)
+ expect(new_result.doc_auth_success).to eq(false)
+ expect(new_result.selfie_success).to be_nil
end
end
end
diff --git a/spec/services/doc_auth/acuant/responses/get_results_response_spec.rb b/spec/services/doc_auth/acuant/responses/get_results_response_spec.rb
index 178b6027ffe..29e071d13a3 100644
--- a/spec/services/doc_auth/acuant/responses/get_results_response_spec.rb
+++ b/spec/services/doc_auth/acuant/responses/get_results_response_spec.rb
@@ -62,6 +62,8 @@
},
address_line2_present: true,
doc_type_supported: true,
+ doc_auth_success: true,
+ selfie_success: nil,
}
processed_alerts = response_hash[:processed_alerts]
@@ -418,6 +420,8 @@
},
address_line2_present: true,
doc_type_supported: false,
+ doc_auth_success: true,
+ selfie_success: nil,
}
processed_alerts = response_hash[:processed_alerts]
@@ -499,6 +503,8 @@
},
address_line2_present: true,
doc_type_supported: false,
+ doc_auth_success: true,
+ selfie_success: nil,
}
expect(response_hash).to match(expected_hash)
diff --git a/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb b/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb
index 3c39172fcb8..838d010d97b 100644
--- a/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb
+++ b/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb
@@ -2,9 +2,15 @@
RSpec.describe DocAuth::LexisNexis::Responses::TrueIdResponse do
let(:success_response_body) { LexisNexisFixtures.true_id_response_success_3 }
+ let(:success_with_liveness_response_body) do
+ LexisNexisFixtures.true_id_response_success_with_liveness
+ end
let(:success_response) do
instance_double(Faraday::Response, status: 200, body: success_response_body)
end
+ let(:success_with_liveness_response) do
+ instance_double(Faraday::Response, status: 200, body: success_with_liveness_response_body)
+ end
let(:failure_body_no_liveness) { LexisNexisFixtures.true_id_response_failure_no_liveness }
let(:failure_body_with_liveness) { LexisNexisFixtures.true_id_response_failure_with_liveness }
let(:failure_body_with_all_failures) do
@@ -140,6 +146,8 @@
Front: a_hash_including(:ClassName, :CountryCode, :IssuerType),
Back: a_hash_including(:ClassName, :CountryCode, :IssuerType),
},
+ doc_auth_success: true,
+ selfie_success: nil,
)
passed_alerts = response_hash.dig(:processed_alerts, :passed)
passed_alerts.each do |alert|
@@ -320,8 +328,11 @@ def get_decision_product(resp)
end
it 'returns Failed for liveness failure' do
- output = described_class.new(failure_response_with_liveness, config).to_h
+ response = described_class.new(failure_response_with_liveness, config)
+ output = response.to_h
expect(output[:success]).to eq(false)
+ expect(response.doc_auth_success?).to eq(false)
+ expect(response.selfie_success).to eq(false)
end
it 'produces expected hash output' do
@@ -376,6 +387,8 @@ def get_decision_product(resp)
Front: a_hash_including(:ClassName, :CountryCode, :IssuerType),
Back: a_hash_including(:ClassName, :CountryCode, :IssuerType),
},
+ doc_auth_success: false,
+ selfie_success: false,
)
end
it 'produces appropriate errors with document tampering' do
@@ -635,4 +648,48 @@ def get_decision_product(resp)
end
end
end
+ describe '#doc_auth_success?' do
+ context 'when document validation is successful' do
+ let(:response) { described_class.new(success_response, config) }
+ it 'returns true' do
+ expect(response.doc_auth_success?).to eq(true)
+ end
+ end
+ context 'when document validation failed' do
+ let(:response) { described_class.new(failure_response_tampering, config) }
+ it 'returns false' do
+ expect(response.doc_auth_success?).to eq(false)
+ end
+ end
+ end
+
+ describe '#selfie_success' do
+ context 'when selfie check is disabled' do
+ let(:response) { described_class.new(success_response, config, false) }
+ it 'returns nil' do
+ expect(response.selfie_success).to eq(nil)
+ end
+ end
+
+ context 'when selfie check is enabled' do
+ context 'whe missing selfie result in response' do
+ let(:response) { described_class.new(success_response, config, true) }
+ it 'returns nil when missing selfie in response' do
+ expect(response.selfie_success).to eq(nil)
+ end
+ end
+ context 'when selfie passed' do
+ let(:response) { described_class.new(success_with_liveness_response, config, true) }
+ it 'returns true' do
+ expect(response.selfie_success).to eq(true)
+ end
+ end
+ context 'when selfie failed' do
+ let(:response) { described_class.new(failure_response_with_liveness, config, true) }
+ it 'returns false' do
+ expect(response.selfie_success).to eq(false)
+ end
+ end
+ end
+ end
end
diff --git a/spec/services/doc_auth/mock/result_response_spec.rb b/spec/services/doc_auth/mock/result_response_spec.rb
index 586a87ef64d..e3296ca83c0 100644
--- a/spec/services/doc_auth/mock/result_response_spec.rb
+++ b/spec/services/doc_auth/mock/result_response_spec.rb
@@ -304,6 +304,8 @@
billed: true,
classification_info: {},
)
+ expect(response.doc_auth_success?).to eq(true)
+ expect(response.selfie_success).to be_nil
end
end
@@ -669,6 +671,8 @@
expect(response.selfie_check_performed?).to eq(true)
expect(response.success?).to eq(true)
expect(response.extra[:portrait_match_results]).to eq(selfie_results)
+ expect(response.doc_auth_success?).to eq(true)
+ expect(response.selfie_success).to eq(true)
end
end
@@ -693,6 +697,8 @@
expect(response.selfie_check_performed?).to eq(true)
expect(response.success?).to eq(false)
expect(response.extra[:portrait_match_results]).to eq(selfie_results)
+ expect(response.doc_auth_success?).to eq(true)
+ expect(response.selfie_success).to eq(false)
end
end
end
@@ -704,6 +710,8 @@
it 'returns the expected values' do
expect(response.selfie_check_performed?).to eq(false)
expect(response.extra).not_to have_key(:portrait_match_results)
+ expect(response.doc_auth_success?).to eq(true)
+ expect(response.selfie_success).to be_nil
end
end
end
diff --git a/spec/services/reporting/account_reuse_report_spec.rb b/spec/services/reporting/account_reuse_report_spec.rb
index 1aec438fdb0..7c399430e44 100644
--- a/spec/services/reporting/account_reuse_report_spec.rb
+++ b/spec/services/reporting/account_reuse_report_spec.rb
@@ -21,14 +21,6 @@
let(:sp_c) { 'c' }
let(:sp_d) { 'd' }
- def create_identity(user_id:, created_at:, provider:, verified_at:)
- ServiceProviderIdentity.create(
- user_id: user_id, service_provider: provider,
- created_at: created_at,
- last_ial2_authenticated_at: in_query, verified_at: verified_at
- )
- end
-
before do
create(
:service_provider,
@@ -136,10 +128,11 @@ def create_identity(user_id:, created_at:, provider:, verified_at:)
users_to_query.each do |user|
user[:sp].each_with_index do |sp, i|
- create_identity(
+ ServiceProviderIdentity.create(
user_id: user[:id],
+ service_provider: sp,
created_at: user[:created_timestamp],
- provider: sp,
+ last_ial2_authenticated_at: in_query,
verified_at: user[:sp_timestamp][i],
)
end
@@ -148,10 +141,20 @@ def create_identity(user_id:, created_at:, provider:, verified_at:)
# Create active profiles for total_proofed_identities
# These 13 profiles will yield 10 active profiles in the results
(1..10).each do |_|
- create(:profile, :active, activated_at: in_query)
+ create(
+ :profile,
+ :active,
+ activated_at: in_query,
+ user: create(:user, :fully_registered, registered_at: in_query),
+ )
end
(1..3).each do |_|
- create(:profile, :active, activated_at: out_of_query)
+ create(
+ :profile,
+ :active,
+ activated_at: out_of_query,
+ user: create(:user, :fully_registered, registered_at: in_query),
+ )
end
end
diff --git a/spec/support/lexis_nexis_fixtures.rb b/spec/support/lexis_nexis_fixtures.rb
index 6d3e80f9cfa..750c9a51baf 100644
--- a/spec/support/lexis_nexis_fixtures.rb
+++ b/spec/support/lexis_nexis_fixtures.rb
@@ -160,6 +160,10 @@ def true_id_response_success_3
read_fixture_file_at_path('true_id/true_id_response_success_3.json')
end
+ def true_id_response_success_with_liveness
+ read_fixture_file_at_path('true_id/true_id_response_success_with_liveness.json')
+ end
+
def true_id_response_failure_no_liveness
read_fixture_file_at_path('true_id/true_id_response_failure_no_liveness.json')
end