Skip to content

Commit

Permalink
Merge pull request #11279 from 18F/stages/rc-2024-09-24
Browse files Browse the repository at this point in the history
Deploy RC 416 to Production
  • Loading branch information
matthinz authored Sep 24, 2024
2 parents 59a2720 + ef7c472 commit 2b6d4f7
Show file tree
Hide file tree
Showing 54 changed files with 593 additions and 206 deletions.
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

0 comments on commit 2b6d4f7

Please sign in to comment.