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
9cb0e01
Remove unused single country variant from phone input component (#11260)
aduth Sep 18, 2024
a242e8f
LG-14400 add recaptcha disclaimer text on sign-in (#11253)
kevinsmaster5 Sep 19, 2024
4a3b7a3
LG-14370 Add biographical info to proofing result (#11258)
jmhooper Sep 19, 2024
983e216
Move Aaguid call in webauthn Setup form (#11248)
mdiarra3 Sep 19, 2024
47c2325
Fix flaky `RECAPTCHA_SIGN_IN` A/B test (#11264)
jmhooper Sep 19, 2024
3b61ba4
changelog: Bug Fixes, In-person Proofing, Add validation for PO searc…
WilliamBirdsall Sep 19, 2024
06e1ee7
Fix flaky spec on the welcome controller (#11266)
jmhooper Sep 19, 2024
8981f27
Stop logging the year of birth in proofing results (#11267)
jmhooper Sep 19, 2024
0f3dce0
LG-14382: Update intl-tel-input to the latest version (#11257)
aduth Sep 19, 2024
48edf37
changelog: User-Facing Improvments, form step titles, added new selfi…
AShukla-GSA Sep 19, 2024
f32384a
LG-14393: Require threatmetrix_session_id for verify info step (#11254)
matthinz Sep 19, 2024
4e909b8
LG-14100 Include identity-verified status in account reset delete eve…
kevinsmaster5 Sep 20, 2024
07324d0
Update saml_idp version (#11270)
Sgtpluck Sep 20, 2024
c518bfa
Update Puma (#11271)
Sep 20, 2024
c803542
changelog: User-Facing Improvments, doc auth, split doc auth content …
AShukla-GSA Sep 20, 2024
c2103d2
Update google-protobuf (#11273)
Sep 23, 2024
4011c10
Revert "changelog: User-Facing Improvments, doc auth, split doc auth …
amirbey Sep 23, 2024
03c22a1
Add NewRelic method tracers to the AbTest bucket tooling (#11275)
jmhooper Sep 23, 2024
ef7c472
update webrick (#11277)
amirbey Sep 24, 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
3 changes: 3 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
"globalThis": true
},
"rules": {
// `no-unresolved` doesn't support package.json exports
// See: https://github.com/import-js/eslint-plugin-import/issues/1810
"import/no-unresolved": ["error", { "ignore": ["intl-tel-input"] }],
"no-restricted-syntax": [
"error",
{
Expand Down
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ gem 'rqrcode'
gem 'ruby-progressbar'
gem 'ruby-saml'
gem 'safe_target_blank', '>= 1.0.2'
gem 'saml_idp', github: '18F/saml_idp', tag: '0.22.0-18f'
gem 'saml_idp', github: '18F/saml_idp', tag: '0.23.0-18f'
gem 'scrypt'
gem 'simple_form', '>= 5.0.2'
gem 'stringex', require: false
Expand Down
16 changes: 9 additions & 7 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ GIT

GIT
remote: https://github.com/18F/saml_idp.git
revision: 9b9e118e3811df70e4586a4b6585dbc69f928625
tag: 0.22.0-18f
revision: 23b69117593e9b9217910af1dd627febd8d18cf4
tag: 0.23.0-18f
specs:
saml_idp (0.22.0.pre.18f)
saml_idp (0.23.0.pre.18f)
activesupport
builder
faraday
Expand Down Expand Up @@ -350,7 +350,9 @@ GEM
fugit (>= 1.1)
railties (>= 6.0.0)
thor (>= 0.14.1)
google-protobuf (3.24.4)
google-protobuf (4.28.2)
bigdecimal
rake (>= 13)
hashdiff (1.1.0)
heapy (0.2.0)
thor
Expand Down Expand Up @@ -464,7 +466,7 @@ GEM
ast (~> 2.4.1)
racc
pg (1.5.6)
pg_query (4.2.3)
pg_query (5.1.0)
google-protobuf (>= 3.22.3)
phonelib (0.9.1)
pkcs11 (0.3.4)
Expand Down Expand Up @@ -498,7 +500,7 @@ GEM
psych (5.1.2)
stringio
public_suffix (6.0.1)
puma (6.4.2)
puma (6.4.3)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.8.1)
Expand Down Expand Up @@ -724,7 +726,7 @@ GEM
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.8.1)
webrick (1.8.2)
websocket (1.2.10)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
Expand Down
14 changes: 4 additions & 10 deletions app/components/phone_input_component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,12 @@ lg-phone-input {
}
}

.iti__dial-code {
color: color('ink');
}

.iti:not(.iti--allow-dropdown) input {
padding-left: 36px;
padding-right: 6px;
.iti__search-input {
@extend %block-input-styles;
}

.iti:not(.iti--allow-dropdown) .iti__flag-container {
left: 0;
right: auto;
.iti__dial-code {
color: color('ink');
}
}

Expand Down
15 changes: 15 additions & 0 deletions app/controllers/concerns/idv/verify_info_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ module VerifyInfoConcern

STEP_NAME = 'verify_info'

class_methods do
def threatmetrix_session_id_present_or_not_required?(idv_session:)
return true unless FeatureManagement.proofing_device_profiling_decisioning_enabled?
idv_session.threatmetrix_session_id.present?
end
end

def shared_update
return if idv_session.verify_info_step_document_capture_session_uuid
analytics.idv_doc_auth_verify_submitted(**analytics_arguments)
Expand Down Expand Up @@ -36,6 +43,11 @@ def shared_update
return true
end

def log_event_for_missing_threatmetrix_session_id
return if self.class.threatmetrix_session_id_present_or_not_required?(idv_session:)
analytics.idv_verify_info_missing_threatmetrix_session_id if idv_session.ssn_step_complete?
end

private

def ipp_enrollment_in_progress?
Expand Down Expand Up @@ -173,6 +185,9 @@ def async_state_done(current_async_state)
[:proofing_results, :context, :stages, :threatmetrix, :response_body, :first_name],
[:same_address_as_id],
[:proofing_results, :context, :stages, :state_id, :state_id_jurisdiction],
[:proofing_results, :biographical_info, :identity_doc_address_state],
[:proofing_results, :biographical_info, :state_id_jurisdiction],
[:proofing_results, :biographical_info, :same_address_as_id],
],
},
)
Expand Down
15 changes: 14 additions & 1 deletion app/controllers/idv/in_person/usps_locations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

module Idv
module InPerson
class UspsLocationsError < StandardError
def initialize
super('Unsupported characters in address field.')
end
end

class UspsLocationsController < ApplicationController
include Idv::AvailabilityConcern
include Idv::HybridMobile::HybridMobileConcern
Expand All @@ -18,6 +24,7 @@ class UspsLocationsController < ApplicationController
rescue_from ActionController::InvalidAuthenticityToken,
Faraday::Error,
StandardError,
UspsLocationsError,
Faraday::BadRequestError,
with: :handle_error

Expand All @@ -28,6 +35,11 @@ def index
city: search_params['city'], state: search_params['state'],
zip_code: search_params['zip_code']
)

unless candidate.has_valid_address?
raise UspsLocationsError.new
end

locations = proofer.request_facilities(candidate, authn_context_enhanced_ipp?)
if locations.length > 0
analytics.idv_in_person_locations_searched(
Expand Down Expand Up @@ -96,7 +108,8 @@ def localized_locations(locations)
def handle_error(err)
remapped_error = case err
when ActionController::InvalidAuthenticityToken,
Faraday::Error
Faraday::Error,
UspsLocationsError
:unprocessable_entity
else
:internal_server_error
Expand Down
5 changes: 4 additions & 1 deletion app/controllers/idv/verify_info_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class VerifyInfoController < ApplicationController
include Steps::ThreatMetrixStepHelper

before_action :confirm_not_rate_limited_after_doc_auth, except: [:show]
before_action :log_event_for_missing_threatmetrix_session_id
before_action :confirm_step_allowed

def show
Expand Down Expand Up @@ -45,7 +46,9 @@ def self.step_info
controller: self,
next_steps: [:phone, :request_letter],
preconditions: ->(idv_session:, user:) do
idv_session.ssn && idv_session.remote_document_capture_complete?
idv_session.remote_document_capture_complete? &&
idv_session.ssn_step_complete? &&
threatmetrix_session_id_present_or_not_required?(idv_session:)
end,
undo_step: ->(idv_session:, user:) do
idv_session.resolution_successful = nil
Expand Down
9 changes: 7 additions & 2 deletions app/forms/webauthn_setup_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def generic_error_message

private

attr_reader :success, :transports, :aaguid, :invalid_transports, :protocol
attr_reader :success, :transports, :invalid_transports, :protocol
attr_accessor :user, :challenge, :attestation_object, :client_data_json,
:name, :platform_authenticator, :authenticator_data_flags, :device_name

Expand Down Expand Up @@ -110,7 +110,6 @@ def valid_attestation_response?(protocol)
)

begin
@aaguid = attestation_response.authenticator_data.aaguid
attestation_response.valid?(@challenge.pack('c*'), original_origin)
rescue StandardError
false
Expand Down Expand Up @@ -161,6 +160,12 @@ def mfa_user
@mfa_user ||= MfaContext.new(user)
end

def aaguid
attestation_response&.authenticator_data&.aaguid
rescue StandardError
nil
end

def extra_analytics_attributes
auth_method = if platform_authenticator?
'webauthn_platform'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { RegisterFieldCallback } from '@18f/identity-form-steps';
import { useDidUpdateEffect } from '@18f/identity-react-hooks';
import { SpinnerButtonRefHandle, SpinnerButton } from '@18f/identity-spinner-button';
import { ValidatedField } from '@18f/identity-validated-field';
import { t } from '@18f/identity-i18n';
import { useI18n } from '@18f/identity-react-i18n';
import { useCallback, useEffect, useRef, useState } from 'react';
import useValidatedUspsLocations from '../hooks/use-validated-usps-locations';

Expand All @@ -30,6 +30,7 @@ export default function FullAddressSearchInput({
registerField = () => undefined,
usStatesTerritories,
}: FullAddressSearchInputProps) {
const { t } = useI18n();
const spinnerButtonRef = useRef<SpinnerButtonRefHandle>(null);
const [addressValue, setAddressValue] = useState('');
const [cityValue, setCityValue] = useState('');
Expand Down Expand Up @@ -82,12 +83,27 @@ export default function FullAddressSearchInput({
[addressValue, cityValue, stateValue, zipCodeValue],
);

const getErroneousAddressChars = () => {
const addressReStr = validatedAddressFieldRef.current?.pattern;

if (!addressReStr) {
return;
}

const addressRegex = new RegExp(addressReStr, 'g');
const errChars = addressValue.replace(addressRegex, '');
const uniqErrChars = [...new Set(errChars.split(''))].join('');
return uniqErrChars;
};

return (
<>
<ValidatedField
ref={validatedAddressFieldRef}
messages={{
patternMismatch: t('simple_form.required.text'),
patternMismatch: t('in_person_proofing.form.address.errors.unsupported_chars', {
char_list: getErroneousAddressChars(),
}),
}}
>
<TextInput
Expand All @@ -98,7 +114,7 @@ export default function FullAddressSearchInput({
label={t('in_person_proofing.body.location.po_search.address_label')}
disabled={disabled}
maxLength={255}
pattern=".*\S.*$"
pattern="[A-Za-z0-9\-' .\/#]*"
/>
</ValidatedField>
<ValidatedField
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';
import type { SetupServer } from 'msw/node';
import { SWRConfig } from 'swr';
import { I18n } from '@18f/identity-i18n';
import { I18nContext } from '@18f/identity-react-i18n';
import FullAddressSearch from './full-address-search';

describe('FullAddressSearch', () => {
Expand Down Expand Up @@ -133,6 +135,62 @@ describe('FullAddressSearch', () => {
expect(errors).to.have.lengthOf(4);
});

it('displays an error for unsupported characters in address field', async () => {
const handleLocationsFound = sandbox.stub();
const locationCache = new Map();
const { findByText, findByLabelText } = render(
<I18nContext.Provider
value={
new I18n({
strings: {
'in_person_proofing.form.address.errors.unsupported_chars':
'Our system cannot read the following characters: %{char_list} . Please try again using substitutes for those characters.',
},
})
}
>
<SWRConfig value={{ provider: () => locationCache }}>
<FullAddressSearch
usStatesTerritories={usStatesTerritories}
onFoundLocations={handleLocationsFound}
locationsURL={locationsURL}
registerField={() => undefined}
handleLocationSelect={undefined}
disabled={false}
/>
</SWRConfig>
,
</I18nContext.Provider>,
);

await userEvent.type(
await findByLabelText('in_person_proofing.body.location.po_search.address_label'),
'20, main',
);
await userEvent.type(
await findByLabelText('in_person_proofing.body.location.po_search.city_label'),
'Endeavor',
);
await userEvent.selectOptions(
await findByLabelText('in_person_proofing.body.location.po_search.state_label'),
'DE',
);
await userEvent.type(
await findByLabelText('in_person_proofing.body.location.po_search.zipcode_label'),
'00010',
);
await userEvent.click(
await findByText('in_person_proofing.body.location.po_search.search_button'),
);

const error = await findByText(
'Our system cannot read the following characters: , . Please try again using substitutes for those characters.',
);

expect(error).to.exist();
expect(locationCache.size).to.equal(1);
});

it('displays an error for an invalid ZIP code length (length = 1)', async () => {
const handleLocationsFound = sandbox.stub();
const { findByText, findByLabelText, findAllByText } = render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { t } from '@18f/identity-i18n';

export default function useValidatedUspsLocations(locationsURL: string) {
const [locationQuery, setLocationQuery] = useState<LocationQuery | null>(null);
const validatedAddressFieldRef = useRef<HTMLFormElement>(null);
const validatedCityFieldRef = useRef<HTMLFormElement>(null);
const validatedStateFieldRef = useRef<HTMLFormElement>(null);
const validatedZipCodeFieldRef = useRef<HTMLFormElement>(null);
const validatedAddressFieldRef = useRef<HTMLInputElement>(null);
const validatedCityFieldRef = useRef<HTMLInputElement>(null);
const validatedStateFieldRef = useRef<HTMLInputElement>(null);
const validatedZipCodeFieldRef = useRef<HTMLInputElement>(null);

const checkValidityAndDisplayErrors = (address, city, state, zipCode) => {
let formIsValid = true;
Expand Down Expand Up @@ -48,7 +48,14 @@ export default function useValidatedUspsLocations(locationsURL: string) {
validatedStateFieldRef.current?.reportValidity();
validatedZipCodeFieldRef.current?.reportValidity();

return formIsValid && zipCodeIsValid;
const hasInvalidFields = [
validatedAddressFieldRef,
validatedCityFieldRef,
validatedStateFieldRef,
validatedZipCodeFieldRef,
].some((fieldRef) => fieldRef.current?.validity?.valid === false);

return formIsValid && zipCodeIsValid && !hasInvalidFields;
};

const handleLocationSearch = useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ function DocumentCapture({ onStepChange = () => {} }: DocumentCaptureProps) {
const documentFormStep: FormStep = {
name: 'documents',
form: DocumentsStep,
title: t('doc_auth.headings.document_capture'), // might want to change title to isolated doc capture heading
title: t('doc_auth.headings.document_capture'),
};
const selfieFormStep: FormStep = {
name: 'selfie',
form: SelfieStep,
title: '', // TODO: replace with yml selfie_capture (Ticket LG-14392)
title: t('doc_auth.headings.selfie_capture'),
};
const documentsFormSteps: FormStep[] =
isSelfieCaptureEnabled && docAuthSeparatePagesEnabled && submissionError === undefined
Expand Down
Loading