Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e5ea733
Add documentation for missing piv_cac_login arguments (#10246)
aduth Mar 14, 2024
e9ee7b3
LG-12307: don't allow upload when selfie capture is enabled (#10232)
Mar 14, 2024
6e3a7c0
LG-12595 Fix Selfie Screenreader Behavior by adding <FullScreen /> (#…
charleyf Mar 14, 2024
8f74bcd
Bugfix: Clear in_person_verification_pending_at on fraud deactivation…
n1zyy Mar 14, 2024
10b9f2b
LG-12414 Fraud Metrics LG-99 report bug fix (#10255)
olatifflexion Mar 15, 2024
c5ff10f
LG-12678 x509:presented attribute type (#10239)
Sgtpluck Mar 15, 2024
b7f2534
LG-12577 Allow active profile to sign in to non-biometric SP (#10211)
theabrad Mar 15, 2024
3326211
LG-12455: Improve readability of hint text in selfie SDK (#10253)
eileen-nava Mar 15, 2024
7f3a0a3
Improve Acuant Global Code Comment (#10247)
charleyf Mar 15, 2024
439fa72
LG-12672: handoff page update when selfie required (#10251)
dawei-nava Mar 15, 2024
66fbddc
LG-12597: Make revisions on MFA selection screen (#10249)
jmdembe Mar 18, 2024
480b00d
Updates nil return value to false (#10259)
Sgtpluck Mar 18, 2024
6ae88ce
LG-12606 Security Key Error flow content and workflow improvements (#…
kevinsmaster5 Mar 18, 2024
39a2919
Clean PIV session detail after deletion (#10238)
aduth Mar 18, 2024
cecba48
Update ImageUploadsController spec to use have_logged_event (#10258)
matthinz Mar 18, 2024
4e47f1e
Bump phonelib from 0.8.7 to 0.8.8 (#10261)
dependabot[bot] Mar 18, 2024
46d54a0
Add **extra where missing from track_event calls (#10262)
matthinz Mar 18, 2024
77148e9
LG-12067 TMX Integration Specs (#10231)
jack-ryan-nava-pbc Mar 19, 2024
65a4854
Fix Network Error in Dev by Adding missing selfie analytics event (#1…
charleyf Mar 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ GEM
pg (1.5.4)
pg_query (4.2.3)
google-protobuf (>= 3.22.3)
phonelib (0.8.7)
phonelib (0.8.8)
pkcs11 (0.3.4)
premailer (1.21.0)
addressable
Expand Down
1 change: 1 addition & 0 deletions app/controllers/frontend_log_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class FrontendLogController < ApplicationController
'IdV: personal key acknowledgment toggled' => :idv_personal_key_acknowledgment_toggled,
'IdV: prepare submitted' => :idv_in_person_prepare_submitted,
'IdV: prepare visited' => :idv_in_person_prepare_visited,
'IdV: selfie image clicked' => :idv_selfie_image_clicked,
'IdV: switch_back submitted' => :idv_in_person_switch_back_submitted,
'IdV: switch_back visited' => :idv_in_person_switch_back_visited,
'IdV: user clicked sp link on ready to verify page' => :idv_in_person_ready_to_verify_sp_link_clicked,
Expand Down
1 change: 1 addition & 0 deletions app/controllers/idv/document_capture_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def self.step_info
idv_session.flow_path == 'standard' && (
# mobile
idv_session.skip_hybrid_handoff ||
idv_session.skip_doc_auth ||
!idv_session.selfie_check_required || # desktop but selfie not required
idv_session.desktop_selfie_test_mode_enabled?
)
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/idv/hybrid_handoff_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ def show
@upload_disabled = idv_session.selfie_check_required &&
!idv_session.desktop_selfie_test_mode_enabled?

@selfie_required = idv_session.selfie_check_required

analytics.idv_doc_auth_hybrid_handoff_visited(**analytics_arguments)

Funnel::DocAuth::RegisterStep.new(current_user.id, sp_session[:issuer]).call(
Expand Down
14 changes: 13 additions & 1 deletion app/controllers/openid_connect/authorization_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class AuthorizationController < ApplicationController
def index
if @authorize_form.ial2_or_greater?
return redirect_to reactivate_account_url if user_needs_to_reactivate_account?
return redirect_to url_for_pending_profile_reason if user_has_pending_profile?
return redirect_to url_for_pending_profile_reason if user_has_usable_pending_profile?
return redirect_to idv_url if identity_needs_verification?
return redirect_to idv_url if selfie_needed?
end
Expand All @@ -47,6 +47,18 @@ def index

private

def pending_profile_policy
@pending_profile_policy ||= PendingProfilePolicy.new(
user: current_user,
resolved_authn_context_result: resolved_authn_context_result,
biometric_comparison_requested: biometric_comparison_requested?,
)
end

def user_has_usable_pending_profile?
pending_profile_policy.user_has_usable_pending_profile?
end

def block_biometric_requests_in_production
if biometric_comparison_requested? &&
!FeatureManagement.idv_allow_selfie_check?
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/users/piv_cac_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Users
class PivCacController < ApplicationController
include ReauthenticationRequiredConcern
include PivCacConcern

before_action :confirm_two_factor_authenticated
before_action :confirm_recently_authenticated_2fa
Expand Down Expand Up @@ -33,6 +34,7 @@ def destroy
create_user_event(:piv_cac_disabled)
revoke_remember_device(current_user)
deliver_push_notification
clear_piv_cac_information

flash[:success] = presenter.delete_success_alert_text
redirect_to account_path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,4 @@
.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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -698,12 +698,16 @@ function AcuantCapture(
onImageCaptureClose={onSelfieCaptureClosed}
onImageCaptureFeedback={onImageCaptureFeedback}
>
<AcuantSelfieCaptureCanvas
fullScreenRef={fullScreenRef}
fullScreenLabel={t('doc_auth.accessible_labels.document_capture_dialog')}
onRequestClose={() => setIsCapturingEnvironment(false)}
imageCaptureText={imageCaptureText}
/>
<FullScreen
ref={fullScreenRef}
label={t('doc_auth.accessible_labels.document_capture_dialog')}
hideCloseButton
>
<AcuantSelfieCaptureCanvas
imageCaptureText={imageCaptureText}
onSelfieCaptureClosed={onSelfieCaptureClosed}
/>
</FullScreen>
</AcuantSelfieCamera>
)}
<FileInput
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,37 @@
import { useContext } from 'react';
import { getAssetPath } from '@18f/identity-assets';
import { FullScreen } from '@18f/identity-components';
import { useI18n } from '@18f/identity-react-i18n';
import AcuantContext from '../context/acuant';

function FullScreenLoadingSpinner({ fullScreenRef, onRequestClose, fullScreenLabel }) {
function LoadingSpinner() {
return (
<FullScreen ref={fullScreenRef} label={fullScreenLabel} onRequestClose={onRequestClose}>
<img
src={getAssetPath('loading-badge.gif')}
alt=""
width="144"
height="144"
className="acuant-capture-canvas__spinner"
/>
</FullScreen>
<img
src={getAssetPath('loading-badge.gif')}
alt=""
width="144"
height="144"
className="acuant-capture-canvas__spinner"
/>
);
}

function AcuantSelfieCaptureCanvas({
fullScreenRef,
onRequestClose,
fullScreenLabel,
imageCaptureText,
}) {
function AcuantSelfieCaptureCanvas({ imageCaptureText, onSelfieCaptureClosed }) {
const { isReady } = useContext(AcuantContext);
const { t } = useI18n();
// 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 ? (
<div id={acuantCaptureContainerId}>
return (
<>
{!isReady && <LoadingSpinner />}
<div id={acuantCaptureContainerId} />
<p aria-live="assertive" className="document-capture-selfie-feedback">
{imageCaptureText}
</p>
</div>
) : (
<FullScreenLoadingSpinner
fullScreenRef={fullScreenRef}
onRequestClose={onRequestClose}
fullScreenLabel={fullScreenLabel}
/>
<button type="button" onClick={onSelfieCaptureClosed} className="usa-sr-only">
{t('doc_auth.buttons.close')}
</button>
</>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.document-capture-selfie-feedback {
color: color('white');
background: color('black');
left: 50%;
top: 10%;
position: fixed;
transform: translateX(-50%);
z-index: 11;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
RegisterFieldCallback,
} from '@18f/identity-form-steps';
import AcuantCapture from './acuant-capture';
import SelfieCaptureContext from '../context/selfie-capture';

interface DocumentSideAcuantCaptureProps {
side: 'front' | 'back' | 'selfie';
Expand Down Expand Up @@ -43,6 +44,9 @@ function DocumentSideAcuantCapture({
}: DocumentSideAcuantCaptureProps) {
const error = errors.find(({ field }) => field === side)?.error;
const { changeStepCanComplete } = useContext(FormStepsContext);
const { isSelfieCaptureEnabled, isSelfieDesktopTestMode } = useContext(SelfieCaptureContext);
const isUploadAllowed = isSelfieDesktopTestMode || !isSelfieCaptureEnabled;

return (
<AcuantCapture
ref={registerField(side, { isRequired: true })}
Expand Down Expand Up @@ -74,6 +78,7 @@ function DocumentSideAcuantCapture({
errorMessage={error ? error.message : undefined}
name={side}
className={className}
allowUpload={isUploadAllowed}
/>
);
}
Expand Down
5 changes: 2 additions & 3 deletions app/javascript/packages/document-capture/context/acuant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import SelfieCaptureContext from './selfie-capture';
/**
* Global declarations
*/
declare let AcuantJavascriptWebSdk: AcuantJavascriptWebSdkInterface; // As of 11.7.0, this is now a global object that is not on the window object.
declare let AcuantCamera: AcuantCameraInterface;

declare global {
Expand Down Expand Up @@ -182,11 +181,11 @@ const getActualAcuantJavascriptWebSdk = (): AcuantJavascriptWebSdkInterface => {
if (window.AcuantJavascriptWebSdk && typeof window.AcuantJavascriptWebSdk.start === 'function') {
return window.AcuantJavascriptWebSdk;
}
if (typeof AcuantJavascriptWebSdk === 'undefined') {
if (!window.AcuantJavascriptWebSdk) {
// eslint-disable-next-line no-console
console.error('AcuantJavascriptWebSdk is not defined in the global scope');
}
return AcuantJavascriptWebSdk;
return window.AcuantJavascriptWebSdk;
};

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createContext, useState } from 'react';
import { createContext, useContext, useState } from 'react';
import type { ReactNode } from 'react';
import useCounter from '../hooks/use-counter';
import SelfieCaptureContext from './selfie-capture';

interface CaptureAttemptMetadata {
isAssessedAsGlare: boolean;
Expand Down Expand Up @@ -125,6 +126,7 @@ function FailedCaptureAttemptsContextProvider({
useCounter();
const [failedSubmissionAttempts, incrementFailedSubmissionAttempts] = useCounter();
const [failedCameraPermissionAttempts, incrementFailedCameraPermissionAttempts] = useCounter();
const { isSelfieCaptureEnabled } = useContext(SelfieCaptureContext);

const [failedSubmissionImageFingerprints, setFailedSubmissionImageFingerprints] =
useState<UploadedImageFingerprints>(failedFingerprints);
Expand All @@ -143,10 +145,12 @@ function FailedCaptureAttemptsContextProvider({
incrementFailedCameraPermissionAttempts();
}

const forceNativeCamera =
const hasExhaustedAttempts =
failedCaptureAttempts >= maxCaptureAttemptsBeforeNativeCamera ||
failedSubmissionAttempts >= maxSubmissionAttemptsBeforeNativeCamera;

const forceNativeCamera = isSelfieCaptureEnabled ? false : hasExhaustedAttempts;

return (
<FailedCaptureAttemptsContext.Provider
value={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ interface SelfieCaptureProps {
* Specify whether to show the selfie capture on the doc capture screen.
*/
isSelfieCaptureEnabled: boolean;
/**
* Specify whether to allow uploads for selfie when in test mode.
*/
isSelfieDesktopTestMode: boolean;
}

const SelfieCaptureContext = createContext<SelfieCaptureProps>({
isSelfieCaptureEnabled: false,
isSelfieDesktopTestMode: false,
});

SelfieCaptureContext.displayName = 'SelfieCaptureContext';
Expand Down
1 change: 1 addition & 0 deletions app/javascript/packages/document-capture/styles.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@forward './components/acuant-capture';
@forward './components/acuant-capture-canvas';
@forward './components/acuant-selfie-capture-canvas';
@forward './components/file-input';
@forward './components/location-collection-item';
@forward './components/optional-questions';
Expand Down
19 changes: 11 additions & 8 deletions app/javascript/packs/document-capture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ interface AppRootData {
skipDocAuth: string;
howToVerifyURL: string;
uiExitQuestionSectionEnabled: string;
docAuthSelfieDesktopTestMode: string;
}

const appRoot = document.getElementById('document-capture-form')!;
Expand Down Expand Up @@ -106,6 +107,7 @@ const {
skipDocAuth,
howToVerifyUrl,
uiExitQuestionSectionEnabled = '',
docAuthSelfieDesktopTestMode,
} = appRoot.dataset as DOMStringMap & AppRootData;

let parsedUsStatesTerritories = [];
Expand Down Expand Up @@ -177,6 +179,15 @@ const App = composeComponents(
value: getServiceProvider(),
},
],
[
SelfieCaptureContext.Provider,
{
value: {
isSelfieCaptureEnabled: getSelfieCaptureEnabled(),
isSelfieDesktopTestMode: String(docAuthSelfieDesktopTestMode) === 'true',
},
},
],
[
FailedCaptureAttemptsContextProvider,
{
Expand All @@ -192,14 +203,6 @@ const App = composeComponents(
},
},
],
[
SelfieCaptureContext.Provider,
{
value: {
isSelfieCaptureEnabled: getSelfieCaptureEnabled(),
},
},
],
[
DocumentCapture,
{
Expand Down
1 change: 1 addition & 0 deletions app/models/profile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ def deactivate_for_fraud_review
active: false,
fraud_review_pending_at: Time.zone.now,
fraud_rejection_at: nil,
in_person_verification_pending_at: nil,
)
end

Expand Down
40 changes: 40 additions & 0 deletions app/policies/pending_profile_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
class PendingProfilePolicy
def initialize(user:, resolved_authn_context_result:, biometric_comparison_requested:)
@user = user
@resolved_authn_context_result = resolved_authn_context_result
@biometric_comparison_requested = biometric_comparison_requested
end

def user_has_usable_pending_profile?
if biometric_comparison_requested?
pending_biometric_profile?
else
pending_legacy_profile? || fraud_review_pending?
end
end

private

attr_reader :user, :resolved_authn_context_result, :biometric_comparison_requested

def active_biometric_profile?
user.active_profile&.idv_level == 'unsupervised_with_selfie'
end

def pending_biometric_profile?
user.pending_profile&.idv_level == 'unsupervised_with_selfie'
end

def biometric_comparison_requested?
return false if !FeatureManagement.idv_allow_selfie_check?
resolved_authn_context_result.biometric_comparison? || biometric_comparison_requested
end

def pending_legacy_profile?
user.pending_profile.present? && user.pending_profile&.idv_level != 'unsupervised_with_selfie'
end

def fraud_review_pending?
user.fraud_review_pending?
end
end
Loading