diff --git a/app/assets/stylesheets/email.css.scss b/app/assets/stylesheets/email.css.scss index 20a619cab65..2015a1661d0 100644 --- a/app/assets/stylesheets/email.css.scss +++ b/app/assets/stylesheets/email.css.scss @@ -39,9 +39,13 @@ line-height: 60px; } -h4 { - font-weight: bold; - margin-bottom: 16px; +h1, +h2, +h3, +h4, +h5, +h6 { + line-height: 1.4; } .button.large.expanded table a { diff --git a/app/assets/stylesheets/variables/_email.scss b/app/assets/stylesheets/variables/_email.scss index 134d4856e85..aca2ac2c8f6 100644 --- a/app/assets/stylesheets/variables/_email.scss +++ b/app/assets/stylesheets/variables/_email.scss @@ -66,14 +66,14 @@ $global-line-height: 1.5; $global-font-size: 16px; $body-line-height: $global-line-height; $header-font-family: $body-font-family; -$header-font-weight: $global-font-weight; -$h1-font-size: 34px; -$h2-font-size: 30px; -$h3-font-size: 28px; -$h4-font-size: 24px; -$h5-font-size: 20px; -$h6-font-size: 18px; -$header-margin-bottom: 10px; +$header-font-weight: 700; +$h1-font-size: 28px; +$h2-font-size: 22px; +$h3-font-size: 18px; +$h4-font-size: 16px; +$h5-font-size: 14px; +$h6-font-size: 12px; +$header-margin-bottom: 16px; $paragraph-margin-bottom: 10px; $small-font-size: 80%; $small-font-color: $medium-gray; diff --git a/app/controllers/concerns/idv/verify_info_concern.rb b/app/controllers/concerns/idv/verify_info_concern.rb index a4172f60aab..9169af3cf40 100644 --- a/app/controllers/concerns/idv/verify_info_concern.rb +++ b/app/controllers/concerns/idv/verify_info_concern.rb @@ -25,6 +25,7 @@ def shared_update idv_session.vendor_phone_confirmation = false idv_session.user_phone_confirmation = false + pii[:ssn] = idv_session.ssn # Required for proof_resolution job Idv::Agent.new(pii).proof_resolution( document_capture_session, should_proof_state_id: should_use_aamva?(pii), @@ -69,9 +70,8 @@ def resolution_rate_limiter end def ssn_rate_limiter - ssn = idv_session.ssn || pii[:ssn] @ssn_rate_limiter ||= RateLimiter.new( - target: Pii::Fingerprinter.fingerprint(ssn), + target: Pii::Fingerprinter.fingerprint(idv_session.ssn), rate_limit_type: :proof_ssn, ) end @@ -301,19 +301,18 @@ def log_idv_verification_submitted_event(success: false, failure_reason: nil) last_name: pii_from_doc[:last_name], date_of_birth: pii_from_doc[:dob], address: pii_from_doc[:address1], - ssn: idv_session.ssn || pii_from_doc[:ssn], + ssn: idv_session.ssn, failure_reason: failure_reason, ) end def check_ssn - ssn = idv_session.ssn || pii[:ssn] - Idv::SsnForm.new(current_user).submit(ssn: ssn) + Idv::SsnForm.new(current_user).submit(ssn: idv_session.ssn) end def move_applicant_to_idv_session idv_session.applicant = pii - idv_session.applicant[:ssn] ||= idv_session.ssn + idv_session.applicant[:ssn] = idv_session.ssn idv_session.applicant['uuid'] = current_user.uuid delete_pii end diff --git a/app/controllers/concerns/idv_step_concern.rb b/app/controllers/concerns/idv_step_concern.rb index 44ce2834e9d..157b6d8ab8b 100644 --- a/app/controllers/concerns/idv_step_concern.rb +++ b/app/controllers/concerns/idv_step_concern.rb @@ -52,7 +52,7 @@ def flow_path private def confirm_ssn_step_complete - return if pii.present? && (idv_session.ssn.present? || pii[:ssn].present?) + return if pii.present? && idv_session.ssn.present? redirect_to prev_url end diff --git a/app/controllers/concerns/rate_limit_concern.rb b/app/controllers/concerns/rate_limit_concern.rb index d5b3a9ae4e7..8ada2c10913 100644 --- a/app/controllers/concerns/rate_limit_concern.rb +++ b/app/controllers/concerns/rate_limit_concern.rb @@ -64,9 +64,7 @@ def idv_attempter_rate_limited?(rate_limit_type) end def pii_ssn - return unless defined?(flow_session) && defined?(idv_session) && user_session - pii_from_doc_ssn = idv_session&.ssn || flow_session[:pii_from_doc]&.[](:ssn) - return pii_from_doc_ssn if pii_from_doc_ssn - flow_session[:pii_from_user]&.[](:ssn) + return unless defined?(idv_session) && user_session + idv_session&.ssn end end diff --git a/app/controllers/idv/in_person/ssn_controller.rb b/app/controllers/idv/in_person/ssn_controller.rb index 8a717ba6968..6b9dad37590 100644 --- a/app/controllers/idv/in_person/ssn_controller.rb +++ b/app/controllers/idv/in_person/ssn_controller.rb @@ -14,8 +14,7 @@ class SsnController < ApplicationController attr_accessor :error_message def show - incoming_ssn = idv_session.ssn || flow_session.dig(:pii_from_user, :ssn) - @ssn_form = Idv::SsnFormatForm.new(current_user, incoming_ssn) + @ssn_form = Idv::SsnFormatForm.new(current_user, idv_session.ssn) analytics.idv_doc_auth_redo_ssn_submitted(**analytics_arguments) if updating_ssn? analytics.idv_doc_auth_ssn_visited(**analytics_arguments) @@ -28,8 +27,7 @@ def show def update @error_message = nil - incoming_ssn = idv_session.ssn || flow_session.dig(:pii_from_user, :ssn) - @ssn_form = Idv::SsnFormatForm.new(current_user, incoming_ssn) + @ssn_form = Idv::SsnFormatForm.new(current_user, idv_session.ssn) ssn = params.require(:doc_auth).permit(:ssn) form_response = @ssn_form.submit(ssn) @@ -42,7 +40,6 @@ def update ) if form_response.success? - flow_session[:pii_from_user][:ssn] = params[:doc_auth][:ssn] idv_session.ssn = params[:doc_auth][:ssn] idv_session.invalidate_steps_after_ssn! redirect_to idv_in_person_verify_info_url @@ -70,7 +67,7 @@ def flow_path end def confirm_repeat_ssn - return if !idv_session.ssn && !pii_from_user[:ssn] + return if !idv_session.ssn return if request.referer == idv_in_person_verify_info_url redirect_to idv_in_person_verify_info_url end @@ -86,7 +83,7 @@ def analytics_arguments end def updating_ssn? - idv_session.ssn.present? || flow_session.dig(:pii_from_user, :ssn).present? + idv_session.ssn.present? end def confirm_in_person_address_step_complete diff --git a/app/controllers/idv/in_person/verify_info_controller.rb b/app/controllers/idv/in_person/verify_info_controller.rb index e2853047e3c..6ce2fc38a91 100644 --- a/app/controllers/idv/in_person/verify_info_controller.rb +++ b/app/controllers/idv/in_person/verify_info_controller.rb @@ -12,7 +12,7 @@ class VerifyInfoController < ApplicationController def show @step_indicator_steps = step_indicator_steps - @ssn = idv_session.ssn || flow_session[:pii_from_user][:ssn] + @ssn = idv_session.ssn @capture_secondary_id_enabled = capture_secondary_id_enabled analytics.idv_doc_auth_verify_visited(**analytics_arguments) diff --git a/app/controllers/idv/session_errors_controller.rb b/app/controllers/idv/session_errors_controller.rb index 4dd98df5847..d7b9ca201c3 100644 --- a/app/controllers/idv/session_errors_controller.rb +++ b/app/controllers/idv/session_errors_controller.rb @@ -39,9 +39,9 @@ def failure def ssn_failure rate_limiter = nil - if ssn_from_doc + if idv_session&.ssn rate_limiter = RateLimiter.new( - target: Pii::Fingerprinter.fingerprint(ssn_from_doc), + target: Pii::Fingerprinter.fingerprint(idv_session.ssn), rate_limit_type: :proof_ssn, ) @expires_at = rate_limiter.expires_at @@ -59,10 +59,6 @@ def rate_limited private - def ssn_from_doc - idv_session&.ssn || user_session&.dig('idv/doc_auth', :pii_from_doc, :ssn) - end - def confirm_two_factor_authenticated_or_user_id_in_session return if session[:doc_capture_user_id].present? diff --git a/app/controllers/idv/ssn_controller.rb b/app/controllers/idv/ssn_controller.rb index ab41a5b0df5..f0e64000d91 100644 --- a/app/controllers/idv/ssn_controller.rb +++ b/app/controllers/idv/ssn_controller.rb @@ -13,8 +13,7 @@ class SsnController < ApplicationController attr_accessor :error_message def show - incoming_ssn = idv_session.ssn || flow_session.dig(:pii_from_doc, :ssn) - @ssn_form = Idv::SsnFormatForm.new(current_user, incoming_ssn) + @ssn_form = Idv::SsnFormatForm.new(current_user, idv_session.ssn) analytics.idv_doc_auth_redo_ssn_submitted(**analytics_arguments) if @ssn_form.updating_ssn? analytics.idv_doc_auth_ssn_visited(**analytics_arguments) @@ -28,8 +27,7 @@ def show def update @error_message = nil - incoming_ssn = idv_session.ssn || flow_session.dig(:pii_from_doc, :ssn) - @ssn_form = Idv::SsnFormatForm.new(current_user, incoming_ssn) + @ssn_form = Idv::SsnFormatForm.new(current_user, idv_session.ssn) form_response = @ssn_form.submit(params.require(:doc_auth).permit(:ssn)) analytics.idv_doc_auth_ssn_submitted( @@ -40,7 +38,6 @@ def update ) if form_response.success? - flow_session[:pii_from_doc][:ssn] = params[:doc_auth][:ssn] idv_session.ssn = params[:doc_auth][:ssn] idv_session.invalidate_steps_after_ssn! redirect_to next_url @@ -53,7 +50,7 @@ def update private def confirm_repeat_ssn - return if !idv_session.ssn && !pii_from_doc[:ssn] + return if !idv_session.ssn return if request.referer == idv_verify_info_url redirect_to idv_verify_info_url diff --git a/app/controllers/idv/verify_info_controller.rb b/app/controllers/idv/verify_info_controller.rb index 833143b4cb7..cd4f7e1c340 100644 --- a/app/controllers/idv/verify_info_controller.rb +++ b/app/controllers/idv/verify_info_controller.rb @@ -11,7 +11,7 @@ class VerifyInfoController < ApplicationController def show @step_indicator_steps = step_indicator_steps - @ssn = idv_session.ssn || pii_from_doc[:ssn] + @ssn = idv_session.ssn analytics.idv_doc_auth_verify_visited(**analytics_arguments) Funnel::DocAuth::RegisterStep.new(current_user.id, sp_session[:issuer]). diff --git a/app/controllers/users/phones_controller.rb b/app/controllers/users/phones_controller.rb deleted file mode 100644 index b5bd2834ef8..00000000000 --- a/app/controllers/users/phones_controller.rb +++ /dev/null @@ -1,78 +0,0 @@ -module Users - class PhonesController < ApplicationController - include PhoneConfirmation - include RecaptchaConcern - include ReauthenticationRequiredConcern - include MfaSetupConcern - - before_action :confirm_user_authenticated_for_2fa_setup - before_action :redirect_if_phone_vendor_outage - before_action :check_max_phone_numbers_per_account, only: %i[add create] - before_action :allow_csp_recaptcha_src, if: :recaptcha_enabled? - before_action :confirm_recently_authenticated_2fa - - helper_method :in_multi_mfa_selection_flow? - - def add - user_session[:phone_id] = nil - @new_phone_form = NewPhoneForm.new(user: current_user, analytics: analytics) - analytics.add_phone_setup_visit - end - - def create - @new_phone_form = NewPhoneForm.new(user: current_user, analytics: analytics) - result = @new_phone_form.submit(user_params) - analytics.multi_factor_auth_phone_setup(**result.to_h) - if result.success? - confirm_phone - elsif recoverable_recaptcha_error?(result) - render 'users/phone_setup/spam_protection' - else - render :add - end - end - - private - - def redirect_if_phone_vendor_outage - return unless OutageStatus.new.all_phone_vendor_outage? - redirect_to vendor_outage_path(from: :users_phones) - end - - def user_params - params.require(:new_phone_form).permit( - :phone, - :international_code, - :otp_delivery_preference, - :otp_make_default_number, - :recaptcha_token, - :recaptcha_version, - :recaptcha_mock_score, - ) - end - - def confirm_phone - flash[:info] = t('devise.registrations.phone_update_needs_confirmation') - prompt_to_confirm_phone( - id: user_session[:phone_id], - phone: @new_phone_form.phone, - selected_delivery_method: @new_phone_form.otp_delivery_preference, - selected_default_number: @new_phone_form.otp_make_default_number, - ) - end - - def check_max_phone_numbers_per_account - max_phones_count = IdentityConfig.store.max_phone_numbers_per_account - return if current_user.phone_configurations.count < max_phones_count - flash[:phone_error] = t('users.phones.error_message') - redirect_path = request.referer.match(account_two_factor_authentication_url) ? - account_two_factor_authentication_url(anchor: 'phones') : - account_url(anchor: 'phones') - redirect_to redirect_path - end - - def recaptcha_enabled? - FeatureManagement.phone_recaptcha_enabled? - end - end -end diff --git a/app/controllers/users/two_factor_authentication_controller.rb b/app/controllers/users/two_factor_authentication_controller.rb index b451223895f..14f823d11c3 100644 --- a/app/controllers/users/two_factor_authentication_controller.rb +++ b/app/controllers/users/two_factor_authentication_controller.rb @@ -206,8 +206,6 @@ def handle_telephony_result(method:, default:, otp_delivery_selection_result:) otp_make_default_number: default, ) elsif @telephony_result.error.is_a?(Telephony::OptOutError) - # clear message from https://github.com/18F/identity-idp/blob/7ad3feab24f6f9e0e45224d9e9be9458c0a6a648/app/controllers/users/phones_controller.rb#L40 - flash.delete(:info) opt_out = PhoneNumberOptOut.mark_opted_out(phone_to_deliver_to) redirect_to login_two_factor_sms_opt_in_path(opt_out_uuid: opt_out) else diff --git a/app/forms/idv/state_id_form.rb b/app/forms/idv/state_id_form.rb index 2225225344a..3f80e517ca3 100644 --- a/app/forms/idv/state_id_form.rb +++ b/app/forms/idv/state_id_form.rb @@ -13,8 +13,9 @@ def self.model_name ActiveModel::Name.new(self, nil, 'StateId') end - def initialize(pii) + def initialize(pii, capture_secondary_id_enabled:) @pii = pii + @capture_secondary_id_enabled = capture_secondary_id_enabled end def submit(params) @@ -35,6 +36,9 @@ def submit(params) private + attr_reader :capture_secondary_id_enabled + alias_method :capture_secondary_id_enabled?, :capture_secondary_id_enabled + def consume_params(params) params.each do |key, value| raise_invalid_state_id_parameter_error(key) unless ATTRIBUTES.include?(key.to_sym) 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 3615ce763a3..07be6b3d90a 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 @@ -10,7 +10,6 @@ import { useI18n } from '@18f/identity-react-i18n'; import UnknownError from './unknown-error'; import TipList from './tip-list'; import DocumentSideAcuantCapture from './document-side-acuant-capture'; -import DocumentCaptureTroubleshootingOptions from './document-capture-troubleshooting-options'; interface DocumentCaptureReviewIssuesProps { isFailedDocType: boolean; @@ -22,6 +21,7 @@ interface DocumentCaptureReviewIssuesProps { errors: FormStepError[]; onChange: (...args: any) => void; onError: OnErrorCallback; + hasDismissed: boolean; } type DocumentSide = 'front' | 'back'; @@ -40,6 +40,7 @@ function DocumentCaptureReviewIssues({ onChange = () => undefined, onError = () => undefined, value = {}, + hasDismissed, }: DocumentCaptureReviewIssuesProps) { const { t } = useI18n(); return ( @@ -50,9 +51,11 @@ function DocumentCaptureReviewIssues({ remainingAttempts={remainingAttempts} isFailedDocType={isFailedDocType} altFailedDocTypeMsg={isFailedDocType ? t('doc_auth.errors.doc.wrong_id_type') : null} + hasDismissed={hasDismissed} /> {!isFailedDocType && captureHints && ( ))} - - ); diff --git a/app/javascript/packages/document-capture/components/document-capture-warning.tsx b/app/javascript/packages/document-capture/components/document-capture-warning.tsx index 42ae52b3455..24c74a56edf 100644 --- a/app/javascript/packages/document-capture/components/document-capture-warning.tsx +++ b/app/javascript/packages/document-capture/components/document-capture-warning.tsx @@ -13,6 +13,7 @@ interface DocumentCaptureWarningProps { remainingAttempts: number; actionOnClick?: () => void; unknownFieldErrors: FormStepError<{ front: string; back: string }>[]; + hasDismissed: boolean; } const DISPLAY_ATTEMPTS = 3; @@ -23,6 +24,7 @@ function DocumentCaptureWarning({ remainingAttempts, actionOnClick, unknownFieldErrors = [], + hasDismissed, }: DocumentCaptureWarningProps) { const { t } = useI18n(); const { inPersonURL } = useContext(InPersonContext); @@ -59,6 +61,7 @@ function DocumentCaptureWarning({ unknownFieldErrors={unknownFieldErrors} remainingAttempts={remainingAttempts} isFailedDocType={isFailedDocType} + hasDismissed={hasDismissed} /> {!isFailedDocType && remainingAttempts <= DISPLAY_ATTEMPTS && ( diff --git a/app/javascript/packages/document-capture/components/documents-step.jsx b/app/javascript/packages/document-capture/components/documents-step.jsx index 4158186a2ff..bc88c754cf7 100644 --- a/app/javascript/packages/document-capture/components/documents-step.jsx +++ b/app/javascript/packages/document-capture/components/documents-step.jsx @@ -7,7 +7,6 @@ import HybridDocCaptureWarning from './hybrid-doc-capture-warning'; import DocumentSideAcuantCapture from './document-side-acuant-capture'; import DeviceContext from '../context/device'; import UploadContext from '../context/upload'; -import DocumentCaptureTroubleshootingOptions from './document-capture-troubleshooting-options'; import TipList from './tip-list'; /** @@ -51,6 +50,7 @@ function DocumentsStep({ {t('doc_auth.headings.document_capture')}

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

))} {isLastStep ? : } - ); diff --git a/app/javascript/packages/document-capture/components/full-address-search-input.tsx b/app/javascript/packages/document-capture/components/full-address-search-input.tsx new file mode 100644 index 00000000000..e81c5f9f771 --- /dev/null +++ b/app/javascript/packages/document-capture/components/full-address-search-input.tsx @@ -0,0 +1,177 @@ +import { TextInput, SelectInput } from '@18f/identity-components'; +import type { FormattedLocation, LocationQuery } from '@18f/identity-address-search/types'; +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 { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { InPersonContext } from '../context'; +import useValidatedUspsLocations from '../hooks/use-validated-usps-locations'; + +interface FullAddressSearchProps { + registerField?: RegisterFieldCallback; + onFoundLocations?: ( + address: LocationQuery | null, + locations: FormattedLocation[] | null | undefined, + ) => void; + onLoadingLocations?: (isLoading: boolean) => void; + onError?: (error: Error | null) => void; + disabled?: boolean; + locationsURL: string; +} + +export default function FullAddressSearchInput({ + registerField = () => undefined, + onFoundLocations = () => undefined, + onLoadingLocations = () => undefined, + onError = () => undefined, + disabled = false, + locationsURL, +}: FullAddressSearchProps) { + const spinnerButtonRef = useRef(null); + const [addressValue, setAddressValue] = useState(''); + const [cityValue, setCityValue] = useState(''); + const [stateValue, setStateValue] = useState(''); + const [zipCodeValue, setZipCodeValue] = useState(''); + const { + locationQuery, + locationResults, + uspsError, + isLoading, + handleLocationSearch: onSearch, + validatedAddressFieldRef, + validatedCityFieldRef, + validatedStateFieldRef, + validatedZipCodeFieldRef, + } = useValidatedUspsLocations(locationsURL); + + const inputChangeHandler = + (input) => + (event: React.ChangeEvent) => { + const { target } = event; + input(target.value); + }; + + type SelectChangeEvent = React.ChangeEvent; + + const onAddressChange = inputChangeHandler(setAddressValue); + const onCityChange = inputChangeHandler(setCityValue); + const onStateChange = (e: SelectChangeEvent) => setStateValue(e.target.value); + const onZipCodeChange = inputChangeHandler(setZipCodeValue); + + useEffect(() => { + spinnerButtonRef.current?.toggleSpinner(isLoading); + onLoadingLocations(isLoading); + }, [isLoading]); + + useEffect(() => { + uspsError && onError(uspsError); + }, [uspsError]); + + useDidUpdateEffect(() => { + onFoundLocations(locationQuery, locationResults); + }, [locationResults]); + + const handleSearch = useCallback( + (event) => { + onError(null); + onSearch(event, addressValue, cityValue, stateValue, zipCodeValue); + }, + [addressValue, cityValue, stateValue, zipCodeValue], + ); + + const { usStatesTerritories } = useContext(InPersonContext); + + return ( + <> + + + + + + + + + + {usStatesTerritories.map(([name, abbr]) => ( + + ))} + + + + + +
+ + {t('in_person_proofing.body.location.po_search.search_button')} + +
+ + ); +} diff --git a/app/javascript/packages/document-capture/components/in-person-full-address-search.spec.tsx b/app/javascript/packages/document-capture/components/in-person-full-address-search.spec.tsx index 713fe90b93b..d879b20da61 100644 --- a/app/javascript/packages/document-capture/components/in-person-full-address-search.spec.tsx +++ b/app/javascript/packages/document-capture/components/in-person-full-address-search.spec.tsx @@ -16,7 +16,13 @@ describe('FullAddressSearch', () => { const handleLocationsFound = sandbox.stub(); const { findByText, findAllByText } = render( new Map() }}> - + , ); @@ -32,7 +38,13 @@ describe('FullAddressSearch', () => { const handleLocationsFound = sandbox.stub(); const { findByText, findByLabelText, findAllByText } = render( new Map() }}> - + , ); @@ -64,7 +76,13 @@ describe('FullAddressSearch', () => { const handleLocationsFound = sandbox.stub(); const { findByText, findByLabelText, queryByText } = render( new Map() }}> - + , ); @@ -109,7 +127,13 @@ describe('FullAddressSearch', () => { const handleLocationsFound = sandbox.stub(); const { findByText, findByLabelText } = render( new Map() }}> - + , ); diff --git a/app/javascript/packages/document-capture/components/in-person-full-address-search.tsx b/app/javascript/packages/document-capture/components/in-person-full-address-search.tsx index 0f3d3639feb..1cbd826ed70 100644 --- a/app/javascript/packages/document-capture/components/in-person-full-address-search.tsx +++ b/app/javascript/packages/document-capture/components/in-person-full-address-search.tsx @@ -1,177 +1,55 @@ -import { TextInput, SelectInput } from '@18f/identity-components'; -import { useState, useRef, useEffect, useCallback, useContext } from 'react'; +import { Alert, PageHeading } from '@18f/identity-components'; +import { useState } from 'react'; import { t } from '@18f/identity-i18n'; -import ValidatedField from '@18f/identity-validated-field/validated-field'; -import SpinnerButton, { SpinnerButtonRefHandle } from '@18f/identity-spinner-button/spinner-button'; -import type { RegisterFieldCallback } from '@18f/identity-form-steps'; -import { useDidUpdateEffect } from '@18f/identity-react-hooks'; +import { InPersonLocations } from '@18f/identity-address-search'; import type { LocationQuery, FormattedLocation } from '@18f/identity-address-search/types'; -import { InPersonContext } from '../context'; -import useValidatedUspsLocations from '../hooks/use-validated-usps-locations'; - -interface FullAddressSearchProps { - registerField?: RegisterFieldCallback; - onFoundLocations?: ( - address: LocationQuery | null, - locations: FormattedLocation[] | null | undefined, - ) => void; - onLoadingLocations?: (isLoading: boolean) => void; - onError?: (error: Error | null) => void; - disabled?: boolean; - locationsURL: string; -} +import FullAddressSearchInput from './full-address-search-input'; function FullAddressSearch({ - registerField = () => undefined, - onFoundLocations = () => undefined, - onLoadingLocations = () => undefined, - onError = () => undefined, - disabled = false, + registerField, locationsURL, -}: FullAddressSearchProps) { - const spinnerButtonRef = useRef(null); - const [addressValue, setAddressValue] = useState(''); - const [cityValue, setCityValue] = useState(''); - const [stateValue, setStateValue] = useState(''); - const [zipCodeValue, setZipCodeValue] = useState(''); - const { - locationQuery, - locationResults, - uspsError, - isLoading, - handleLocationSearch: onSearch, - validatedAddressFieldRef, - validatedCityFieldRef, - validatedStateFieldRef, - validatedZipCodeFieldRef, - } = useValidatedUspsLocations(locationsURL); - - const inputChangeHandler = - (input) => - (event: React.ChangeEvent) => { - const { target } = event; - input(target.value); - }; - - type SelectChangeEvent = React.ChangeEvent; - - const onAddressChange = inputChangeHandler(setAddressValue); - const onCityChange = inputChangeHandler(setCityValue); - const onStateChange = (e: SelectChangeEvent) => setStateValue(e.target.value); - const onZipCodeChange = inputChangeHandler(setZipCodeValue); - - useEffect(() => { - spinnerButtonRef.current?.toggleSpinner(isLoading); - onLoadingLocations(isLoading); - }, [isLoading]); - - useEffect(() => { - uspsError && onError(uspsError); - }, [uspsError]); - - useDidUpdateEffect(() => { - onFoundLocations(locationQuery, locationResults); - }, [locationResults]); - - const handleSearch = useCallback( - (event) => { - onError(null); - onSearch(event, addressValue, cityValue, stateValue, zipCodeValue); - }, - [addressValue, cityValue, stateValue, zipCodeValue], + handleLocationSelect, + disabled, + onFoundLocations, +}) { + const [apiError, setApiError] = useState(null); + const [foundAddress, setFoundAddress] = useState(null); + const [locationResults, setLocationResults] = useState( + null, ); - - const { usStatesTerritories } = useContext(InPersonContext); + const [isLoadingLocations, setLoadingLocations] = useState(false); return ( <> - - - - - - - - - - {usStatesTerritories.map(([name, abbr]) => ( - - ))} - - - + {t('idv.failure.exceptions.post_office_search_error')} + + )} + {t('in_person_proofing.headings.po_search.location')} +

{t('in_person_proofing.body.location.po_search.po_search_about')}

+ { + setFoundAddress(address); + setLocationResults(locations); + onFoundLocations(locations); }} - > - + {locationResults && foundAddress && !isLoadingLocations && ( + -
-
- - {t('in_person_proofing.body.location.po_search.search_button')} - -
+ )} ); } diff --git a/app/javascript/packages/document-capture/components/in-person-location-full-address-entry-post-office-search-step.tsx b/app/javascript/packages/document-capture/components/in-person-location-full-address-entry-post-office-search-step.tsx index 564c9fdcb0c..1ce97987a37 100644 --- a/app/javascript/packages/document-capture/components/in-person-location-full-address-entry-post-office-search-step.tsx +++ b/app/javascript/packages/document-capture/components/in-person-location-full-address-entry-post-office-search-step.tsx @@ -1,10 +1,8 @@ import { useState, useEffect, useCallback, useRef, useContext } from 'react'; -import { t } from '@18f/identity-i18n'; -import { Alert, PageHeading } from '@18f/identity-components'; import { request } from '@18f/identity-request'; import { forceRedirect } from '@18f/identity-url'; -import { transformKeys, snakeCase, InPersonLocations } from '@18f/identity-address-search'; -import type { LocationQuery, FormattedLocation } from '@18f/identity-address-search/types'; +import { transformKeys, snakeCase } from '@18f/identity-address-search'; +import type { FormattedLocation } from '@18f/identity-address-search/types'; import FullAddressSearch from './in-person-full-address-search'; import BackButton from './back-button'; import AnalyticsContext from '../context/analytics'; @@ -19,25 +17,14 @@ function InPersonLocationFullAddressEntryPostOfficeSearchStep({ }) { const { inPersonURL } = useContext(InPersonContext); const [inProgress, setInProgress] = useState(false); - const [isLoadingLocations, setLoadingLocations] = useState(false); const [autoSubmit, setAutoSubmit] = useState(false); const { trackEvent } = useContext(AnalyticsContext); const [locationResults, setLocationResults] = useState( null, ); - const [foundAddress, setFoundAddress] = useState(null); - const [apiError, setApiError] = useState(null); const [disabledAddressSearch, setDisabledAddressSearch] = useState(false); const { flowPath } = useContext(UploadContext); - const onFoundLocations = ( - address: LocationQuery | null, - locations: FormattedLocation[] | null | undefined, - ) => { - setFoundAddress(address); - setLocationResults(locations); - }; - // ref allows us to avoid a memory leak const mountedRef = useRef(false); @@ -105,28 +92,13 @@ function InPersonLocationFullAddressEntryPostOfficeSearchStep({ return ( <> - {apiError && ( - - {t('idv.failure.exceptions.post_office_search_error')} - - )} - {t('in_person_proofing.headings.po_search.location')} -

{t('in_person_proofing.body.location.po_search.po_search_about')}

- {locationResults && foundAddress && !isLoadingLocations && ( - - )} ); diff --git a/app/javascript/packages/document-capture/components/review-issues-step.tsx b/app/javascript/packages/document-capture/components/review-issues-step.tsx index 649a7574a56..b3cc4ab2b96 100644 --- a/app/javascript/packages/document-capture/components/review-issues-step.tsx +++ b/app/javascript/packages/document-capture/components/review-issues-step.tsx @@ -88,6 +88,7 @@ function ReviewIssuesStep({ remainingAttempts={remainingAttempts} unknownFieldErrors={unknownFieldErrors} actionOnClick={onWarningPageDismissed} + hasDismissed={false} /> ); } @@ -103,6 +104,7 @@ function ReviewIssuesStep({ errors={errors} onChange={onChange} onError={onError} + hasDismissed /> ); } diff --git a/app/javascript/packages/document-capture/components/tip-list.tsx b/app/javascript/packages/document-capture/components/tip-list.tsx index 13a87755ed4..d2e2fbbc9de 100644 --- a/app/javascript/packages/document-capture/components/tip-list.tsx +++ b/app/javascript/packages/document-capture/components/tip-list.tsx @@ -1,11 +1,12 @@ interface TipListProps { title: string; items: string[]; + titleClassName?: string; } -function TipList({ title, items = [] }: TipListProps) { +function TipList({ title, items = [], titleClassName }: TipListProps) { return ( <> -

{title}

+

{title}

    {items.map((item) => (
  • {item}
  • diff --git a/app/javascript/packages/document-capture/components/unknown-error.tsx b/app/javascript/packages/document-capture/components/unknown-error.tsx index 96ec5e3cc17..dd7fdf4c7ca 100644 --- a/app/javascript/packages/document-capture/components/unknown-error.tsx +++ b/app/javascript/packages/document-capture/components/unknown-error.tsx @@ -1,12 +1,16 @@ import type { ComponentProps } from 'react'; +import { useContext } from 'react'; import { useI18n, HtmlTextWithStrongNoWrap } from '@18f/identity-react-i18n'; import { FormStepError } from '@18f/identity-form-steps'; +import { Link } from '@18f/identity-components'; +import MarketingSiteContext from '../context/marketing-site'; interface UnknownErrorProps extends ComponentProps<'p'> { unknownFieldErrors: FormStepError<{ front: string; back: string }>[]; isFailedDocType: boolean; remainingAttempts: number; altFailedDocTypeMsg?: string | null; + hasDismissed: boolean; } function UnknownError({ @@ -14,8 +18,15 @@ function UnknownError({ isFailedDocType = false, remainingAttempts, altFailedDocTypeMsg = null, + hasDismissed, }: UnknownErrorProps) { const { t } = useI18n(); + const { getHelpCenterURL } = useContext(MarketingSiteContext); + const helpCenterLink = getHelpCenterURL({ + category: 'verify-your-identity', + article: 'how-to-add-images-of-your-state-issued-id', + location: 'document_capture_review_issues', + }); const errs = !!unknownFieldErrors && unknownFieldErrors.filter((error) => !['front', 'back'].includes(error.field!)); @@ -33,9 +44,19 @@ function UnknownError({

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

    {err.message}

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

    + {err.message}{' '} + + {t('doc_auth.info.review_examples_of_photos')} + +

    + ); + } return

    ; } diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/rails-i18n-webpack-plugin.js b/app/javascript/packages/rails-i18n-webpack-plugin/rails-i18n-webpack-plugin.js index 975fafbffc0..520f48ab767 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/rails-i18n-webpack-plugin.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/rails-i18n-webpack-plugin.js @@ -113,8 +113,7 @@ const getKeyDomains = (keys) => uniq(keys.map(getKeyDomain)); class RailsI18nWebpackPlugin extends ExtractKeysWebpackPlugin { /** @type {RailsI18nWebpackPluginOptions} */ static DEFAULT_OPTIONS = { - template: - '!function(){var k,o=%j,l=window._locale_data=window._locale_data||{};for(k in o)l[k]=o[k]}()', + template: '_locale_data=Object.assign(%j,this._locale_data)', configPath: path.resolve(process.cwd(), 'config/locales'), defaultLocale: 'en', onMissingString: () => {}, diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/rails-i18n-webpack-plugin.spec.js b/app/javascript/packages/rails-i18n-webpack-plugin/rails-i18n-webpack-plugin.spec.js index d46c64c362d..e89b4c9b8c2 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/rails-i18n-webpack-plugin.spec.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/rails-i18n-webpack-plugin.spec.js @@ -161,22 +161,22 @@ describe('RailsI18nWebpackPlugin', () => { ); expect(manifest).to.deep.equal({ - 'actualmain-3b0c232b.en.js': 'actualmain-3b0c232b.en.js', - 'actualmain-a43216c8.es.js': 'actualmain-a43216c8.es.js', + 'actualmain-5b00aabc.en.js': 'actualmain-5b00aabc.en.js', + 'actualmain-941d1f5f.es.js': 'actualmain-941d1f5f.es.js', 'actualmain.js': 'actualmain.js', entrypoints: { main: { assets: { js: [ 'actualmain.js', - 'actualmain-3b0c232b.en.js', - 'actualmain-a43216c8.es.js', - 'actualmain-3b0c232b.fr.js', + 'actualmain-5b00aabc.en.js', + 'actualmain-941d1f5f.es.js', + 'actualmain-5b00aabc.fr.js', ], }, }, }, - 'main.js': 'actualmain-3b0c232b.fr.js', + 'main.js': 'actualmain-5b00aabc.fr.js', }); done(); } catch (error) { diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.en.js b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.en.js index efa2763e950..e9c79acc6a8 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.en.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.en.js @@ -1 +1 @@ -!function(){var k,o={"forms.button.submit":"Submit","forms.messages":{"one":"One message","other":"%{count} messages"},"forms.key1":"value1-en","forms.key2":"value2-en","item.1":"First","item.2":"Second","item.3":"","forms.button.reset":"Reset"},l=window._locale_data=window._locale_data||{};for(k in o)l[k]=o[k]}() \ No newline at end of file +_locale_data=Object.assign({"forms.button.submit":"Submit","forms.messages":{"one":"One message","other":"%{count} messages"},"forms.key1":"value1-en","forms.key2":"value2-en","item.1":"First","item.2":"Second","item.3":"","forms.button.reset":"Reset"},this._locale_data) \ No newline at end of file diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.es.js b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.es.js index 814a80003c2..839199f2238 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.es.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.es.js @@ -1 +1 @@ -!function(){var k,o={"forms.button.submit":"Enviar","forms.messages":{"one":"Un mensaje","other":"%{count} mensajes"},"forms.key1":"value1-es","forms.key2":"value2-es","item.1":"First","item.2":"Second","item.3":"","forms.button.reset":"Reiniciar"},l=window._locale_data=window._locale_data||{};for(k in o)l[k]=o[k]}() \ No newline at end of file +_locale_data=Object.assign({"forms.button.submit":"Enviar","forms.messages":{"one":"Un mensaje","other":"%{count} mensajes"},"forms.key1":"value1-es","forms.key2":"value2-es","item.1":"First","item.2":"Second","item.3":"","forms.button.reset":"Reiniciar"},this._locale_data) \ No newline at end of file diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.fr.js b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.fr.js index da9b0654b94..2b9c238a96e 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.fr.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.fr.js @@ -1 +1 @@ -!function(){var k,o={"forms.button.submit":"Submit","forms.messages":{"one":"Un message","other":"%{count} messages"},"forms.key1":"value1-fr","forms.key2":"value2-fr","item.1":"Premier","item.2":"Second","item.3":"","forms.button.reset":"Réinitialiser"},l=window._locale_data=window._locale_data||{};for(k in o)l[k]=o[k]}() \ No newline at end of file +_locale_data=Object.assign({"forms.button.submit":"Submit","forms.messages":{"one":"Un message","other":"%{count} messages"},"forms.key1":"value1-fr","forms.key2":"value2-fr","item.1":"Premier","item.2":"Second","item.3":"","forms.button.reset":"Réinitialiser"},this._locale_data) \ No newline at end of file diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected452.en.js b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected452.en.js index 042002cfc7b..7da96c839e7 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected452.en.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected452.en.js @@ -1 +1 @@ -!function(){var k,o={"forms.dynamic":"Dynamic"},l=window._locale_data=window._locale_data||{};for(k in o)l[k]=o[k]}() \ No newline at end of file +_locale_data=Object.assign({"forms.dynamic":"Dynamic"},this._locale_data) \ No newline at end of file diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected452.es.js b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected452.es.js index d831638ab36..9a114ac5633 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected452.es.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected452.es.js @@ -1 +1 @@ -!function(){var k,o={"forms.dynamic":"Dinámico"},l=window._locale_data=window._locale_data||{};for(k in o)l[k]=o[k]}() \ No newline at end of file +_locale_data=Object.assign({"forms.dynamic":"Dinámico"},this._locale_data) \ No newline at end of file diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected452.fr.js b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected452.fr.js index 008b99685d7..878d87b2ffd 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected452.fr.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected452.fr.js @@ -1 +1 @@ -!function(){var k,o={"forms.dynamic":"Dynamique"},l=window._locale_data=window._locale_data||{};for(k in o)l[k]=o[k]}() \ No newline at end of file +_locale_data=Object.assign({"forms.dynamic":"Dynamique"},this._locale_data) \ No newline at end of file diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected946.en.js b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected946.en.js index 5d74f558a55..c2fdf8b71ed 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected946.en.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected946.en.js @@ -1 +1 @@ -!function(){var k,o={"forms.button.cancel":"Cancel"},l=window._locale_data=window._locale_data||{};for(k in o)l[k]=o[k]}() \ No newline at end of file +_locale_data=Object.assign({"forms.button.cancel":"Cancel"},this._locale_data) \ No newline at end of file diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected946.es.js b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected946.es.js index b62d6b49124..b8e17f1742b 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected946.es.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected946.es.js @@ -1 +1 @@ -!function(){var k,o={"forms.button.cancel":"Cancelar"},l=window._locale_data=window._locale_data||{};for(k in o)l[k]=o[k]}() \ No newline at end of file +_locale_data=Object.assign({"forms.button.cancel":"Cancelar"},this._locale_data) \ No newline at end of file diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected946.fr.js b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected946.fr.js index deae187476e..95b04979ff3 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected946.fr.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected946.fr.js @@ -1 +1 @@ -!function(){var k,o={"forms.button.cancel":"Annuler"},l=window._locale_data=window._locale_data||{};for(k in o)l[k]=o[k]}() \ No newline at end of file +_locale_data=Object.assign({"forms.button.cancel":"Annuler"},this._locale_data) \ No newline at end of file diff --git a/app/models/suspended_email.rb b/app/models/suspended_email.rb index 2e0005c4c7e..5a1011bc884 100644 --- a/app/models/suspended_email.rb +++ b/app/models/suspended_email.rb @@ -8,7 +8,7 @@ def generate_email_digest(email) OpenSSL::Digest::SHA256.hexdigest(normalized_email) end - def create_from_email_adddress!(email_address) + def create_from_email_address!(email_address) create!( digested_base_email: generate_email_digest(email_address.email), email_address: email_address, diff --git a/app/models/user.rb b/app/models/user.rb index 4f48edb3853..653b3edd461 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -123,7 +123,7 @@ def suspend! update!(suspended_at: Time.zone.now, unique_session_id: nil) analytics.user_suspended(success: true) email_addresses.map do |email_address| - SuspendedEmail.create_from_email_adddress!(email_address) + SuspendedEmail.create_from_email_address!(email_address) end end diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index efd3dc1df3f..de0670f125a 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -529,6 +529,50 @@ def forget_all_browsers_visited track_event('Forget All Browsers Visited') end + # @param [Boolean] success + # @param [Hash] errors + # @param [String] exception + # @param [String] profile_fraud_review_pending_at + # The user was passed by manual fraud review + def fraud_review_passed( + success:, + errors:, + exception:, + profile_fraud_review_pending_at:, + **extra + ) + track_event( + 'Fraud: Profile review passed', + success: success, + errors: errors, + exception: exception, + profile_fraud_review_pending_at: profile_fraud_review_pending_at, + **extra, + ) + end + + # @param [Boolean] success + # @param [Hash] errors + # @param [String] exception + # @param [String] profile_fraud_review_pending_at + # The user was rejected by manual fraud review + def fraud_review_rejected( + success:, + errors:, + exception:, + profile_fraud_review_pending_at:, + **extra + ) + track_event( + 'Fraud: Profile review rejected', + success: success, + errors: errors, + exception: exception, + profile_fraud_review_pending_at: profile_fraud_review_pending_at, + **extra, + ) + end + # @param [Boolean] success # @param [Boolean] address_edited # @param [Hash] pii_like_keypaths diff --git a/app/services/encryption/regional_ciphertext_pair.rb b/app/services/encryption/regional_ciphertext_pair.rb index 73646f14e79..1c5ab32b013 100644 --- a/app/services/encryption/regional_ciphertext_pair.rb +++ b/app/services/encryption/regional_ciphertext_pair.rb @@ -6,10 +6,6 @@ def to_ary end def multi_or_single_region_ciphertext - if IdentityConfig.store.aws_kms_multi_region_read_enabled - multi_region_ciphertext.presence || single_region_ciphertext - else - single_region_ciphertext - end + multi_region_ciphertext.presence || single_region_ciphertext end end diff --git a/app/services/idv/steps/in_person/state_id_step.rb b/app/services/idv/steps/in_person/state_id_step.rb index f87327898c1..872387abe32 100644 --- a/app/services/idv/steps/in_person/state_id_step.rb +++ b/app/services/idv/steps/in_person/state_id_step.rb @@ -104,7 +104,10 @@ def flow_params end def form - @form ||= Idv::StateIdForm.new(current_user) + @form ||= Idv::StateIdForm.new( + current_user, + capture_secondary_id_enabled: capture_secondary_id_enabled?, + ) end def form_submit diff --git a/app/validators/idv/form_state_id_validator.rb b/app/validators/idv/form_state_id_validator.rb index 6e409bd9916..cb993bae471 100644 --- a/app/validators/idv/form_state_id_validator.rb +++ b/app/validators/idv/form_state_id_validator.rb @@ -11,6 +11,11 @@ module FormStateIdValidator :state_id_number, presence: true + validates :identity_doc_address1, + :identity_doc_city, + presence: true, + if: :capture_secondary_id_enabled? + validates_with UspsInPersonProofing::TransliterableValidator, fields: [:first_name, :last_name, :identity_doc_city], reject_chars: /[^A-Za-z\-' ]/, diff --git a/app/views/layouts/user_mailer.html.erb b/app/views/layouts/user_mailer.html.erb index a4005ae384b..24a1b1d6aff 100644 --- a/app/views/layouts/user_mailer.html.erb +++ b/app/views/layouts/user_mailer.html.erb @@ -73,8 +73,7 @@ <% unless @hide_title %> -

    <%= @header || message.subject %> -

    +

    <%= @header || message.subject %>

    <% end %> <%= yield %> diff --git a/app/views/two_factor_authentication/otp_verification/show.html.erb b/app/views/two_factor_authentication/otp_verification/show.html.erb index fed58b72d88..bc98b579a25 100644 --- a/app/views/two_factor_authentication/otp_verification/show.html.erb +++ b/app/views/two_factor_authentication/otp_verification/show.html.erb @@ -39,7 +39,7 @@ checked: @presenter.remember_device_box_checked?, }, ) %> - <%= f.submit t('forms.buttons.submit.default'), class: 'display-block margin-y-5' %> + <%= f.submit t('forms.buttons.submit.default'), class: 'display-block margin-top-5 margin-bottom-2' %> <%= hidden_field_tag 'otp_make_default_number', @presenter.otp_make_default_number %> <%= render ButtonComponent.new( diff --git a/app/views/user_mailer/in_person_verified.html.erb b/app/views/user_mailer/in_person_verified.html.erb index f5df6949ede..e1378c107f5 100644 --- a/app/views/user_mailer/in_person_verified.html.erb +++ b/app/views/user_mailer/in_person_verified.html.erb @@ -5,7 +5,7 @@ alt: '', class: 'float-center padding-bottom-4', ) %> -

    <%= message.subject %>

    +

    <%= message.subject %>

    <%= t('user_mailer.in_person_verified.greeting') %>
    <%= t( diff --git a/app/views/user_mailer/shared/_in_person_ready_to_verify.html.erb b/app/views/user_mailer/shared/_in_person_ready_to_verify.html.erb index 1a2431f284c..ae55ab5aa3b 100644 --- a/app/views/user_mailer/shared/_in_person_ready_to_verify.html.erb +++ b/app/views/user_mailer/shared/_in_person_ready_to_verify.html.erb @@ -9,9 +9,9 @@

    -

    +

    <%= t('in_person_proofing.headings.barcode') %> -

    + <% end %>
    <%= render 'idv/shared/mini_logo', filename: 'logo.png' %> diff --git a/app/views/users/phones/add.html.erb b/app/views/users/phones/add.html.erb deleted file mode 100644 index b91627fabe5..00000000000 --- a/app/views/users/phones/add.html.erb +++ /dev/null @@ -1,51 +0,0 @@ -<%= title t('titles.add_info.phone') %> - -<%= render(VendorOutageAlertComponent.new(vendors: [:sms, :voice])) %> - -<%= render PageHeadingComponent.new.with_content(t('headings.add_info.phone')) %> - -

    - <%= t('two_factor_authentication.phone_info') %> -

    - -<%= simple_form_for( - @new_phone_form, - method: :post, - html: { autocomplete: 'off' }, - url: add_phone_path, - ) do |f| %> - - <%= render PhoneInputComponent.new( - form: f, - confirmed_phone: false, - required: true, - captcha_exempt_countries: FeatureManagement.phone_recaptcha_enabled? ? PhoneRecaptchaValidator.exempt_countries : nil, - class: 'margin-bottom-4', - ) %> - - <%= render 'users/shared/otp_delivery_preference_selection', - form_obj: @new_phone_form %> - <% if TwoFactorAuthentication::PhonePolicy.new(current_user).enabled? %> - <%= render 'users/shared/otp_make_default_number', - form_obj: @new_phone_form %> - <% end %> - <%= render CaptchaSubmitButtonComponent.new( - form: f, - action: PhoneRecaptchaValidator::RECAPTCHA_ACTION, - ).with_content(t('forms.buttons.send_one_time_code')) %> -<% end %> - -

    - <%= t('two_factor_authentication.phone_fee_disclosure') %> - <% if IdentityConfig.store.voip_block %> - <%= t('two_factor_authentication.two_factor_choice_options.phone_info_no_voip') %> - <% end %> -

    - -<%= render 'shared/cancel', link: account_path %> - - -<% if FeatureManagement.phone_recaptcha_enabled? %> - <%= render 'shared/recaptcha_disclosure' %> -<% end %> - diff --git a/config/application.yml.default b/config/application.yml.default index d95b9ab29a1..63d9d0941d4 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -51,7 +51,6 @@ aws_kms_key_id: alias/login-dot-gov-test-keymaker aws_kms_client_contextless_pool_size: 5 aws_kms_client_multi_pool_size: 5 aws_kms_multi_region_key_id: alias/login-dot-gov-keymaker-multi-region -aws_kms_multi_region_read_enabled: false aws_kms_session_key_id: alias/login-dot-gov-test-keymaker aws_logo_bucket: '' aws_region: 'us-west-2' diff --git a/config/locales/devise/en.yml b/config/locales/devise/en.yml index 905812cf78b..8aaae21b502 100644 --- a/config/locales/devise/en.yml +++ b/config/locales/devise/en.yml @@ -45,9 +45,6 @@ en: registrations: close_window: You can close this window if you’re done. destroyed: Your account has been successfully deleted. - phone_update_needs_confirmation: Your request to update your phone configuration - was processed, but we need to confirm your new number before you can use - it. Please follow the instructions below. start: accordion: You will also need bullet_1_html: An email address. diff --git a/config/locales/devise/es.yml b/config/locales/devise/es.yml index 8d75ecdfb40..b12b9758c5c 100644 --- a/config/locales/devise/es.yml +++ b/config/locales/devise/es.yml @@ -49,10 +49,6 @@ es: registrations: close_window: Puedes cerrar esta ventana si terminaste. destroyed: Su cuenta se ha eliminado exitosamente. - phone_update_needs_confirmation: Su solicitud para actualizar su número de - teléfono fue procesada, pero necesitamos confirmar primero su número - nuevo. Por favor, siga las siguientes instrucciones. Si no confirma su - nuevo número, seguiremos usando su número de teléfono antiguo. start: accordion: También necesitarás bullet_1_html: Una dirección de correo electrónico. diff --git a/config/locales/devise/fr.yml b/config/locales/devise/fr.yml index c8d7d9daaef..1fccfa56f8c 100644 --- a/config/locales/devise/fr.yml +++ b/config/locales/devise/fr.yml @@ -54,11 +54,6 @@ fr: registrations: close_window: Vous pouvez fermer cette fenêtre si vous avez terminé. destroyed: Votre compte a bien été supprimé. - phone_update_needs_confirmation: Votre demande de mise à jour de votre numéro de - téléphone a été traitée, mais nous devons d’abord confirmer votre - nouveau numéro. Veuillez suivre les instructions ci-dessous. Si vous ne - confirmez pas votre nouveau numéro, nous continuerons d’utiliser votre - ancien numéro de téléphone. start: accordion: Vous aurez aussi besoin bullet_1_html: Une adresse e-mail. diff --git a/config/locales/doc_auth/en.yml b/config/locales/doc_auth/en.yml index 3385fe7c07c..896682efd6b 100644 --- a/config/locales/doc_auth/en.yml +++ b/config/locales/doc_auth/en.yml @@ -188,6 +188,7 @@ en: privacy: '%{app_name} is a secure, government website that adheres to the highest standards in data protection. We use your data to verify your identity.' + review_examples_of_photos: Review examples of how to take clear photos of your ID. 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. diff --git a/config/locales/doc_auth/es.yml b/config/locales/doc_auth/es.yml index 1f1f3407c73..c72483ac0ea 100644 --- a/config/locales/doc_auth/es.yml +++ b/config/locales/doc_auth/es.yml @@ -220,6 +220,7 @@ es: privacy: '%{app_name} es un sitio web gubernamental seguro que cumple con las normas más estrictas de protección de datos. Utilizamos sus datos para verificar su identidad.' + review_examples_of_photos: Revisa ejemplos de cómo hacer fotos nítidas de tu documento de identidad. 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. diff --git a/config/locales/doc_auth/fr.yml b/config/locales/doc_auth/fr.yml index 47fb28b8d34..bbc982b19ea 100644 --- a/config/locales/doc_auth/fr.yml +++ b/config/locales/doc_auth/fr.yml @@ -228,6 +228,7 @@ fr: privacy: '%{app_name} est un site gouvernemental sécurisé qui respecte les normes les plus strictes en matière de protection des données. Nous utilisons vos données pour vérifier votre identité.' + review_examples_of_photos: Examinez des exemples de photos claires de votre pièce d’identité. 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. diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml index d331b10ef3d..2d0af4d1d7c 100644 --- a/config/locales/idv/en.yml +++ b/config/locales/idv/en.yml @@ -292,14 +292,14 @@ en: need_assistance: 'Need immediate assistance? Here’s how to get help:' options: contact_support: Contact %{app_name} Support - doc_capture_tips: More tips for adding photos of your ID + doc_capture_tips: Tips for taking clear photos of your ID get_help_at_sp: Get help at %{sp_name} learn_more_address_verification_options: Learn more about verifying by phone or mail learn_more_verify_by_mail: Learn more about verifying your address by mail learn_more_verify_by_phone: Learn more about what phone number to use learn_more_verify_by_phone_in_person: Learn more about verifying your phone number learn_more_verify_in_person: Learn more about verifying in person - supported_documents: See a list of accepted state‑issued IDs + supported_documents: Learn more about accepted IDs verify_by_mail: Verify your address by mail instead unavailable: exit_button: 'Exit %{app_name}' diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml index 6056fcee9f2..7dfebd40c00 100644 --- a/config/locales/idv/es.yml +++ b/config/locales/idv/es.yml @@ -304,7 +304,7 @@ es: need_assistance: '¿Necesita ayuda inmediata? Así es como puede obtener ayuda:' options: contact_support: Póngase en contacto con el servicio de asistencia de %{app_name} - doc_capture_tips: Más consejos para agregar fotos de su identificación + doc_capture_tips: Sugerencias para obtener fotos nítidas de tu documento de identidad get_help_at_sp: Obtenga ayuda en %{sp_name} learn_more_address_verification_options: Obtenga más información sobre la verificación por teléfono o por correo learn_more_verify_by_mail: Obtenga más información sobre la verificación de su @@ -312,8 +312,7 @@ es: learn_more_verify_by_phone: Más información sobre qué número de teléfono usar learn_more_verify_by_phone_in_person: Más información sobre cómo verificar su número de teléfono learn_more_verify_in_person: Más información sobre la verificación en persona - supported_documents: Vea la lista de documentos de identidad emitidos por el - estado que son aceptados + supported_documents: Más información sobre los documentos de identidad aceptados verify_by_mail: Verifique su dirección por correo unavailable: exit_button: 'Salir de %{app_name}' diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml index 5de4838c0e0..cdb49352b88 100644 --- a/config/locales/idv/fr.yml +++ b/config/locales/idv/fr.yml @@ -324,14 +324,14 @@ fr: obtenir de l’aide:' options: contact_support: Contacter le service d’assistance de %{app_name} - doc_capture_tips: Plus de conseils pour ajouter des photos de votre carte d’identité + doc_capture_tips: Conseils pour prendre des photos claires de votre pièce d’identité get_help_at_sp: Demandez de l’aide à %{sp_name} learn_more_address_verification_options: En savoir plus sur la vérification par téléphone ou par courrier learn_more_verify_by_mail: En savoir plus sur la vérification de votre adresse par courrier learn_more_verify_by_phone: Apprenez-en plus sur quel numéro de téléphone utiliser learn_more_verify_by_phone_in_person: En savoir plus sur la vérification de votre numéro de téléphone learn_more_verify_in_person: En savoir plus sur la vérification en personne - supported_documents: Voir la liste des pièces d’identité acceptées et délivrées par l’État + supported_documents: En savoir plus sur les pièces d’identité acceptées verify_by_mail: Vérifiez plutôt votre adresse par courrier unavailable: exit_button: 'Quitter %{app_name}' diff --git a/config/routes.rb b/config/routes.rb index a0f8f899ecf..ad4ff4c4ed3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -237,8 +237,6 @@ get '/manage/email/confirm_delete/:id' => 'users/emails#confirm_delete', as: :manage_email_confirm_delete - get '/add/phone' => 'users/phones#add' - post '/add/phone' => 'users/phones#create' get '/manage/phone/:id' => 'users/edit_phone#edit', as: :manage_phone match '/manage/phone/:id' => 'users/edit_phone#update', via: %i[patch put] delete '/manage/phone/:id' => 'users/edit_phone#destroy' diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 79645c6d376..ab4bfce0cc9 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -138,7 +138,6 @@ def self.build_store(config_map) config.add(:aws_kms_client_multi_pool_size, type: :integer) config.add(:aws_kms_key_id, type: :string) config.add(:aws_kms_multi_region_key_id, type: :string) - config.add(:aws_kms_multi_region_read_enabled, type: :boolean) config.add(:aws_kms_session_key_id, type: :string) config.add(:aws_logo_bucket, type: :string) config.add(:aws_region, type: :string) diff --git a/lib/tasks/review_profile.rake b/lib/tasks/review_profile.rake index 581e43cb611..a5d4dd00cc8 100644 --- a/lib/tasks/review_profile.rake +++ b/lib/tasks/review_profile.rake @@ -5,6 +5,11 @@ namespace :users do namespace :review do desc 'Pass a user that has a pending review' task pass: :environment do |_task, args| + user = nil + profile_fraud_review_pending_at = nil + success = false + exception = nil + STDOUT.sync = true STDOUT.print 'Enter the name of the investigator: ' investigator_name = STDIN.gets.chomp @@ -18,17 +23,18 @@ namespace :users do user = User.find_by(uuid: user_uuid) if !user - STDOUT.puts 'Error: Could not find user with that UUID' + error = 'Error: Could not find user with that UUID' next end if !user.fraud_review_pending? - STDOUT.puts 'Error: User does not have a pending fraud review' + error = 'Error: User does not have a pending fraud review' next end if FraudReviewChecker.new(user).fraud_review_eligible? profile = user.fraud_review_pending_profile + profile_fraud_review_pending_at = profile.fraud_review_pending_at profile.activate_after_passing_review if profile.active? @@ -41,17 +47,42 @@ namespace :users do sp_name: nil, ) + success = true STDOUT.puts "User's profile has been activated and the user has been emailed." else - STDOUT.puts "There was an error activating the user's profile. Please try again" + error = "There was an error activating the user's profile. Please try again" end else - STDOUT.puts 'User is past the 30 day review eligibility' + error = 'User is past the 30 day review eligibility' + end + rescue StandardError => e + success = false + exception = e + raise e + ensure + analytics_error_hash = nil + if error.present? + STDOUT.puts error + analytics_error_hash = { message: error } end + + Analytics.new( + user: user || AnonymousUser.new, request: nil, session: {}, sp: nil, + ).fraud_review_passed( + success:, + errors: analytics_error_hash, + exception: exception&.inspect, + profile_fraud_review_pending_at: profile_fraud_review_pending_at, + ) end desc 'Reject a user that has a pending review' task reject: :environment do |_task, args| + error = nil + user = nil + profile_fraud_review_pending_at = nil + success = false + STDOUT.sync = true STDOUT.print 'Enter the name of the investigator: ' investigator_name = STDIN.gets.chomp @@ -65,23 +96,44 @@ namespace :users do user = User.find_by(uuid: user_uuid) if !user - STDOUT.puts 'Error: Could not find user with that UUID' + error = 'Error: Could not find user with that UUID' next end if !user.fraud_review_pending? - STDOUT.puts 'Error: User does not have a pending fraud review' + error = 'Error: User does not have a pending fraud review' next end if FraudReviewChecker.new(user).fraud_review_eligible? profile = user.fraud_review_pending_profile - + profile_fraud_review_pending_at = profile.fraud_review_pending_at profile.reject_for_fraud(notify_user: true) + + success = true STDOUT.puts "User's profile has been deactivated due to fraud rejection." else - STDOUT.puts 'User is past the 30 day review eligibility' + error = 'User is past the 30 day review eligibility' end + rescue StandardError => e + success = false + exception = e + raise e + ensure + analytics_error_hash = nil + if error.present? + STDOUT.puts error + analytics_error_hash = { message: error } + end + + Analytics.new( + user: user || AnonymousUser.new, request: nil, session: {}, sp: nil, + ).fraud_review_rejected( + success:, + errors: analytics_error_hash, + exception: exception&.inspect, + profile_fraud_review_pending_at: profile_fraud_review_pending_at, + ) end end end diff --git a/spec/controllers/concerns/rate_limit_concern_spec.rb b/spec/controllers/concerns/rate_limit_concern_spec.rb index 25bc2ee6482..f8e3b8b29b7 100644 --- a/spec/controllers/concerns/rate_limit_concern_spec.rb +++ b/spec/controllers/concerns/rate_limit_concern_spec.rb @@ -89,16 +89,6 @@ def update ).increment_to_limited! end - context 'ssn is in flow session' do - it 'redirects to proof_ssn rate limited error page' do - flow_session = { pii_from_doc: { ssn: ssn } } - allow(subject).to receive(:flow_session).and_return(flow_session) - get :show - - expect(response).to redirect_to idv_session_errors_ssn_failure_url - end - end - context 'ssn is in idv_session' do it 'redirects to proof_ssn rate limited error page' do subject.idv_session.ssn = ssn diff --git a/spec/controllers/idv/in_person/ssn_controller_spec.rb b/spec/controllers/idv/in_person/ssn_controller_spec.rb index a5ad306f80b..1bf95e7713f 100644 --- a/spec/controllers/idv/in_person/ssn_controller_spec.rb +++ b/spec/controllers/idv/in_person/ssn_controller_spec.rb @@ -81,31 +81,6 @@ expect { get :show }.to change { subject.idv_session.threatmetrix_session_id }.from(nil) end - context 'with an ssn in flow_session' do - let(:referer) { idv_in_person_step_url(step: :address) } - before do - flow_session[:pii_from_user][:ssn] = ssn - request.env['HTTP_REFERER'] = referer - end - - context 'referer is not verify_info' do - it 'redirects to verify_info' do - get :show - - expect(response).to redirect_to(idv_in_person_verify_info_url) - end - end - - context 'referer is verify_info' do - let(:referer) { idv_in_person_verify_info_url } - it 'does not redirect' do - get :show - - expect(response).to render_template :show - end - end - end - context 'with an ssn in idv_session' do let(:referer) { idv_in_person_step_url(step: :address) } before do @@ -163,12 +138,6 @@ put :update, params: params end - it 'merges ssn into pii session value' do - put :update, params: params - - expect(flow_session[:pii_from_user][:ssn]).to eq(ssn) - end - it 'adds ssn to idv_session' do put :update, params: params @@ -190,7 +159,7 @@ end it 'does not change threatmetrix_session_id when updating ssn' do - flow_session[:pii_from_user][:ssn] = ssn + subject.idv_session.ssn = ssn put :update, params: params session_id = subject.idv_session.threatmetrix_session_id subject.threatmetrix_view_variables 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 3cd9a7773dd..eeaca608221 100644 --- a/spec/controllers/idv/in_person/verify_info_controller_spec.rb +++ b/spec/controllers/idv/in_person/verify_info_controller_spec.rb @@ -20,6 +20,7 @@ before do allow(subject).to receive(:flow_session).and_return(flow_session) stub_sign_in(user) + subject.idv_session.ssn = Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID[:ssn] allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end diff --git a/spec/controllers/idv/session_errors_controller_spec.rb b/spec/controllers/idv/session_errors_controller_spec.rb index 82c9da477c2..8fd17a30332 100644 --- a/spec/controllers/idv/session_errors_controller_spec.rb +++ b/spec/controllers/idv/session_errors_controller_spec.rb @@ -285,7 +285,7 @@ rate_limit_type: :proof_ssn, target: Pii::Fingerprinter.fingerprint(ssn), ).increment_to_limited! - controller.user_session['idv/doc_auth'] = { pii_from_doc: { ssn: ssn } } + allow(idv_session).to receive(:ssn).and_return(ssn) end it 'assigns expiration time' do @@ -304,30 +304,6 @@ ) get action end - - context 'with ssn in idv_session' do - before do - controller.user_session['idv/doc_auth'] = {} - allow(idv_session).to receive(:ssn).and_return(ssn) - end - - it 'assigns expiration time' do - get action - - expect(assigns(:expires_at)).not_to eq(Time.zone.now) - end - - it 'logs an event with attempts remaining' do - expect(@analytics).to receive(:track_event).with( - 'IdV: session error visited', - hash_including( - type: 'ssn_failure', - attempts_remaining: 0, - ), - ) - get action - end - end end end diff --git a/spec/controllers/idv/ssn_controller_spec.rb b/spec/controllers/idv/ssn_controller_spec.rb index e64e04f9f0c..d16a33c2bcd 100644 --- a/spec/controllers/idv/ssn_controller_spec.rb +++ b/spec/controllers/idv/ssn_controller_spec.rb @@ -90,31 +90,6 @@ expect { get :show }.to change { subject.idv_session.threatmetrix_session_id }.from(nil) end - context 'with an ssn in flow_session' do - let(:referer) { idv_document_capture_url } - before do - flow_session[:pii_from_doc][:ssn] = ssn - request.env['HTTP_REFERER'] = referer - end - - context 'referer is not verify_info' do - it 'redirects to verify_info' do - get :show - - expect(response).to redirect_to(idv_verify_info_url) - end - end - - context 'referer is verify_info' do - let(:referer) { idv_verify_info_url } - it 'does not redirect' do - get :show - - expect(response).to render_template :show - end - end - end - context 'with an ssn in idv_session' do let(:referer) { idv_document_capture_url } before do @@ -178,12 +153,6 @@ }.merge(ab_test_args) end - it 'merges ssn into pii session value' do - put :update, params: params - - expect(flow_session[:pii_from_doc][:ssn]).to eq(ssn) - end - it 'updates idv_session.ssn' do expect { put :update, params: params }.to change { subject.idv_session.ssn }. from(nil).to(ssn) @@ -199,7 +168,7 @@ end it 'redirects to the verify info controller if a user is updating their SSN' do - flow_session[:pii_from_doc][:ssn] = ssn + subject.idv_session.ssn = ssn flow_session[:pii_from_doc][:state] = 'PR' put :update, params: params @@ -226,7 +195,7 @@ end it 'does not change threatmetrix_session_id when updating ssn' do - flow_session[:pii_from_doc][:ssn] = ssn + subject.idv_session.ssn = ssn put :update, params: params session_id = subject.idv_session.threatmetrix_session_id subject.threatmetrix_view_variables diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb index 8e52b8a21a1..97cd1a4f177 100644 --- a/spec/controllers/idv/verify_info_controller_spec.rb +++ b/spec/controllers/idv/verify_info_controller_spec.rb @@ -4,7 +4,7 @@ include IdvHelper let(:flow_session) do - { pii_from_doc: Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.dup } + { pii_from_doc: Idp::Constants::MOCK_IDV_APPLICANT.dup } end let(:user) { create(:user) } @@ -26,6 +26,7 @@ stub_attempts_tracker stub_idv_steps_before_verify_step(user) subject.idv_session.flow_path = 'standard' + subject.idv_session.ssn = Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN[:ssn] subject.user_session['idv/doc_auth'] = flow_session allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end @@ -64,7 +65,7 @@ }.merge(ab_test_args) end - it 'renders the show template (with ssn in flow_session)' do + it 'renders the show template' do get :show expect(response).to render_template :show @@ -114,7 +115,6 @@ end it 'redirects to ssn controller when ssn info is missing' do - flow_session[:pii_from_doc][:ssn] = nil subject.idv_session.ssn = nil get :show @@ -122,15 +122,6 @@ expect(response).to redirect_to(idv_ssn_url) end - it 'renders show when ssn is in idv_session' do - subject.idv_session.ssn = flow_session[:pii_from_doc][:ssn] - flow_session[:pii_from_doc][:ssn] = nil - - get :show - - expect(response).to render_template :show - end - context 'when the user is ssn rate limited' do before do RateLimiter.new( @@ -141,16 +132,7 @@ ).increment_to_limited! end - it 'redirects to ssn failure url with ssn in flow session' do - get :show - - expect(response).to redirect_to idv_session_errors_ssn_failure_url - end - - it 'redirects to ssn failure url with ssn in idv_session' do - subject.idv_session.ssn = flow_session[:pii_from_doc][:ssn] - flow_session[:pii_from_doc][:ssn] = nil - + it 'redirects to ssn failure url' do get :show expect(response).to redirect_to idv_session_errors_ssn_failure_url diff --git a/spec/controllers/users/phones_controller_spec.rb b/spec/controllers/users/phones_controller_spec.rb deleted file mode 100644 index 91af6cdbbe2..00000000000 --- a/spec/controllers/users/phones_controller_spec.rb +++ /dev/null @@ -1,241 +0,0 @@ -require 'rails_helper' - -RSpec.describe Users::PhonesController do - let(:user) { create(:user, :fully_registered, with: { phone: '+1 (202) 555-1234' }) } - before do - stub_sign_in(user) - - stub_analytics - allow(@analytics).to receive(:track_event) - end - - context 'user adds phone' do - it 'gives the user a form to enter a new phone number' do - get :add - - expect(response).to render_template(:add) - expect(response.request.flash[:alert]).to be_nil - end - - it 'tracks analytics' do - expect(@analytics).to receive(:track_event). - with('Phone Setup Visited') - get :add - end - end - - context 'user exceeds phone number limit' do - before do - user.phone_configurations.create(encrypted_phone: '4105555551') - user.phone_configurations.create(encrypted_phone: '4105555552') - user.phone_configurations.create(encrypted_phone: '4105555553') - user.phone_configurations.create(encrypted_phone: '4105555554') - end - - it 'displays error if phone number exceeds limit' do - controller.request.headers.merge({ HTTP_REFERER: account_url }) - - get :add - expect(response).to redirect_to(account_url(anchor: 'phones')) - expect(response.request.flash[:phone_error]).to_not be_nil - end - - it 'renders the #phone anchor when it exceeds limit' do - controller.request.headers.merge({ HTTP_REFERER: account_url }) - - get :add - expect(response.location).to include('#phone') - end - - it 'it redirects to two factor auth url if the referer was two factor auth' do - controller.request.headers.merge({ HTTP_REFERER: account_two_factor_authentication_url }) - - get :add - expect(response).to redirect_to(account_two_factor_authentication_url(anchor: 'phones')) - end - - it 'defaults to account url if the url is anything but two factor auth url' do - controller.request.headers.merge({ HTTP_REFERER: add_phone_url }) - - get :add - expect(response).to redirect_to(account_url(anchor: 'phones')) - end - end - - context 'phone vendor outage' do - before do - allow_any_instance_of(OutageStatus).to receive(:all_phone_vendor_outage?).and_return(true) - end - - it 'redirects to outage page' do - get :add - - expect(response).to redirect_to vendor_outage_path(from: :users_phones) - end - end - - describe 'recaptcha csp' do - before { stub_sign_in } - - it 'does not allow recaptcha in the csp' do - expect(subject).not_to receive(:allow_csp_recaptcha_src) - - get :add - end - - context 'recaptcha enabled' do - before do - allow(FeatureManagement).to receive(:phone_recaptcha_enabled?).and_return(true) - end - - it 'allows recaptcha in the csp' do - expect(subject).to receive(:allow_csp_recaptcha_src) - - get :add - end - end - end - - describe '#create' do - context 'with recoverable recaptcha error' do - it 'renders spam protection template' do - stub_sign_in - - allow(controller).to receive(:recoverable_recaptcha_error?) do |result| - result.is_a?(FormResponse) - end - - post :create, params: { new_phone_form: { international_code: 'CA' } } - - expect(response).to render_template('users/phone_setup/spam_protection') - end - end - - context 'invalid number' do - it 'tracks an event when the number is invalid' do - sign_in(user) - - stub_analytics - result = { - success: false, - errors: { - phone: [ - t('errors.messages.improbable_phone'), - t( - 'two_factor_authentication.otp_delivery_preference.voice_unsupported', - location: '', - ), - ], - }, - error_details: { - phone: [ - :improbable_phone, - t( - 'two_factor_authentication.otp_delivery_preference.voice_unsupported', - location: '', - ), - ], - }, - otp_delivery_preference: 'sms', - area_code: nil, - carrier: 'Test Mobile Carrier', - country_code: nil, - phone_type: :mobile, - types: [], - pii_like_keypaths: [[:errors, :phone], [:error_details, :phone]], - } - - expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: phone setup', result) - - post :create, params: { - new_phone_form: { - phone: '703-555-010', - international_code: 'US', - }, - } - - expect(response).to render_template(:add) - end - end - - context 'with SMS' do - it 'prompts to confirm the number' do - sign_in(user) - - stub_analytics - - result = { - success: true, - errors: {}, - otp_delivery_preference: 'sms', - area_code: '703', - carrier: 'Test Mobile Carrier', - country_code: 'US', - phone_type: :mobile, - types: [:fixed_or_mobile], - pii_like_keypaths: [[:errors, :phone], [:error_details, :phone]], - } - - expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: phone setup', result) - - post( - :create, - params: { - new_phone_form: { phone: '703-555-0100', - international_code: 'US' }, - }, - ) - - expect(response).to redirect_to( - otp_send_path( - otp_delivery_selection_form: { otp_delivery_preference: 'sms', - otp_make_default_number: false }, - ), - ) - - expect(subject.user_session[:context]).to eq 'confirmation' - end - end - - context 'without selection' do - it 'prompts to confirm via SMS by default' do - sign_in(user) - - stub_analytics - result = { - success: true, - errors: {}, - otp_delivery_preference: 'sms', - area_code: '703', - carrier: 'Test Mobile Carrier', - country_code: 'US', - phone_type: :mobile, - types: [:fixed_or_mobile], - pii_like_keypaths: [[:errors, :phone], [:error_details, :phone]], - } - - expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: phone setup', result) - - patch( - :create, - params: { - new_phone_form: { phone: '703-555-0100', - international_code: 'US' }, - }, - ) - - expect(response).to redirect_to( - otp_send_path( - otp_delivery_selection_form: { otp_delivery_preference: 'sms', - otp_make_default_number: false }, - ), - ) - - expect(subject.user_session[:context]).to eq 'confirmation' - end - end - end -end diff --git a/spec/controllers/users/sessions_controller_spec.rb b/spec/controllers/users/sessions_controller_spec.rb index 13fed2ef90d..21dc0bff8fe 100644 --- a/spec/controllers/users/sessions_controller_spec.rb +++ b/spec/controllers/users/sessions_controller_spec.rb @@ -238,6 +238,9 @@ profile = create(:profile, :active, :verified, user: user, pii: { ssn: '1234' }) profile.update!( encrypted_pii: { encrypted_data: Base64.strict_encode64('nonsense') }.to_json, + encrypted_pii_multi_region: { + encrypted_data: Base64.strict_encode64('nonsense'), + }.to_json, ) stub_analytics diff --git a/spec/features/idv/doc_auth/document_capture_spec.rb b/spec/features/idv/doc_auth/document_capture_spec.rb index 0a85d2e485a..1b55ebcddf7 100644 --- a/spec/features/idv/doc_auth/document_capture_spec.rb +++ b/spec/features/idv/doc_auth/document_capture_spec.rb @@ -23,22 +23,6 @@ complete_doc_auth_steps_before_document_capture_step end - it 'logs return to sp link click' do - new_window = window_opened_by do - click_on t('idv.troubleshooting.options.get_help_at_sp', sp_name: sp_name) - end - - within_window new_window do - expect(fake_analytics).to have_logged_event( - 'Return to SP: Failed to proof', - flow: nil, - location: 'document_capture', - redirect_url: instance_of(String), - step: 'document_capture', - ) - end - end - context 'wrong doc type is uploaded', allow_browser_log: true do it 'try again and page show doc type inline error message' do attach_images( diff --git a/spec/features/idv/in_person_spec.rb b/spec/features/idv/in_person_spec.rb index 99a3357d6c5..14fe80b0713 100644 --- a/spec/features/idv/in_person_spec.rb +++ b/spec/features/idv/in_person_spec.rb @@ -456,7 +456,7 @@ complete_location_step expect(page).to have_current_path(idv_in_person_step_path(step: :state_id), wait: 10) - fill_out_state_id_form_ok(double_address_verification: double_address_verification) + fill_out_state_id_form_ok(capture_secondary_id_enabled: capture_secondary_id_enabled) fill_in t('in_person_proofing.form.state_id.first_name'), with: 'T0mmy "Lee"' fill_in t('in_person_proofing.form.state_id.last_name'), with: 'Джейкоб' fill_in t('in_person_proofing.form.state_id.address1'), with: '#1 $treet' @@ -531,7 +531,7 @@ expect(page).to have_content(I18n.t('in_person_proofing.form.state_id.address2_hint')) # change state selection - fill_out_state_id_form_ok(double_address_verification: true) + fill_out_state_id_form_ok(capture_secondary_id_enabled: true) expect(page).not_to have_content(I18n.t('in_person_proofing.form.state_id.address1_hint')) expect(page).not_to have_content(I18n.t('in_person_proofing.form.state_id.address2_hint')) @@ -693,7 +693,9 @@ complete_location_step(user) end it 'successfully proceeds through the flow' do - complete_state_id_step(user, same_address_as_id: false, double_address_verification: true) + complete_state_id_step( + user, same_address_as_id: false, capture_secondary_id_enabled: true + ) complete_address_step(user, double_address_verification: true) @@ -732,7 +734,9 @@ end it 'skips the address page' do - complete_state_id_step(user, same_address_as_id: true, double_address_verification: true) + complete_state_id_step( + user, same_address_as_id: true, capture_secondary_id_enabled: true + ) # skip address step complete_ssn_step(user) # Ensure the page submitted successfully @@ -740,7 +744,9 @@ end it 'can redo the address page form even if that page is skipped' do - complete_state_id_step(user, same_address_as_id: true, double_address_verification: true) + complete_state_id_step( + user, same_address_as_id: true, capture_secondary_id_enabled: true + ) # skip address step complete_ssn_step(user) # click update address button on the verify page @@ -753,7 +759,9 @@ end it 'allows user to update their residential address as different from their state id' do - complete_state_id_step(user, same_address_as_id: true, double_address_verification: true) + complete_state_id_step( + user, same_address_as_id: true, capture_secondary_id_enabled: true + ) complete_ssn_step(user) # click "update residential address" @@ -797,7 +805,9 @@ it 'does not update their previous selection of "Yes, I live at the address on my state-issued ID"' do - complete_state_id_step(user, same_address_as_id: true, double_address_verification: true) + complete_state_id_step( + user, same_address_as_id: true, capture_secondary_id_enabled: true + ) # skip address step complete_ssn_step(user) # expect to be on verify page @@ -829,7 +839,9 @@ end it 'does not update their previous selection of "No, I live at a different address"' do - complete_state_id_step(user, same_address_as_id: false, double_address_verification: true) + complete_state_id_step( + user, same_address_as_id: false, capture_secondary_id_enabled: true + ) # expect to be on address page expect(page).to have_content(t('in_person_proofing.headings.address')) # complete address step @@ -863,7 +875,9 @@ end it 'updates their previous selection from "Yes" TO "No, I live at a different address"' do - complete_state_id_step(user, same_address_as_id: true, double_address_verification: true) + complete_state_id_step( + user, same_address_as_id: true, capture_secondary_id_enabled: true + ) # skip address step complete_ssn_step(user) # click update state ID button on the verify page @@ -898,7 +912,9 @@ it 'updates their previous selection from "No" TO "Yes, I live at the address on my state-issued ID"' do - complete_state_id_step(user, same_address_as_id: false, double_address_verification: true) + complete_state_id_step( + user, same_address_as_id: false, capture_secondary_id_enabled: true + ) # expect to be on address page expect(page).to have_content(t('in_person_proofing.headings.address')) # complete address step diff --git a/spec/features/idv/steps/in_person/ssn_spec.rb b/spec/features/idv/steps/in_person/ssn_spec.rb index ef7e6a5fbd3..011f4ed029e 100644 --- a/spec/features/idv/steps/in_person/ssn_spec.rb +++ b/spec/features/idv/steps/in_person/ssn_spec.rb @@ -111,7 +111,7 @@ # location page complete_location_step(user) # state ID page - fill_out_state_id_form_ok(double_address_verification: true, same_address_as_id: false) + fill_out_state_id_form_ok(same_address_as_id: false, capture_secondary_id_enabled: true) click_idv_continue fill_out_address_form_ok(double_address_verification: true, same_address_as_id: false) click_idv_continue diff --git a/spec/features/idv/steps/in_person/state_id_step_spec.rb b/spec/features/idv/steps/in_person/state_id_step_spec.rb index d0109843d9c..a265174d37f 100644 --- a/spec/features/idv/steps/in_person/state_id_step_spec.rb +++ b/spec/features/idv/steps/in_person/state_id_step_spec.rb @@ -23,7 +23,7 @@ complete_prepare_step(user) complete_location_step(user) expect(page).to have_current_path(idv_in_person_step_path(step: :state_id), wait: 10) - fill_out_state_id_form_ok(double_address_verification: true, same_address_as_id: true) + fill_out_state_id_form_ok(same_address_as_id: true, capture_secondary_id_enabled: true) # blank out the zip code field fill_in t('in_person_proofing.form.state_id.zipcode'), with: '' # try to enter invalid input into the zip code field diff --git a/spec/features/idv/steps/in_person/verify_info_spec.rb b/spec/features/idv/steps/in_person/verify_info_spec.rb index e0872eef1cc..c4a4be66b56 100644 --- a/spec/features/idv/steps/in_person/verify_info_spec.rb +++ b/spec/features/idv/steps/in_person/verify_info_spec.rb @@ -7,10 +7,14 @@ let(:user) { user_with_2fa } let(:fake_analytics) { FakeAnalytics.new(user: user) } + let(:capture_secondary_id_enabled) { false } + let(:enrollment) { InPersonEnrollment.new(capture_secondary_id_enabled:) } before do allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true) allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics) + allow(user).to receive(:enrollment). + and_return(enrollment) end it 'provides back buttons for address, state ID, and SSN that discard changes', diff --git a/spec/features/legacy_passwords_spec.rb b/spec/features/legacy_passwords_spec.rb index 7396123944e..44751d51310 100644 --- a/spec/features/legacy_passwords_spec.rb +++ b/spec/features/legacy_passwords_spec.rb @@ -5,6 +5,7 @@ user = create(:user, :fully_registered) user.update!( encrypted_password_digest: Encryption::UakPasswordVerifier.digest('legacy password'), + encrypted_password_digest_multi_region: nil, ) expect( @@ -26,6 +27,7 @@ user = create(:user, :fully_registered) user.update!( encrypted_password_digest: Encryption::UakPasswordVerifier.digest('legacy password'), + encrypted_password_digest_multi_region: nil, ) signin(user.email, 'a different password') @@ -40,6 +42,7 @@ user = create(:user, :fully_registered) user.update!( encrypted_recovery_code_digest: Encryption::UakPasswordVerifier.digest('1111 2222 3333 4444'), + encrypted_recovery_code_digest_multi_region: nil, ) expect( @@ -59,6 +62,7 @@ user = create(:user, :fully_registered) user.update!( encrypted_recovery_code_digest: Encryption::UakPasswordVerifier.digest('1111 2222 3333 4444'), + encrypted_recovery_code_digest_multi_region: nil, ) sign_in_user(user) diff --git a/spec/forms/idv/state_id_form_spec.rb b/spec/forms/idv/state_id_form_spec.rb index 7cb107f7db5..a2d371c481d 100644 --- a/spec/forms/idv/state_id_form_spec.rb +++ b/spec/forms/idv/state_id_form_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' RSpec.describe Idv::StateIdForm do - let(:subject) { Idv::StateIdForm.new(pii) } + let(:subject) { Idv::StateIdForm.new(pii, capture_secondary_id_enabled:) } let(:valid_dob) do valid_d = Time.zone.today - IdentityConfig.store.idv_min_age_years.years - 1.day ActionController::Parameters.new( @@ -22,6 +22,7 @@ dob: valid_dob, identity_doc_address1: Faker::Address.street_address, identity_doc_address2: Faker::Address.secondary_address, + identity_doc_city: Faker::Address.city, identity_doc_zipcode: Faker::Address.zip_code, identity_doc_address_state: Faker::Address.state_abbr, same_address_as_id: 'true', @@ -36,6 +37,7 @@ dob: too_young_dob, identity_doc_address1: Faker::Address.street_address, identity_doc_address2: Faker::Address.secondary_address, + identity_doc_city: Faker::Address.city, identity_doc_zipcode: Faker::Address.zip_code, identity_doc_address_state: Faker::Address.state_abbr, same_address_as_id: 'true', @@ -51,6 +53,7 @@ dob: valid_dob, identity_doc_address1: Faker::Address.street_address, identity_doc_address2: Faker::Address.secondary_address, + identity_doc_city: Faker::Address.city, identity_doc_zipcode: Faker::Address.zip_code, identity_doc_address_state: Faker::Address.state_abbr, same_address_as_id: 'true', @@ -59,6 +62,7 @@ } end let(:pii) { nil } + let(:capture_secondary_id_enabled) { true } describe '#submit' do context 'when the form is valid' do it 'returns a successful form response' do diff --git a/spec/javascript/packages/document-capture/components/document-capture-review-issues-spec.jsx b/spec/javascript/packages/document-capture/components/document-capture-review-issues-spec.jsx index ef8f32deccf..f2f8e7a8476 100644 --- a/spec/javascript/packages/document-capture/components/document-capture-review-issues-spec.jsx +++ b/spec/javascript/packages/document-capture/components/document-capture-review-issues-spec.jsx @@ -48,11 +48,6 @@ describe('DocumentCaptureReviewIssues', () => { expect(h1).to.be.ok(); expect(getByText('general error')).to.be.ok(); - const h2 = screen.getByRole('heading', { - name: 'components.troubleshooting_options.default_heading', - level: 2, - }); - expect(h2).to.be.ok(); // tips header expect(getByText('doc_auth.tips.review_issues_id_header_text')).to.be.ok(); @@ -63,18 +58,7 @@ describe('DocumentCaptureReviewIssues', () => { tipListItem.forEach((li, idx) => { expect(li.textContent).to.equals(`doc_auth.tips.review_issues_id_text${idx + 1}`); }); - const troubleShootingList = lists[1]; - - expect(troubleShootingList).to.be.ok(); - const troubleShootingListItem = within(troubleShootingList).getAllByRole('listitem'); - expect(troubleShootingListItem).to.be.ok(); - const hrefLinks = within(troubleShootingList).getAllByRole('link'); - - hrefLinks.forEach((hrefLink) => { - expect(hrefLink.href).to.contains('location=post_submission_review'); - expect(hrefLink.href).to.contains('article='); - }); // front capture input const frontCapture = getByLabelText('doc_auth.headings.document_capture_front'); expect(frontCapture).to.be.ok(); @@ -123,18 +107,6 @@ describe('DocumentCaptureReviewIssues', () => { expect(getByText('doc_auth.errors.doc.wrong_id_type')).to.be.ok(); - const troubleShootingList = getByRole('list'); - - expect(troubleShootingList).to.be.ok(); - - const troubleShootingListItem = within(troubleShootingList).getAllByRole('listitem'); - expect(troubleShootingListItem).to.be.ok(); - const hrefLinks = within(troubleShootingList).getAllByRole('link'); - - hrefLinks.forEach((hrefLink) => { - expect(hrefLink.href).to.contains('location=post_submission_review'); - expect(hrefLink.href).to.contains('article='); - }); // front capture input const frontCapture = getByLabelText('doc_auth.headings.document_capture_front'); expect(frontCapture).to.be.ok(); diff --git a/spec/javascript/packages/document-capture/components/document-capture-spec.jsx b/spec/javascript/packages/document-capture/components/document-capture-spec.jsx index ccbcf60017c..87febba9db3 100644 --- a/spec/javascript/packages/document-capture/components/document-capture-spec.jsx +++ b/spec/javascript/packages/document-capture/components/document-capture-spec.jsx @@ -222,8 +222,9 @@ describe('document-capture/components/document-capture', () => { ); // Make sure that the first focusable element after a tab is what we expect it to be. + // Since the error wasn't related to an unsupported document type, user should see a help center link. await userEvent.tab(); - const firstFocusable = getByLabelText('doc_auth.headings.document_capture_front'); + const firstFocusable = getByText('doc_auth.info.review_examples_of_photos'); expect(document.activeElement).to.equal(firstFocusable); const hasValueSelected = !!getByLabelText('doc_auth.headings.document_capture_front'); 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 88174889e0b..a5c5cb4d2a7 100644 --- a/spec/javascript/packages/document-capture/components/documents-step-spec.jsx +++ b/spec/javascript/packages/document-capture/components/documents-step-spec.jsx @@ -1,11 +1,7 @@ import userEvent from '@testing-library/user-event'; import sinon from 'sinon'; import { t } from '@18f/identity-i18n'; -import { - DeviceContext, - ServiceProviderContextProvider, - UploadContextProvider, -} from '@18f/identity-document-capture'; +import { DeviceContext, UploadContextProvider } from '@18f/identity-document-capture'; import DocumentsStep from '@18f/identity-document-capture/components/documents-step'; import { render } from '../../../support/document-capture'; import { getFixtureFile } from '../../../support/file'; @@ -50,26 +46,6 @@ describe('document-capture/components/documents-step', () => { expect(() => getByText('doc_auth.tips.document_capture_id_text4')).not.to.throw(); }); - it('renders troubleshooting options', () => { - const { getByRole } = render( - - - , - ); - - expect( - getByRole('heading', { name: 'components.troubleshooting_options.default_heading' }), - ).to.be.ok(); - expect( - getByRole('link', { name: 'idv.troubleshooting.options.get_help_at_sp links.new_tab' }).href, - ).to.equal('https://example.com/?step=document_capture&location=document_capture'); - }); - it('renders the hybrid flow warning if the flow is hybrid', () => { const { getByText } = render( diff --git a/spec/javascript/packages/document-capture/components/review-issues-step-spec.jsx b/spec/javascript/packages/document-capture/components/review-issues-step-spec.jsx index 45cb46e341f..5984f8f72d4 100644 --- a/spec/javascript/packages/document-capture/components/review-issues-step-spec.jsx +++ b/spec/javascript/packages/document-capture/components/review-issues-step-spec.jsx @@ -169,28 +169,6 @@ describe('document-capture/components/review-issues-step', () => { }); }); - it('renders troubleshooting options', async () => { - const { getByRole } = render( - - - , - ); - - await userEvent.click(getByRole('button', { name: 'idv.failure.button.warning' })); - - expect( - getByRole('heading', { name: 'components.troubleshooting_options.default_heading' }), - ).to.be.ok(); - expect( - getByRole('link', { name: 'idv.troubleshooting.options.get_help_at_sp links.new_tab' }).href, - ).to.equal('https://example.com/?step=document_capture&location=post_submission_review'); - }); - it('does not render sp help troubleshooting option for errored review', () => { const { queryByRole } = render( diff --git a/spec/javascript/packages/document-capture/components/tip-list-spec.jsx b/spec/javascript/packages/document-capture/components/tip-list-spec.jsx index 5a02d58fdee..2e6990d7b38 100644 --- a/spec/javascript/packages/document-capture/components/tip-list-spec.jsx +++ b/spec/javascript/packages/document-capture/components/tip-list-spec.jsx @@ -3,9 +3,10 @@ import TipList from '@18f/identity-document-capture/components/tip-list'; import { render } from '../../../support/document-capture'; describe('document-capture/components/tip-list', () => { + const title = 'doc_auth.tips.review_issues_id_header_text'; + const items = ['doc_auth.tips.review_issues_id_text1', 'doc_auth.tips.review_issues_id_text2']; + it('renders title and list', () => { - const title = 'doc_auth.tips.review_issues_id_header_text'; - const items = ['doc_auth.tips.review_issues_id_text1', 'doc_auth.tips.review_issues_id_text2']; const { getByRole, getAllByRole, getByText } = render( , ); @@ -17,4 +18,13 @@ describe('document-capture/components/tip-list', () => { 'doc_auth.tips.review_issues_id_text2', ]); }); + + it('formats the title based on titleClassName', () => { + const titleClassName = 'margin-bottom-0 margin-top-2'; + const { getByText } = render( + , + ); + const tipsTitle = getByText('doc_auth.tips.review_issues_id_header_text'); + expect(tipsTitle.className).to.eql(titleClassName); + }); }); diff --git a/spec/javascript/packages/document-capture/components/unknown-error-spec.jsx b/spec/javascript/packages/document-capture/components/unknown-error-spec.jsx index 6d4616ddcf5..19e143cdd10 100644 --- a/spec/javascript/packages/document-capture/components/unknown-error-spec.jsx +++ b/spec/javascript/packages/document-capture/components/unknown-error-spec.jsx @@ -6,91 +6,126 @@ import { within } from '@testing-library/dom'; import { render } from '../../../support/document-capture'; describe('UnknownError', () => { - it('render an empty paragraph when no errors', () => { - const { container } = render( - , - ); - expect(container.querySelector('p')).to.be.ok(); - }); + context('there is no doc type failure', () => { + it('render an empty paragraph when no errors', () => { + const { container } = render( + , + ); + expect(container.querySelector('p')).to.be.ok(); + }); - it('renders error message with errors but not a doc type failure', () => { - const { container } = render( - , - ); - const paragraph = container.querySelector('p'); - expect(within(paragraph).getByText('An unknown error occurred')).to.be.ok(); - }); + context('hasDismissed is true', () => { + it('renders error message with errors and a help center link', () => { + const { container } = render( + , + ); + const paragraph = container.querySelector('p'); + expect(within(paragraph).getByText('An unknown error occurred')).to.be.ok(); + const link = container.querySelector('a'); + expect(link.text).to.eql('doc_auth.info.review_examples_of_photoslinks.new_tab'); + }); + }); - it('renders error message with errors and is a doc type failure', () => { - const { container } = render( - One attempt remaining', - other: 'You have%{count} attempts remaining', + context('hasDismissed is false', () => { + it('renders error message with errors but no link', () => { + const { container, queryByRole } = render( + - - , - ); - const paragraph = container.querySelector('p'); - expect(within(paragraph).getByText(/An unknown error occurred/)).to.be.ok(); - expect(within(paragraph).getByText(/2 attempts/)).to.be.ok(); - expect(within(paragraph).getByText(/remaining/)).to.be.ok(); + ]} + isFailedDocType={false} + remainingAttempts={10} + hasDismissed={false} + />, + ); + + const paragraph = container.querySelector('p'); + expect(within(paragraph).getByText('An unknown error occurred')).to.be.ok(); + expect( + queryByRole('link', { + name: 'doc_auth.info.review_examples_of_photos', + }), + ).to.not.exist(); + }); + }); }); - it('renders alternative error message with errors and is a doc type failure', () => { - const { container } = render( - One attempt remaining', - other: 'You have %{count} attempts remaining', + context('there is a doc type failure', () => { + it('renders error message with errors and is a doc type failure', () => { + const { container } = render( + One attempt remaining', + other: 'You have%{count} attempts remaining', + }, + 'errors.doc_auth.doc_type_not_supported_heading': 'doc type not supported', + }, + }) + } + > + + , + ); + const paragraph = container.querySelector('p'); + expect(within(paragraph).getByText(/An unknown error occurred/)).to.be.ok(); + expect(within(paragraph).getByText(/2 attempts/)).to.be.ok(); + expect(within(paragraph).getByText(/remaining/)).to.be.ok(); + }); + + it('renders alternative error message with errors and is a doc type failure', () => { + const { container } = render( + One attempt remaining', + other: 'You have %{count} attempts remaining', + }, + }, + }) + } + > + - - , - ); - const paragraph = container.querySelector('p'); - expect(within(paragraph).getByText(/alternative message/)).to.be.ok(); + ]} + remainingAttempts={2} + isFailedDocType + altFailedDocTypeMsg="alternative message" + /> + , + ); + const paragraph = container.querySelector('p'); + expect(within(paragraph).getByText(/alternative message/)).to.be.ok(); + }); }); }); diff --git a/spec/jobs/multi_region_kms_migration/profile_migration_job_spec.rb b/spec/jobs/multi_region_kms_migration/profile_migration_job_spec.rb index 04bf863ea33..aa7880b5c0b 100644 --- a/spec/jobs/multi_region_kms_migration/profile_migration_job_spec.rb +++ b/spec/jobs/multi_region_kms_migration/profile_migration_job_spec.rb @@ -1,10 +1,6 @@ require 'rails_helper' RSpec.describe MultiRegionKmsMigration::ProfileMigrationJob do - before do - allow(IdentityConfig.store).to receive(:aws_kms_multi_region_read_enabled).and_return(true) - end - let!(:profiles) { create_list(:profile, 4, :with_pii) } let!(:single_region_ciphertext_profiles) do single_region_profiles = profiles[2..3] diff --git a/spec/jobs/multi_region_kms_migration/user_migration_job_spec.rb b/spec/jobs/multi_region_kms_migration/user_migration_job_spec.rb index cfc917cbbae..90e831c86c5 100644 --- a/spec/jobs/multi_region_kms_migration/user_migration_job_spec.rb +++ b/spec/jobs/multi_region_kms_migration/user_migration_job_spec.rb @@ -1,10 +1,6 @@ require 'rails_helper' RSpec.describe MultiRegionKmsMigration::UserMigrationJob do - before do - allow(IdentityConfig.store).to receive(:aws_kms_multi_region_read_enabled).and_return(true) - end - let!(:multi_region_password_digest_user) { create(:user, password: 'salty pickles') } let!(:single_region_password_digest_user) do user = create(:user, password: 'salty_pickles') diff --git a/spec/lib/tasks/review_profile_spec.rb b/spec/lib/tasks/review_profile_spec.rb index 4f8a9ec9933..95cb1f2c97e 100644 --- a/spec/lib/tasks/review_profile_spec.rb +++ b/spec/lib/tasks/review_profile_spec.rb @@ -5,6 +5,7 @@ let(:user) { create(:user, :fraud_review_pending) } let(:uuid) { user.uuid } let(:task_name) { nil } + let(:analytics) { FakeAnalytics.new } subject(:invoke_task) do Rake::Task[task_name].reenable @@ -22,6 +23,8 @@ uuid, ) stub_const('STDOUT', stdout) + + allow(Analytics).to receive(:new).and_return(analytics) end describe 'users:review:pass' do @@ -43,6 +46,20 @@ end end + it 'logs that the user was passed to analytics' do + profile_fraud_review_pending_at = user.profiles.first.fraud_review_pending_at + + invoke_task + + expect(analytics).to have_logged_event( + 'Fraud: Profile review passed', + success: true, + errors: nil, + exception: nil, + profile_fraud_review_pending_at: profile_fraud_review_pending_at, + ) + end + context 'when the user does not exist' do let(:user) { nil } let(:uuid) { 'not-a-real-uuid' } @@ -52,6 +69,18 @@ expect(stdout.string).to include('Error: Could not find user with that UUID') end + + it 'logs the error to analytics' do + invoke_task + + expect(analytics).to have_logged_event( + 'Fraud: Profile review passed', + success: false, + errors: { message: 'Error: Could not find user with that UUID' }, + exception: nil, + profile_fraud_review_pending_at: nil, + ) + end end context 'when the user has cancelled verification' do @@ -62,6 +91,21 @@ expect(user.reload.profiles.first.active).to eq(false) end + + it 'logs an error to analytics' do + profile_fraud_review_pending_at = user.profiles.first.fraud_review_pending_at + user.profiles.first.update!(gpo_verification_pending_at: user.created_at) + + expect { invoke_task }.to raise_error(RuntimeError) + + expect(analytics).to have_logged_event( + 'Fraud: Profile review passed', + success: false, + errors: nil, + exception: a_string_including('Attempting to activate profile with pending reason'), + profile_fraud_review_pending_at: profile_fraud_review_pending_at, + ) + end end end @@ -78,6 +122,20 @@ expect { invoke_task }.to change(ActionMailer::Base.deliveries, :count).by(1) end + it 'logs that the user was rejected to analytics' do + profile_fraud_review_pending_at = user.profiles.first.fraud_review_pending_at + + invoke_task + + expect(analytics).to have_logged_event( + 'Fraud: Profile review rejected', + success: true, + errors: nil, + exception: nil, + profile_fraud_review_pending_at: profile_fraud_review_pending_at, + ) + end + context 'when the user does not exist' do let(:user) { nil } let(:uuid) { 'not-a-real-uuid' } @@ -87,6 +145,18 @@ expect(stdout.string).to include('Error: Could not find user with that UUID') end + + it 'logs the error to analytics' do + invoke_task + + expect(analytics).to have_logged_event( + 'Fraud: Profile review rejected', + success: false, + errors: { message: 'Error: Could not find user with that UUID' }, + exception: nil, + profile_fraud_review_pending_at: nil, + ) + end end context 'when the user profile has a nil fraud_review_pending_at' do @@ -103,6 +173,18 @@ expect(stdout.string).to include('Error: User does not have a pending fraud review') end + + it 'logs the error to analytics' do + invoke_task + + expect(analytics).to have_logged_event( + 'Fraud: Profile review rejected', + success: false, + errors: { message: 'Error: User does not have a pending fraud review' }, + exception: nil, + profile_fraud_review_pending_at: nil, + ) + end end end end diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb index c94c93819db..2da146b5174 100644 --- a/spec/mailers/previews/user_mailer_preview.rb +++ b/spec/mailers/previews/user_mailer_preview.rb @@ -32,6 +32,14 @@ def reset_password_instructions ) end + def reset_password_instructions_with_pending_gpo_letter + UserMailer.with( + user: user_with_pending_gpo_letter, email_address: email_address_record, + ).reset_password_instructions( + token: SecureRandom.hex, request_id: SecureRandom.hex, + ) + end + def password_changed UserMailer.with(user: user, email_address: email_address_record). password_changed(disavowal_token: SecureRandom.hex) diff --git a/spec/models/profile_spec.rb b/spec/models/profile_spec.rb index 1b386a80696..8adea5833d9 100644 --- a/spec/models/profile_spec.rb +++ b/spec/models/profile_spec.rb @@ -195,91 +195,58 @@ end describe '#decrypt_pii' do - it 'decrypts PII' do - expect(profile.encrypted_pii).to be_nil - + it 'decrypts the PII for users with a multi region ciphertext' do profile.encrypt_pii(pii, user.password) + expect(profile.encrypted_pii_multi_region).to_not be_nil + decrypted_pii = profile.decrypt_pii(user.password) expect(decrypted_pii).to eq pii end - it 'fails if the encryption context from the uuid is incorrect' do + it 'decrypts the PII for users with only a single region ciphertext' do profile.encrypt_pii(pii, user.password) + profile.update!(encrypted_pii_multi_region: nil) - allow(profile.user).to receive(:uuid).and_return('a-different-uuid') + decrypted_pii = profile.decrypt_pii(user.password) - expect { profile.decrypt_pii(user.password) }.to raise_error(Encryption::EncryptionError) + expect(decrypted_pii).to eq pii end - context 'with aws_kms_multi_region_read_enabled enabled' do - before do - allow(IdentityConfig.store).to receive(:aws_kms_multi_region_read_enabled).and_return(true) - end - - it 'decrypts the PII for users with a multi region ciphertext' do - profile.encrypt_pii(pii, user.password) - - expect(profile.encrypted_pii_multi_region).to_not be_nil - - decrypted_pii = profile.decrypt_pii(user.password) - - expect(decrypted_pii).to eq pii - end - - it 'decrypts the PII for users with only a single region ciphertext' do - profile.encrypt_pii(pii, user.password) - profile.update!(encrypted_pii_multi_region: nil) + it 'fails if the encryption context from the uuid is incorrect' do + profile.encrypt_pii(pii, user.password) - decrypted_pii = profile.decrypt_pii(user.password) + allow(profile.user).to receive(:uuid).and_return('a-different-uuid') - expect(decrypted_pii).to eq pii - end + expect { profile.decrypt_pii(user.password) }.to raise_error(Encryption::EncryptionError) end end describe '#recover_pii' do - it 'decrypts the encrypted_pii_recovery using a personal key' do - expect(profile.encrypted_pii_recovery).to be_nil - + it 'decrypts recovery PII with personal key for users with a multi region ciphertext' do profile.encrypt_pii(pii, user.password) personal_key = profile.personal_key normalized_personal_key = PersonalKeyGenerator.new(user).normalize(personal_key) - expect(profile.recover_pii(normalized_personal_key)).to eq pii - end - - context 'with aws_kms_multi_region_read_enabled enabled' do - before do - allow(IdentityConfig.store).to receive(:aws_kms_multi_region_read_enabled).and_return(true) - end - - it 'decrypts the PII for users with a multi region ciphertext' do - profile.encrypt_pii(pii, user.password) - personal_key = profile.personal_key - - normalized_personal_key = PersonalKeyGenerator.new(user).normalize(personal_key) + expect(profile.encrypted_pii_recovery_multi_region).to_not be_nil - expect(profile.encrypted_pii_recovery_multi_region).to_not be_nil + decrypted_pii = profile.recover_pii(normalized_personal_key) - decrypted_pii = profile.recover_pii(normalized_personal_key) - - expect(decrypted_pii).to eq pii - end + expect(decrypted_pii).to eq pii + end - it 'decrypts the PII for users with only a single region ciphertext' do - profile.encrypt_pii(pii, user.password) - profile.update!(encrypted_pii_recovery_multi_region: nil) - personal_key = profile.personal_key + it 'decrypts recovery PII with personal key for users with only a single region ciphertext' do + profile.encrypt_pii(pii, user.password) + profile.update!(encrypted_pii_recovery_multi_region: nil) + personal_key = profile.personal_key - normalized_personal_key = PersonalKeyGenerator.new(user).normalize(personal_key) + normalized_personal_key = PersonalKeyGenerator.new(user).normalize(personal_key) - decrypted_pii = profile.recover_pii(normalized_personal_key) + decrypted_pii = profile.recover_pii(normalized_personal_key) - expect(decrypted_pii).to eq pii - end + expect(decrypted_pii).to eq pii end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 88a125c61f2..0a211ae5066 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -432,43 +432,30 @@ end describe '#valid_password?' do - it 'returns true if the password matches the stored digest' do + it 'validates the password for a user with a multi-region digest' do user = build(:user, password: 'test password') + expect(user.encrypted_password_digest_multi_region).to_not be_nil + expect(user.valid_password?('test password')).to eq(true) expect(user.valid_password?('wrong password')).to eq(false) end - context 'aws_kms_multi_region_read_enabled is set to true' do - before do - allow(IdentityConfig.store).to receive(:aws_kms_multi_region_read_enabled).and_return(true) - end - - it 'validates the password for a user with a multi-region digest' do - user = build(:user, password: 'test password') - - expect(user.encrypted_password_digest_multi_region).to_not be_nil - - expect(user.valid_password?('test password')).to eq(true) - expect(user.valid_password?('wrong password')).to eq(false) - end - - it 'validates the password for a user with a only a single-region digest' do - user = build(:user, password: 'test password') - user.encrypted_password_digest_multi_region = nil + it 'validates the password for a user with a only a single-region digest' do + user = build(:user, password: 'test password') + user.encrypted_password_digest_multi_region = nil - expect(user.valid_password?('test password')).to eq(true) - expect(user.valid_password?('wrong password')).to eq(false) - end + expect(user.valid_password?('test password')).to eq(true) + expect(user.valid_password?('wrong password')).to eq(false) + end - it 'validates the password for a user with a only a single-region UAK digest' do - user = build(:user) - user.encrypted_password_digest = Encryption::UakPasswordVerifier.digest('test password') - user.encrypted_password_digest_multi_region = nil + it 'validates the password for a user with a only a single-region UAK digest' do + user = build(:user) + user.encrypted_password_digest = Encryption::UakPasswordVerifier.digest('test password') + user.encrypted_password_digest_multi_region = nil - expect(user.valid_password?('test password')).to eq(true) - expect(user.valid_password?('wrong password')).to eq(false) - end + expect(user.valid_password?('test password')).to eq(true) + expect(user.valid_password?('wrong password')).to eq(false) end end @@ -493,44 +480,31 @@ end describe '#valid_personal_key?' do - it 'returns true if the personal key matches the stored digest' do + it 'validates the personal key for a user with a multi-region digest' do user = build(:user, personal_key: 'test personal key') + expect(user.encrypted_recovery_code_digest_multi_region).to_not be_nil + expect(user.valid_personal_key?('test personal key')).to eq(true) expect(user.valid_personal_key?('wrong personal key')).to eq(false) end - context 'aws_kms_multi_region_read_enabled is set to true' do - before do - allow(IdentityConfig.store).to receive(:aws_kms_multi_region_read_enabled).and_return(true) - end - - it 'validates the personal key for a user with a multi-region digest' do - user = build(:user, personal_key: 'test personal key') - - expect(user.encrypted_recovery_code_digest_multi_region).to_not be_nil - - expect(user.valid_personal_key?('test personal key')).to eq(true) - expect(user.valid_personal_key?('wrong personal key')).to eq(false) - end - - it 'validates the personal key for a user with a only a single-region digest' do - user = build(:user, personal_key: 'test personal key') - user.encrypted_recovery_code_digest_multi_region = nil + it 'validates the personal key for a user with a only a single-region digest' do + user = build(:user, personal_key: 'test personal key') + user.encrypted_recovery_code_digest_multi_region = nil - expect(user.valid_personal_key?('test personal key')).to eq(true) - expect(user.valid_personal_key?('wrong personal key')).to eq(false) - end + expect(user.valid_personal_key?('test personal key')).to eq(true) + expect(user.valid_personal_key?('wrong personal key')).to eq(false) + end - it 'validates the personal key for a user with a only a single-region UAK digest' do - user = build(:user) - user.encrypted_recovery_code_digest = - Encryption::UakPasswordVerifier.digest('test personal key') - user.encrypted_recovery_code_digest_multi_region = nil + it 'validates the personal key for a user with a only a single-region UAK digest' do + user = build(:user) + user.encrypted_recovery_code_digest = + Encryption::UakPasswordVerifier.digest('test personal key') + user.encrypted_recovery_code_digest_multi_region = nil - expect(user.valid_personal_key?('test personal key')).to eq(true) - expect(user.valid_personal_key?('wrong personal key')).to eq(false) - end + expect(user.valid_personal_key?('test personal key')).to eq(true) + expect(user.valid_personal_key?('wrong personal key')).to eq(false) end end diff --git a/spec/services/encryption/encryptors/pii_encryptor_spec.rb b/spec/services/encryption/encryptors/pii_encryptor_spec.rb index c2f442b1465..164ad30f92a 100644 --- a/spec/services/encryption/encryptors/pii_encryptor_spec.rb +++ b/spec/services/encryption/encryptors/pii_encryptor_spec.rb @@ -110,7 +110,7 @@ kms_client = subject.send(:multi_region_kms_client) expect(kms_client).to receive(:decrypt). - with('kms_ciphertext', { 'context' => 'pii-encryption', 'user_uuid' => 'uuid-123-abc' }). + with('kms_ciphertext_mr', { 'context' => 'pii-encryption', 'user_uuid' => 'uuid-123-abc' }). and_return('aes_ciphertext') cipher = subject.send(:aes_cipher) @@ -120,11 +120,15 @@ ciphertext_pair = Encryption::RegionalCiphertextPair.new( single_region_ciphertext: { - encrypted_data: Base64.strict_encode64('kms_ciphertext'), + encrypted_data: Base64.strict_encode64('kms_ciphertext_sr'), + salt: salt, + cost: '800$8$1$', + }.to_json, + multi_region_ciphertext: { + encrypted_data: Base64.strict_encode64('kms_ciphertext_mr'), salt: salt, cost: '800$8$1$', }.to_json, - multi_region_ciphertext: nil, ) result = subject.decrypt(ciphertext_pair, user_uuid: 'uuid-123-abc') @@ -132,59 +136,17 @@ expect(result).to eq(plaintext) end - context 'aws_kms_multi_region_read_enabled is set to true' do - before do - allow(IdentityConfig.store).to receive(:aws_kms_multi_region_read_enabled).and_return(true) - end - - it 'uses the multi-region ciphertext if it is available' do - test_ciphertext_pair = Encryption::RegionalCiphertextPair.new( - single_region_ciphertext: subject.encrypt( - 'single-region-text', user_uuid: '123abc' - ).single_region_ciphertext, - multi_region_ciphertext: subject.encrypt( - 'multi-region-text', user_uuid: '123abc' - ).multi_region_ciphertext, - ) - - result = subject.decrypt(test_ciphertext_pair, user_uuid: '123abc') - - expect(result).to eq('multi-region-text') - end - - it 'uses the single region ciphertext if the multi-region ciphertext is nil' do - test_ciphertext_pair = Encryption::RegionalCiphertextPair.new( - single_region_ciphertext: subject.encrypt( - 'single-region-text', user_uuid: '123abc' - ).single_region_ciphertext, - multi_region_ciphertext: nil, - ) - - result = subject.decrypt(test_ciphertext_pair, user_uuid: '123abc') - - expect(result).to eq('single-region-text') - end - end - - context 'aws_kms_multi_region_read_enabled is set to false' do - before do - allow(IdentityConfig.store).to receive(:aws_kms_multi_region_read_enabled).and_return(false) - end - - it 'uses the single-region ciphertext to decrypt' do - test_ciphertext_pair = Encryption::RegionalCiphertextPair.new( - single_region_ciphertext: subject.encrypt( - 'single-region-text', user_uuid: '123abc' - ).single_region_ciphertext, - multi_region_ciphertext: subject.encrypt( - 'multi-region-text', user_uuid: '123abc' - ).multi_region_ciphertext, - ) + it 'uses the single region ciphertext if the multi-region ciphertext is nil' do + test_ciphertext_pair = Encryption::RegionalCiphertextPair.new( + single_region_ciphertext: subject.encrypt( + 'single-region-text', user_uuid: '123abc' + ).single_region_ciphertext, + multi_region_ciphertext: nil, + ) - result = subject.decrypt(test_ciphertext_pair, user_uuid: '123abc') + result = subject.decrypt(test_ciphertext_pair, user_uuid: '123abc') - expect(result).to eq('single-region-text') - end + expect(result).to eq('single-region-text') end end end diff --git a/spec/services/encryption/multi_region_kms_migration/profile_migrator_spec.rb b/spec/services/encryption/multi_region_kms_migration/profile_migrator_spec.rb index 068c702cbbf..bd1aca5932a 100644 --- a/spec/services/encryption/multi_region_kms_migration/profile_migrator_spec.rb +++ b/spec/services/encryption/multi_region_kms_migration/profile_migrator_spec.rb @@ -7,10 +7,6 @@ subject { described_class.new(profile) } - before do - allow(IdentityConfig.store).to receive(:aws_kms_multi_region_read_enabled).and_return(true) - end - describe '#migrate!' do context 'for a user without multi-region ciphertexts' do before do diff --git a/spec/services/encryption/multi_region_kms_migration/user_migrator_spec.rb b/spec/services/encryption/multi_region_kms_migration/user_migrator_spec.rb index 9a6256aaf96..f7840e468a2 100644 --- a/spec/services/encryption/multi_region_kms_migration/user_migrator_spec.rb +++ b/spec/services/encryption/multi_region_kms_migration/user_migrator_spec.rb @@ -1,10 +1,6 @@ require 'rails_helper' RSpec.describe Encryption::MultiRegionKmsMigration::UserMigrator do - before do - allow(IdentityConfig.store).to receive(:aws_kms_multi_region_read_enabled).and_return(true) - end - let(:user) { create(:user, password: 'salty pickles', personal_key: '1234-ABCD') } subject { described_class.new(user) } diff --git a/spec/services/encryption/password_verifier_spec.rb b/spec/services/encryption/password_verifier_spec.rb index 14715ef8fed..86c8ed14c77 100644 --- a/spec/services/encryption/password_verifier_spec.rb +++ b/spec/services/encryption/password_verifier_spec.rb @@ -130,92 +130,26 @@ expect(bad_match_result).to eq(false) end - context 'aws_kms_multi_region_read_enabled is set to true' do - before do - allow(IdentityConfig.store).to receive(:aws_kms_multi_region_read_enabled).and_return(true) - end - - it 'uses the multi-region digest if it is available' do - test_digest_pair = Encryption::RegionalCiphertextPair.new( - single_region_ciphertext: subject.create_digest_pair( - password: 'single-region-password', - user_uuid: user_uuid, - ).single_region_ciphertext, - multi_region_ciphertext: subject.create_digest_pair( - password: 'multi-region-password', - user_uuid: user_uuid, - ).multi_region_ciphertext, - ) - - single_region_result = subject.verify( - password: 'single-region-password', - digest_pair: test_digest_pair, - user_uuid: user_uuid, - ) - multi_region_result = subject.verify( - password: 'multi-region-password', - digest_pair: test_digest_pair, - user_uuid: user_uuid, - ) - - expect(single_region_result).to eq(false) - expect(multi_region_result).to eq(true) - end - - it 'uses the single region digest if the multi-region digest is nil' do - test_digest_pair = subject.create_digest_pair( - password: password, - user_uuid: user_uuid, - ) - test_digest_pair.multi_region_ciphertext = nil - - correct_password_result = subject.verify( - password: password, - digest_pair: test_digest_pair, - user_uuid: user_uuid, - ) - incorrect_password_result = subject.verify( - password: 'this is a fake password lol', - digest_pair: test_digest_pair, - user_uuid: user_uuid, - ) - - expect(correct_password_result).to eq(true) - expect(incorrect_password_result).to eq(false) - end - end + it 'uses the single region digest if the multi-region digest is nil' do + test_digest_pair = subject.create_digest_pair( + password: password, + user_uuid: user_uuid, + ) + test_digest_pair.multi_region_ciphertext = nil - context 'aws_kms_multi_region_read_enabled is set to false' do - before do - allow(IdentityConfig.store).to receive(:aws_kms_multi_region_read_enabled).and_return(false) - end + correct_password_result = subject.verify( + password: password, + digest_pair: test_digest_pair, + user_uuid: user_uuid, + ) + incorrect_password_result = subject.verify( + password: 'this is a fake password lol', + digest_pair: test_digest_pair, + user_uuid: user_uuid, + ) - it 'uses the single-region digest to verify' do - test_digest_pair = Encryption::RegionalCiphertextPair.new( - single_region_ciphertext: subject.create_digest_pair( - password: 'single-region-password', - user_uuid: user_uuid, - ).single_region_ciphertext, - multi_region_ciphertext: subject.create_digest_pair( - password: 'multi-region-password', - user_uuid: user_uuid, - ).multi_region_ciphertext, - ) - - single_region_result = subject.verify( - password: 'single-region-password', - digest_pair: test_digest_pair, - user_uuid: user_uuid, - ) - multi_region_result = subject.verify( - password: 'multi-region-password', - digest_pair: test_digest_pair, - user_uuid: user_uuid, - ) - - expect(single_region_result).to eq(true) - expect(multi_region_result).to eq(false) - end + expect(correct_password_result).to eq(true) + expect(incorrect_password_result).to eq(false) end end diff --git a/spec/support/features/idv_step_helper.rb b/spec/support/features/idv_step_helper.rb index 4f14215840b..86b3b700e40 100644 --- a/spec/support/features/idv_step_helper.rb +++ b/spec/support/features/idv_step_helper.rb @@ -134,7 +134,7 @@ def complete_idv_steps_before_ssn(user = user_with_2fa) # location page complete_location_step(user) # state ID page - fill_out_state_id_form_ok(double_address_verification: true, same_address_as_id: true) + fill_out_state_id_form_ok(same_address_as_id: true, capture_secondary_id_enabled: true) click_idv_continue end diff --git a/spec/support/features/in_person_helper.rb b/spec/support/features/in_person_helper.rb index 672ea15dbec..8f856af732e 100644 --- a/spec/support/features/in_person_helper.rb +++ b/spec/support/features/in_person_helper.rb @@ -31,7 +31,7 @@ module InPersonHelper GOOD_IDENTITY_DOC_ZIPCODE = Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS[:identity_doc_zipcode] - def fill_out_state_id_form_ok(double_address_verification: false, same_address_as_id: false) + def fill_out_state_id_form_ok(same_address_as_id: false, capture_secondary_id_enabled: false) fill_in t('in_person_proofing.form.state_id.first_name'), with: GOOD_FIRST_NAME fill_in t('in_person_proofing.form.state_id.last_name'), with: GOOD_LAST_NAME year, month, day = GOOD_DOB.split('-') @@ -42,7 +42,7 @@ def fill_out_state_id_form_ok(double_address_verification: false, same_address_a from: t('in_person_proofing.form.state_id.state_id_jurisdiction') fill_in t('in_person_proofing.form.state_id.state_id_number'), with: GOOD_STATE_ID_NUMBER - if double_address_verification + if capture_secondary_id_enabled fill_in t('in_person_proofing.form.state_id.address1'), with: GOOD_IDENTITY_DOC_ADDRESS1 fill_in t('in_person_proofing.form.state_id.address2'), with: GOOD_IDENTITY_DOC_ADDRESS2 fill_in t('in_person_proofing.form.state_id.city'), with: GOOD_IDENTITY_DOC_CITY @@ -123,15 +123,15 @@ def complete_prepare_step(_user = nil) end def complete_state_id_step(_user = nil, same_address_as_id: true, - double_address_verification: false) + capture_secondary_id_enabled: false) # Wait for page to load before attempting to fill out form expect(page).to have_current_path(idv_in_person_step_path(step: :state_id), wait: 10) fill_out_state_id_form_ok( - double_address_verification: double_address_verification, same_address_as_id: same_address_as_id, + capture_secondary_id_enabled: capture_secondary_id_enabled, ) click_idv_continue - unless double_address_verification && same_address_as_id + unless capture_secondary_id_enabled && same_address_as_id expect(page).to have_current_path(idv_in_person_step_path(step: :address), wait: 10) expect_in_person_step_indicator_current_step(t('step_indicator.flows.idv.verify_info')) end diff --git a/spec/views/users/phones/add.html.erb_spec.rb b/spec/views/users/phones/add.html.erb_spec.rb deleted file mode 100644 index 30def5bdbb4..00000000000 --- a/spec/views/users/phones/add.html.erb_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'rails_helper' - -RSpec.describe 'users/phones/add.html.erb' do - include Devise::Test::ControllerHelpers - - subject(:rendered) { render } - - before do - user = build_stubbed(:user) - @new_phone_form = NewPhoneForm.new(user:) - end - - context 'phone vendor outage' do - before do - allow_any_instance_of(OutageStatus).to receive(:vendor_outage?).and_return(false) - allow_any_instance_of(OutageStatus).to receive(:vendor_outage?).with(:sms).and_return(true) - end - - it 'renders alert banner' do - expect(rendered).to have_selector('.usa-alert.usa-alert--error') - end - end -end