diff --git a/.gitignore b/.gitignore index 705451c2553..73ada7f4c7d 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,7 @@ Vagrantfile /doc/ package-lock.json +browsers.json saml_*.txt saml_*.shr diff --git a/Gemfile.lock b/Gemfile.lock index b146be654ec..7cc691748c9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -460,7 +460,7 @@ GEM nio4r (~> 2.0) raabro (1.4.0) racc (1.6.2) - rack (2.2.6.2) + rack (2.2.6.3) rack-attack (6.5.0) rack (>= 1.0, < 3) rack-cors (1.1.1) diff --git a/Makefile b/Makefile index 5b8e175268f..bce9ab58f6c 100644 --- a/Makefile +++ b/Makefile @@ -129,6 +129,9 @@ brakeman: ## Runs brakeman public/packs/manifest.json: yarn.lock $(shell find app/javascript -type f) ## Builds JavaScript assets yarn build +browsers.json: yarn.lock .browserslistrc ## Generates browsers.json browser support file + yarn generate-browsers-json + test: export RAILS_ENV := test test: $(CONFIG) ## Runs RSpec and yarn tests in parallel bundle exec rake parallel:spec && yarn test @@ -153,7 +156,7 @@ tmp/$(HOST)-$(PORT).key tmp/$(HOST)-$(PORT).crt: ## Self-signed cert for local H -keyout tmp/$(HOST)-$(PORT).key \ -out tmp/$(HOST)-$(PORT).crt -run: ## Runs the development server +run: browsers.json ## Runs the development server foreman start -p $(PORT) urn: @@ -165,8 +168,10 @@ run-https: tmp/$(HOST)-$(PORT).key tmp/$(HOST)-$(PORT).crt ## Runs the developme normalize_yaml: ## Normalizes YAML files (alphabetizes keys, fixes line length, smart quotes) yarn normalize-yaml .rubocop.yml --disable-sort-keys --disable-smart-punctuation + find ./config/locales/transliterate -type f -name '*.yml' -exec yarn normalize-yaml --disable-sort-keys --disable-smart-punctuation {} \; find ./config/locales/telephony -type f -name '*.yml' | xargs yarn normalize-yaml --disable-smart-punctuation - find ./config/locales -not -path "./config/locales/telephony*" -type f -name '*.yml' | xargs yarn normalize-yaml \ + find ./config/locales -not \( -path "./config/locales/telephony*" -o -path "./config/locales/transliterate/*" \) -type f -name '*.yml' | \ + xargs yarn normalize-yaml \ config/pinpoint_supported_countries.yml \ config/pinpoint_overrides.yml \ config/country_dialing_codes.yml diff --git a/README.md b/README.md index aa9e4439769..f93eb2b5abb 100644 --- a/README.md +++ b/README.md @@ -315,3 +315,14 @@ If you are getting the error: LoadError: cannot load such file -- sassc ``` Try `make run` for a short time, then use Ctrl+C to kill it + +##### Errors relating to OpenSSL versions + +If you get this error during test runs: +``` + Failure/Error: JWT::JWK.import(certs_response[:keys].first).public_key + OpenSSL::PKey::PKeyError: + rsa#set_key= is incompatible with OpenSSL 3.0 +``` +See [this document](docs/FixingOpenSSLVersionProblem.md) for how to fix it. + diff --git a/app/assets/images/2FA-voice.svg b/app/assets/images/2FA-voice.svg deleted file mode 100644 index 030922c61c4..00000000000 --- a/app/assets/images/2FA-voice.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/images/inherited_proofing/switch.png b/app/assets/images/inherited_proofing/switch.png deleted file mode 100644 index 315b1e56868..00000000000 Binary files a/app/assets/images/inherited_proofing/switch.png and /dev/null differ diff --git a/app/assets/images/shield-spinner.gif b/app/assets/images/shield-spinner.gif deleted file mode 100644 index 729b9ad7f1c..00000000000 Binary files a/app/assets/images/shield-spinner.gif and /dev/null differ diff --git a/app/assets/stylesheets/components/_alert-icon.scss b/app/assets/stylesheets/components/_alert-icon.scss index 7c402b08728..cf540a4686c 100644 --- a/app/assets/stylesheets/components/_alert-icon.scss +++ b/app/assets/stylesheets/components/_alert-icon.scss @@ -4,25 +4,6 @@ height: 88px; } -// For displaying icons as "badges" -// at the top of modals -.iconic-modal-badge { - position: relative; - &::before { - background-repeat: no-repeat; - background-size: contain; - content: ''; - position: absolute; - left: 50%; - top: 0px; - transform: translateX(-50%) translateY(-50%); - height: 88px; - width: 88px; - } - &.personal-key-badge::before { - background-image: url('status/personal-key.svg'); - } -} .alert-icon--centered-top { position: absolute; left: 50%; diff --git a/app/controllers/api/irs_attempts_api_controller.rb b/app/controllers/api/irs_attempts_api_controller.rb index 6f1e1be0611..4d523187e15 100644 --- a/app/controllers/api/irs_attempts_api_controller.rb +++ b/app/controllers/api/irs_attempts_api_controller.rb @@ -106,7 +106,7 @@ def serve_s3_response(log_file_record:) def authenticate_client bearer, csp_id, token = request.authorization&.split(' ', 3) - if bearer != 'Bearer' || !valid_auth_tokens.include?(token) || + if bearer != 'Bearer' || !valid_auth_token?(token) || csp_id != IdentityConfig.store.irs_attempt_api_csp_id analytics.irs_attempts_api_events( **analytics_properties( @@ -118,6 +118,26 @@ def authenticate_client end end + def valid_auth_token?(token) + valid_auth_data = hashed_valid_auth_data + cost = valid_auth_data[:cost] + salt = valid_auth_data[:salt] + hashed_token = scrypt_digest(token: token, salt: salt, cost: cost) + + valid_auth_data[:digested_tokens].any? do |valid_hashed_token| + ActiveSupport::SecurityUtils.secure_compare( + valid_hashed_token, + hashed_token, + ) + end + end + + def scrypt_digest(token:, salt:, cost:) + scrypt_salt = cost + OpenSSL::Digest::SHA256.hexdigest(salt) + scrypted = SCrypt::Engine.hash_secret token, scrypt_salt, 32 + SCrypt::Password.new(scrypted).digest + end + # @return [Array] JWE strings def security_event_tokens return [] unless timestamp @@ -146,8 +166,24 @@ def s3_helper @s3_helper ||= JobHelpers::S3Helper.new end - def valid_auth_tokens - IdentityConfig.store.irs_attempt_api_auth_tokens + def hashed_valid_auth_data + key = IdentityConfig.store.irs_attempt_api_auth_tokens.map do |token| + OpenSSL::Digest::SHA256.hexdigest(token) + end.join(',') + + Rails.cache.fetch("irs_hashed_tokens:#{key}", expires_in: 48.hours) do + salt = SecureRandom.hex(32) + cost = IdentityConfig.store.scrypt_cost + digested_tokens = IdentityConfig.store.irs_attempt_api_auth_tokens.map do |token| + scrypt_digest(token: token, salt: salt, cost: cost) + end + + { + salt: salt, + cost: cost, + digested_tokens: digested_tokens, + } + end end def analytics_properties(authenticated:, elapsed_time:) diff --git a/app/controllers/concerns/allowlisted_flow_step_concern.rb b/app/controllers/concerns/allowlisted_flow_step_concern.rb deleted file mode 100644 index 7a2d4ba1657..00000000000 --- a/app/controllers/concerns/allowlisted_flow_step_concern.rb +++ /dev/null @@ -1,37 +0,0 @@ -# This Concern satisfies the brakeman gem "Dynamic Render Path" violation -# that is raised when rendering dynamic content in views and partials -# that come directly from params. In the below example, idv_inherited_proofing_cancel_path -# would render "/verify/inherited_proofing/cancel?step=" where -# == the value of params[:step], which could potentially be dangerous: -# <%= render ButtonComponent.new(action: ->(...) do -# button_to(idv_inherited_proofing_cancel_path(step: params[:step]), ...) ... -# end -# %> -module AllowlistedFlowStepConcern - extend ActiveSupport::Concern - - included do - before_action :flow_step! - end - - private - - def flow_step! - flow_step = flow_step_param - unless flow_step_allowlist.include? flow_step - Rails.logger.warn "Flow step param \"#{flow_step})\" was not whitelisted!" - render_not_found and return - end - - @flow_step = flow_step - end - - # Override this method for flow step params other than params[:step] - def flow_step_param - params[:step] - end - - def flow_step_allowlist - raise NotImplementedError, '#flow_step_allowlist must be overridden' - end -end diff --git a/app/controllers/concerns/idv/step_utilities_concern.rb b/app/controllers/concerns/idv/step_utilities_concern.rb index 31c4a4f8924..227d3e697a3 100644 --- a/app/controllers/concerns/idv/step_utilities_concern.rb +++ b/app/controllers/concerns/idv/step_utilities_concern.rb @@ -14,10 +14,16 @@ def flow_path def confirm_pii_from_doc @pii = flow_session&.[]('pii_from_doc') # hash with indifferent access return if @pii.present? + flow_session&.delete('Idv::Steps::DocumentCaptureStep') redirect_to idv_doc_auth_url end + def confirm_profile_not_already_confirmed + return unless idv_session.profile_confirmation == true + redirect_to idv_review_url + end + # Copied from capture_doc_flow.rb # and from doc_auth_flow.rb def acuant_sdk_ab_test_analytics_args diff --git a/app/controllers/concerns/idv/verify_info_concern.rb b/app/controllers/concerns/idv/verify_info_concern.rb index e5df7d7ac72..72a3768cef1 100644 --- a/app/controllers/concerns/idv/verify_info_concern.rb +++ b/app/controllers/concerns/idv/verify_info_concern.rb @@ -96,6 +96,7 @@ def async_state_done(current_async_state) # todo: add other edited fields? extra: { address_edited: !!flow_session['address_edited'], + address_line2_present: !pii[:address2].blank?, pii_like_keypaths: [[:errors, :ssn], [:response_body, :first_name]], }, ) @@ -230,6 +231,7 @@ def add_proofing_costs(results) elsif stage == :threatmetrix # transaction_id comes from request_id tmx_id = hash[:transaction_id] + log_irs_tmx_fraud_check_event(hash) if tmx_id add_cost(:threatmetrix, transaction_id: tmx_id) if tmx_id end end diff --git a/app/controllers/concerns/idv_session.rb b/app/controllers/concerns/idv_session.rb index 4f7c25c255e..6329fd9ca87 100644 --- a/app/controllers/concerns/idv_session.rb +++ b/app/controllers/concerns/idv_session.rb @@ -7,10 +7,6 @@ module IdvSession before_action :redirect_if_sp_context_needed end - def confirm_idv_applicant_created - redirect_to idv_verify_info_url if idv_session.applicant.blank? - end - def confirm_idv_needed return if effective_user.active_profile.blank? || decorated_session.requested_more_recent_verification? || diff --git a/app/controllers/concerns/idv_step_concern.rb b/app/controllers/concerns/idv_step_concern.rb index 8568a2138cb..0067dc4c46d 100644 --- a/app/controllers/concerns/idv_step_concern.rb +++ b/app/controllers/concerns/idv_step_concern.rb @@ -7,4 +7,20 @@ module IdvStepConcern before_action :confirm_two_factor_authenticated before_action :confirm_idv_needed end + + def confirm_verify_info_step_complete + return if idv_session.verify_info_step_complete? + + if idv_session.in_person_enrollment? + redirect_to idv_in_person_verify_info_url + else + redirect_to idv_verify_info_url + end + end + + def confirm_address_step_complete + return if idv_session.address_step_complete? + + redirect_to idv_otp_verification_url + end end diff --git a/app/controllers/concerns/inherited_proofing_404_concern.rb b/app/controllers/concerns/inherited_proofing_404_concern.rb deleted file mode 100644 index 51abb1e6cb2..00000000000 --- a/app/controllers/concerns/inherited_proofing_404_concern.rb +++ /dev/null @@ -1,13 +0,0 @@ -module InheritedProofing404Concern - extend ActiveSupport::Concern - - included do - before_action :render_404_if_disabled - end - - private - - def render_404_if_disabled - render_not_found unless IdentityConfig.store.inherited_proofing_enabled - end -end diff --git a/app/controllers/concerns/inherited_proofing_concern.rb b/app/controllers/concerns/inherited_proofing_concern.rb deleted file mode 100644 index 3b26d60826b..00000000000 --- a/app/controllers/concerns/inherited_proofing_concern.rb +++ /dev/null @@ -1,47 +0,0 @@ -# Methods to aid in handling incoming requests from 3rd-party -# inherited proofing service providers. Exclusively, methods -# to handle and help manage incoming requests to create a -# Login.gov account. -module InheritedProofingConcern - extend ActiveSupport::Concern - - # Returns true if Inherited Proofing is currently underway. - def inherited_proofing? - inherited_proofing_service_provider.present? - end - - def inherited_proofing_service_provider - return :va if va_inherited_proofing? - end - - # Department of Veterans Affairs (VA) methods. - # https://github.com/department-of-veterans-affairs/va.gov-team/blob/master/products/identity/Inherited%20Proofing/MHV%20Inherited%20Proofing/inherited-proofing-interface.md - - # Returns true if the incoming request has been identified as a - # request to create a Login.gov account via inherited proofing - # from the VA. - def va_inherited_proofing? - va_inherited_proofing_auth_code.present? - end - - # The VA calls Login.gov to initiate inherited proofing of their - # users. An authorization code is passed as a query param that needs to - # be used in subsequent requests; this method returns this authorization - # code. - def va_inherited_proofing_auth_code - @va_inherited_proofing_auth_code ||= - decorated_session.request_url_params[va_inherited_proofing_auth_code_params_key] - end - - def va_inherited_proofing_auth_code_params_key - 'inherited_proofing_auth' - end - - def inherited_proofing_service_provider_data - if inherited_proofing_service_provider == :va - { auth_code: va_inherited_proofing_auth_code } - else - {} - end - end -end diff --git a/app/controllers/concerns/inherited_proofing_presenter_concern.rb b/app/controllers/concerns/inherited_proofing_presenter_concern.rb deleted file mode 100644 index 993589dda99..00000000000 --- a/app/controllers/concerns/inherited_proofing_presenter_concern.rb +++ /dev/null @@ -1,15 +0,0 @@ -module InheritedProofingPresenterConcern - extend ActiveSupport::Concern - - included do - before_action :init_presenter - end - - private - - def init_presenter - @presenter = Idv::InheritedProofing::InheritedProofingPresenter.new( - service_provider: inherited_proofing_service_provider, - ) - end -end diff --git a/app/controllers/concerns/verify_sp_attributes_concern.rb b/app/controllers/concerns/verify_sp_attributes_concern.rb index 65db253fbd0..6a168bb07b8 100644 --- a/app/controllers/concerns/verify_sp_attributes_concern.rb +++ b/app/controllers/concerns/verify_sp_attributes_concern.rb @@ -8,6 +8,8 @@ def needs_completion_screen_reason :new_sp elsif !requested_attributes_verified?(sp_session_identity) :new_attributes + elsif reverified_after_consent?(sp_session_identity) + :reverified_after_consent elsif consent_has_expired?(sp_session_identity) :consent_expired elsif consent_was_revoked?(sp_session_identity) @@ -30,10 +32,9 @@ def update_verified_attributes def consent_has_expired?(sp_session_identity) return false unless sp_session_identity return false if sp_session_identity.deleted_at.present? - last_estimated_consent = sp_session_identity.last_consented_at || sp_session_identity.created_at + last_estimated_consent = last_estimated_consent_for(sp_session_identity) !last_estimated_consent || - last_estimated_consent < ServiceProviderIdentity::CONSENT_EXPIRATION.ago || - verified_after_consent?(last_estimated_consent) + last_estimated_consent < ServiceProviderIdentity::CONSENT_EXPIRATION.ago end def consent_was_revoked?(sp_session_identity) @@ -41,8 +42,20 @@ def consent_was_revoked?(sp_session_identity) sp_session_identity.deleted_at.present? end + def reverified_after_consent?(sp_session_identity) + return false unless sp_session_identity + return false if sp_session_identity.deleted_at.present? + last_estimated_consent = last_estimated_consent_for(sp_session_identity) + return false if last_estimated_consent.nil? + verified_after_consent?(last_estimated_consent) + end + private + def last_estimated_consent_for(sp_session_identity) + sp_session_identity.last_consented_at || sp_session_identity.created_at + end + def verified_after_consent?(last_estimated_consent) verification_timestamp = current_user.active_profile&.verified_at diff --git a/app/controllers/idv/doc_auth_controller.rb b/app/controllers/idv/doc_auth_controller.rb index 99ead51788d..2861a53eb7f 100644 --- a/app/controllers/idv/doc_auth_controller.rb +++ b/app/controllers/idv/doc_auth_controller.rb @@ -23,7 +23,7 @@ class DocAuthController < ApplicationController FLOW_STATE_MACHINE_SETTINGS = { step_url: :idv_doc_auth_step_url, - final_url: :idv_review_url, + final_url: :idv_ssn_url, flow: Idv::Flows::DocAuthFlow, analytics_id: 'Doc Auth', }.freeze diff --git a/app/controllers/idv/document_capture_controller.rb b/app/controllers/idv/document_capture_controller.rb new file mode 100644 index 00000000000..5a75c89d5d9 --- /dev/null +++ b/app/controllers/idv/document_capture_controller.rb @@ -0,0 +1,101 @@ +module Idv + class DocumentCaptureController < ApplicationController + include IdvSession + include StepIndicatorConcern + include StepUtilitiesConcern + + before_action :render_404_if_document_capture_controller_disabled + before_action :confirm_two_factor_authenticated + + def show + increment_step_counts + + analytics.idv_doc_auth_document_capture_visited(**analytics_arguments) + + render :show, locals: extra_view_variables + end + + def extra_view_variables + url_builder = ImageUploadPresignedUrlGenerator.new + + { + flow_session: flow_session, + flow_path: 'standard', + sp_name: decorated_session.sp_name, + failure_to_proof_url: idv_doc_auth_return_to_sp_url, + + front_image_upload_url: url_builder.presigned_image_upload_url( + image_type: 'front', + transaction_id: flow_session[:document_capture_session_uuid], + ), + back_image_upload_url: url_builder.presigned_image_upload_url( + image_type: 'back', + transaction_id: flow_session[:document_capture_session_uuid], + ), + }.merge( + native_camera_ab_testing_variables, + acuant_sdk_upgrade_a_b_testing_variables, + in_person_cta_variant_testing_variables, + ) + end + + private + + def render_404_if_document_capture_controller_disabled + render_not_found unless IdentityConfig.store.doc_auth_document_capture_controller_enabled + end + + def analytics_arguments + { + flow_path: flow_path, + step: 'document capture', + step_count: current_flow_step_counts['Idv::Steps::DocumentCaptureStep'], + analytics_id: 'Doc Auth', + irs_reproofing: irs_reproofing?, + }.merge(**acuant_sdk_ab_test_analytics_args) + end + + def current_flow_step_counts + user_session['idv/doc_auth_flow_step_counts'] ||= {} + user_session['idv/doc_auth_flow_step_counts'].default = 0 + user_session['idv/doc_auth_flow_step_counts'] + end + + def increment_step_counts + current_flow_step_counts['Idv::Steps::DocumentCaptureStep'] += 1 + end + + def native_camera_ab_testing_variables + { + acuant_sdk_upgrade_ab_test_bucket: + AbTests::ACUANT_SDK.bucket(flow_session[:document_capture_session_uuid]), + } + end + + def acuant_sdk_upgrade_a_b_testing_variables + bucket = AbTests::ACUANT_SDK.bucket(flow_session[:document_capture_session_uuid]) + testing_enabled = IdentityConfig.store.idv_acuant_sdk_upgrade_a_b_testing_enabled + use_alternate_sdk = (bucket == :use_alternate_sdk) + if use_alternate_sdk + acuant_version = IdentityConfig.store.idv_acuant_sdk_version_alternate + else + acuant_version = IdentityConfig.store.idv_acuant_sdk_version_default + end + { + acuant_sdk_upgrade_a_b_testing_enabled: + testing_enabled, + use_alternate_sdk: use_alternate_sdk, + acuant_version: acuant_version, + } + end + + def in_person_cta_variant_testing_variables + bucket = AbTests::IN_PERSON_CTA.bucket(flow_session[:document_capture_session_uuid]) + { + in_person_cta_variant_testing_enabled: + IdentityConfig.store.in_person_cta_variant_testing_enabled, + in_person_cta_variant_active: bucket, + } + end + end +end diff --git a/app/controllers/idv/in_person/usps_locations_controller.rb b/app/controllers/idv/in_person/usps_locations_controller.rb index 2bae32bbf6d..7adb827a183 100644 --- a/app/controllers/idv/in_person/usps_locations_controller.rb +++ b/app/controllers/idv/in_person/usps_locations_controller.rb @@ -65,6 +65,7 @@ def handle_error(err) Faraday::TimeoutError => :unprocessable_entity, Faraday::BadRequestError => :unprocessable_entity, Faraday::ForbiddenError => :unprocessable_entity, + ActionController::InvalidAuthenticityToken => :unprocessable_entity, }[err.class] || :internal_server_error analytics.idv_in_person_locations_request_failure( diff --git a/app/controllers/idv/inherited_proofing_cancellations_controller.rb b/app/controllers/idv/inherited_proofing_cancellations_controller.rb deleted file mode 100644 index e14d7048d61..00000000000 --- a/app/controllers/idv/inherited_proofing_cancellations_controller.rb +++ /dev/null @@ -1,77 +0,0 @@ -module Idv - class InheritedProofingCancellationsController < ApplicationController - include IdvSession - include GoBackHelper - include InheritedProofing404Concern - include AllowlistedFlowStepConcern - - before_action :confirm_idv_needed - - def new - analytics.idv_cancellation_visited(step: params[:step], **analytics_properties) - self.session_go_back_path = go_back_path || idv_inherited_proofing_path - @presenter = CancellationsPresenter.new( - sp_name: decorated_session.sp_name, - url_options: url_options, - ) - end - - def update - analytics.idv_cancellation_go_back(step: params[:step], **analytics_properties) - redirect_to session_go_back_path || idv_inherited_proofing_path - end - - def destroy - analytics.idv_cancellation_confirmed(step: params[:step], **analytics_properties) - cancel_session - render json: { redirect_url: cancelled_redirect_path } - end - - private - - def cancel_session - cancel_idv_session - cancel_user_session - end - - def cancel_idv_session - idv_session = user_session[:idv] - idv_session&.clear - end - - def cancel_user_session - user_session['idv'] = {} - end - - def cancelled_redirect_path - return return_to_sp_failure_to_proof_path(location_params) if decorated_session.sp_name - - account_path - end - - def location_params - params.permit(:step, :location).to_h.symbolize_keys - end - - def session_go_back_path=(path) - idv_session.go_back_path = path - end - - def session_go_back_path - idv_session.go_back_path - end - - def flow_step_allowlist - @flow_step_allowlist ||= Idv::Flows::InheritedProofingFlow::STEPS.keys.map(&:to_s) - end - - def effective_user_id - current_user&.id - end - - def analytics_properties - route_properties = ParseControllerFromReferer.new(request.referer).call - route_properties.merge({ analytics_id: @analytics_id }) - end - end -end diff --git a/app/controllers/idv/inherited_proofing_controller.rb b/app/controllers/idv/inherited_proofing_controller.rb deleted file mode 100644 index d434751b33a..00000000000 --- a/app/controllers/idv/inherited_proofing_controller.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Idv - class InheritedProofingController < ApplicationController - before_action :confirm_two_factor_authenticated - - include Flow::FlowStateMachine - include IdvSession - include InheritedProofing404Concern - include InheritedProofingConcern - include InheritedProofingPresenterConcern - - FLOW_STATE_MACHINE_SETTINGS = { - step_url: :idv_inherited_proofing_step_url, - final_url: :idv_phone_url, - flow: Idv::Flows::InheritedProofingFlow, - analytics_id: 'Inherited Proofing', - }.freeze - - def return_to_sp - redirect_to return_to_sp_failure_to_proof_url(step: next_step, location: params[:location]) - end - end -end diff --git a/app/controllers/idv/inherited_proofing_errors_controller.rb b/app/controllers/idv/inherited_proofing_errors_controller.rb deleted file mode 100644 index b7cdf021f72..00000000000 --- a/app/controllers/idv/inherited_proofing_errors_controller.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Idv - class InheritedProofingErrorsController < ApplicationController - include IdvSession - include InheritedProofingConcern - include InheritedProofingPresenterConcern - - def warning - end - - def failure - end - end -end diff --git a/app/controllers/idv/otp_verification_controller.rb b/app/controllers/idv/otp_verification_controller.rb index a534637b9a6..e3cd1fd9468 100644 --- a/app/controllers/idv/otp_verification_controller.rb +++ b/app/controllers/idv/otp_verification_controller.rb @@ -48,7 +48,7 @@ def confirm_step_needed def confirm_otp_sent return if idv_session.user_phone_confirmation_session.present? - redirect_to idv_otp_delivery_method_url + redirect_to idv_phone_url end def set_code diff --git a/app/controllers/idv/phone_controller.rb b/app/controllers/idv/phone_controller.rb index e5c606c4735..b16f92a9126 100644 --- a/app/controllers/idv/phone_controller.rb +++ b/app/controllers/idv/phone_controller.rb @@ -1,7 +1,5 @@ module Idv class PhoneController < ApplicationController - before_action :confirm_verify_info_complete - include IdvStepConcern include StepIndicatorConcern include PhoneOtpRateLimitable @@ -9,7 +7,7 @@ class PhoneController < ApplicationController attr_reader :idv_form - before_action :confirm_idv_applicant_created + before_action :confirm_verify_info_step_complete before_action :confirm_step_needed before_action :set_idv_form @@ -55,14 +53,6 @@ def create private - def confirm_verify_info_complete - return unless IdentityConfig.store.in_person_verify_info_controller_enabled - return unless user_fully_authenticated? - return if idv_session.resolution_successful - - redirect_to idv_in_person_verify_info_url - end - def throttle @throttle ||= Throttle.new(user: current_user, throttle_type: :proof_address) end diff --git a/app/controllers/idv/review_controller.rb b/app/controllers/idv/review_controller.rb index abd77225e10..121b1740167 100644 --- a/app/controllers/idv/review_controller.rb +++ b/app/controllers/idv/review_controller.rb @@ -6,25 +6,13 @@ class ReviewController < ApplicationController include StepIndicatorConcern include PhoneConfirmation - before_action :confirm_idv_applicant_created - before_action :confirm_idv_steps_complete - before_action :confirm_idv_phone_confirmed + before_action :confirm_verify_info_step_complete + before_action :confirm_address_step_complete before_action :confirm_current_password, only: [:create] rescue_from UspsInPersonProofing::Exception::RequestEnrollException, with: :handle_request_enroll_exception - def confirm_idv_steps_complete - return redirect_to(idv_verify_info_url) unless idv_profile_complete? - return redirect_to(idv_phone_url) unless idv_address_complete? - end - - def confirm_idv_phone_confirmed - return unless idv_session.address_verification_mechanism == 'phone' - return if idv_session.phone_confirmed? - redirect_to idv_otp_verification_path - end - def confirm_current_password return if valid_password? @@ -97,14 +85,6 @@ def flash_message_content end end - def idv_profile_complete? - !!idv_session.profile_confirmation - end - - def idv_address_complete? - idv_session.address_mechanism_chosen? - end - def init_profile idv_session.create_profile_from_applicant_with_password(password) diff --git a/app/controllers/idv/sessions_controller.rb b/app/controllers/idv/sessions_controller.rb index 9f93c28aa36..0021a4f9fa1 100644 --- a/app/controllers/idv/sessions_controller.rb +++ b/app/controllers/idv/sessions_controller.rb @@ -57,7 +57,6 @@ def cancel_in_person_enrollment_if_exists def clear_session user_session['idv/doc_auth'] = {} user_session['idv/in_person'] = {} - user_session['idv/inherited_proofing'] = {} idv_session.clear Pii::Cacher.new(current_user, user_session).delete end diff --git a/app/controllers/idv/ssn_controller.rb b/app/controllers/idv/ssn_controller.rb index 6e8eaf16c3a..a1c85b156ab 100644 --- a/app/controllers/idv/ssn_controller.rb +++ b/app/controllers/idv/ssn_controller.rb @@ -6,6 +6,7 @@ class SsnController < ApplicationController include Steps::ThreatMetrixStepHelper before_action :confirm_two_factor_authenticated + before_action :confirm_profile_not_already_confirmed before_action :confirm_pii_from_doc attr_accessor :error_message diff --git a/app/controllers/idv/verify_info_controller.rb b/app/controllers/idv/verify_info_controller.rb index 05818921dec..3969f3cf8d5 100644 --- a/app/controllers/idv/verify_info_controller.rb +++ b/app/controllers/idv/verify_info_controller.rb @@ -4,6 +4,7 @@ class VerifyInfoController < ApplicationController include StepUtilitiesConcern include StepIndicatorConcern include VerifyInfoConcern + include Steps::ThreatMetrixStepHelper before_action :confirm_two_factor_authenticated before_action :confirm_ssn_step_complete @@ -85,11 +86,7 @@ def update private def prev_url - if IdentityConfig.store.doc_auth_ssn_controller_enabled - idv_ssn_url - else - idv_doc_auth_url - end + idv_ssn_url end def analytics_arguments @@ -118,11 +115,6 @@ def confirm_ssn_step_complete redirect_to prev_url end - def confirm_profile_not_already_confirmed - return unless idv_session.profile_confirmation == true - redirect_to idv_review_url - end - def current_flow_step_counts user_session['idv/doc_auth_flow_step_counts'] ||= {} user_session['idv/doc_auth_flow_step_counts'].default = 0 diff --git a/app/controllers/idv_controller.rb b/app/controllers/idv_controller.rb index 1968d8c0156..905043cbd51 100644 --- a/app/controllers/idv_controller.rb +++ b/app/controllers/idv_controller.rb @@ -1,7 +1,6 @@ class IdvController < ApplicationController include IdvSession include AccountReactivationConcern - include InheritedProofingConcern include FraudReviewConcern before_action :confirm_two_factor_authenticated @@ -34,7 +33,6 @@ def activated def verify_identity analytics.idv_intro_visit - return redirect_to idv_inherited_proofing_url if inherited_proofing? redirect_to idv_doc_auth_url end diff --git a/app/controllers/openid_connect/authorization_controller.rb b/app/controllers/openid_connect/authorization_controller.rb index 05ecba3247f..4a1e52f4cbe 100644 --- a/app/controllers/openid_connect/authorization_controller.rb +++ b/app/controllers/openid_connect/authorization_controller.rb @@ -6,7 +6,6 @@ class AuthorizationController < ApplicationController include SecureHeadersConcern include AuthorizationCountConcern include BillableEventTrackable - include InheritedProofingConcern include FraudReviewConcern before_action :build_authorize_form_from_params, only: [:index] diff --git a/app/forms/idv/api_image_upload_form.rb b/app/forms/idv/api_image_upload_form.rb index 073513e7f67..ae81692fd04 100644 --- a/app/forms/idv/api_image_upload_form.rb +++ b/app/forms/idv/api_image_upload_form.rb @@ -34,11 +34,15 @@ def submit doc_pii_response = validate_pii_from_doc(client_response) if client_response.success? end - determine_response( + response = determine_response( form_response: form_response, client_response: client_response, doc_pii_response: doc_pii_response, ) + + track_event(response) + + response end private @@ -98,25 +102,6 @@ def validate_pii_from_doc(client_response) analytics.idv_doc_auth_submitted_pii_validation(**response.to_h) - pii_from_doc = response.pii_from_doc || {} - stored_image_result = store_encrypted_images_if_required - - irs_attempts_api_tracker.idv_document_upload_submitted( - success: response.success?, - document_state: pii_from_doc[:state], - document_number: pii_from_doc[:state_id_number], - document_issued: pii_from_doc[:state_id_issued], - document_expiration: pii_from_doc[:state_id_expiration], - document_front_image_filename: stored_image_result&.front_filename, - document_back_image_filename: stored_image_result&.back_filename, - document_image_encryption_key: stored_image_result&.encryption_key, - first_name: pii_from_doc[:first_name], - last_name: pii_from_doc[:last_name], - date_of_birth: pii_from_doc[:dob], - address: pii_from_doc[:address1], - failure_reason: response.errors&.except(:hints)&.presence, - ) - store_pii(client_response) if client_response.success? && response.success? response @@ -309,5 +294,26 @@ def throttle throttle_type: :idv_doc_auth, ) end + + def track_event(response) + pii_from_doc = response.pii_from_doc || {} + stored_image_result = store_encrypted_images_if_required + + irs_attempts_api_tracker.idv_document_upload_submitted( + success: response.success?, + document_state: pii_from_doc[:state], + document_number: pii_from_doc[:state_id_number], + document_issued: pii_from_doc[:state_id_issued], + document_expiration: pii_from_doc[:state_id_expiration], + document_front_image_filename: stored_image_result&.front_filename, + document_back_image_filename: stored_image_result&.back_filename, + document_image_encryption_key: stored_image_result&.encryption_key, + first_name: pii_from_doc[:first_name], + last_name: pii_from_doc[:last_name], + date_of_birth: pii_from_doc[:dob], + address: pii_from_doc[:address1], + failure_reason: response.errors&.except(:hints)&.presence, + ) + end end end diff --git a/app/forms/idv/inherited_proofing/base_form.rb b/app/forms/idv/inherited_proofing/base_form.rb deleted file mode 100644 index 16e144690ef..00000000000 --- a/app/forms/idv/inherited_proofing/base_form.rb +++ /dev/null @@ -1,120 +0,0 @@ -module Idv - module InheritedProofing - class BaseForm - include ActiveModel::Model - - class << self - def model_name - ActiveModel::Name.new(self, nil, namespaced_model_name) - end - - def namespaced_model_name - self.to_s.gsub('::', '') - end - end - - private_class_method :namespaced_model_name - - attr_reader :payload_hash - - def initialize(payload_hash:) - raise ArgumentError, 'payload_hash is blank?' if payload_hash.blank? - raise ArgumentError, 'payload_hash is not a Hash' unless payload_hash.is_a? Hash - - @payload_hash = payload_hash.dup - - populate_field_data - end - - def submit - FormResponse.new( - success: valid?, - errors: errors, - extra: {}, - ) - end - - # Perhaps overkill, but a mapper service of some kind, not bound to this class, - # that takes into consideration context, may be more suitable. In the meantime, - # simply return a Hash suitable to place into flow_session[:pii_from_user] in - # our inherited proofing flow steps. - def user_pii - raise NotImplementedError, 'Override this method and return a user PII Hash' - end - - private - - attr_writer :payload_hash - - # Populates our field data from the payload hash. - def populate_field_data - payload_field_info.each do |field_name, field_info| - # Ignore fields we're not interested in. - next unless respond_to? field_name - - value = payload_hash.dig( - *[field_info[:namespace], - field_info[:field_name]].flatten.compact, - ) - public_send("#{field_name}=", value) - end - end - - def payload_field_info - @payload_field_info ||= field_name_info_from payload_hash: payload_hash - end - - # This method simply navigates the payload hash received and creates qualified - # hash key names that can be used to verify/map to our field names in this model. - # This can be used to qualify nested hash fields and saves us some headaches - # if there are nested field names with the same name: - # - # given: - # - # payload_hash = { - # first_name: 'first_name', - # ... - # address: { - # street: '', - # ... - # } - # } - # - # field_name_info_from(payload_hash: payload_hash) #=> - # - # { - # :first_name=>{:field_name=>:first_name, :namespace=>[]}, - # ... - # :address_street=>{:field_name=>:street, :namespace=>[:address]}, - # ... - # } - # - # The generated, qualified field names expected to map to our model, because we named - # them as such. - # - # :field_name is the actual, unqualified field name found in the payload hash sent. - # :namespace is the hash key by which :field_name can be found in the payload hash - # if need be. - def field_name_info_from(payload_hash:, namespace: [], field_name_info: {}) - payload_hash.each do |key, value| - if value.is_a? Hash - field_name_info_from payload_hash: value, namespace: namespace << key, - field_name_info: field_name_info - namespace.pop - next - end - - namespace = namespace.dup - if namespace.blank? - field_name_info[key] = { field_name: key, namespace: namespace } - else - field_name_info["#{namespace.split.join('_')}_#{key}".to_sym] = - { field_name: key, namespace: namespace } - end - end - - field_name_info - end - end - end -end diff --git a/app/forms/idv/inherited_proofing/va/form.rb b/app/forms/idv/inherited_proofing/va/form.rb deleted file mode 100644 index 97878154d4a..00000000000 --- a/app/forms/idv/inherited_proofing/va/form.rb +++ /dev/null @@ -1,68 +0,0 @@ -module Idv - module InheritedProofing - module Va - class Form < Idv::InheritedProofing::BaseForm - REQUIRED_FIELDS = %i[first_name - last_name - birth_date - ssn - address_street - address_zip].freeze - OPTIONAL_FIELDS = %i[phone - address_street2 - address_city - address_state - address_country - service_error].freeze - FIELDS = (REQUIRED_FIELDS + OPTIONAL_FIELDS).freeze - - attr_accessor(*FIELDS) - validate :add_service_error, if: :service_error? - validates(*REQUIRED_FIELDS, presence: true, unless: :service_error?) - - def submit - extra = {} - extra = { service_error: service_error } if service_error? - - FormResponse.new( - success: validate, - errors: errors, - extra: extra, - ) - end - - def user_pii - raise 'User PII is invalid' unless valid? - - user_pii = {} - user_pii[:first_name] = first_name - user_pii[:last_name] = last_name - user_pii[:dob] = birth_date - user_pii[:ssn] = ssn - user_pii[:phone] = phone - user_pii[:address1] = address_street - user_pii[:city] = address_city - user_pii[:state] = address_state - user_pii[:zipcode] = address_zip - user_pii - end - - def service_error? - service_error.present? - end - - private - - def add_service_error - errors.add( - :service_provider, - # Use a "safe" error message for the model in case it's displayed - # to the user at any point. - I18n.t('inherited_proofing.errors.service_provider.communication'), - type: :service_error, - ) - end - end - end - end -end diff --git a/app/javascript/packages/build-sass/package.json b/app/javascript/packages/build-sass/package.json index 2fa932f297d..b338c3c190d 100644 --- a/app/javascript/packages/build-sass/package.json +++ b/app/javascript/packages/build-sass/package.json @@ -22,7 +22,7 @@ }, "homepage": "https://github.com/18f/identity-idp", "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.21.5", "chokidar": "^3.5.3", "lightningcss": "^1.16.1", "sass-embedded": "^1.56.1" diff --git a/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.spec.tsx b/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.spec.tsx index 1d645eb35cf..4df8ac8d242 100644 --- a/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.spec.tsx +++ b/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.spec.tsx @@ -105,7 +105,7 @@ describe('InPersonLocationStep', () => { await findByText('in_person_proofing.body.location.po_search.search_button'), ); - const error = await findByText('idv.failure.exceptions.internal_error'); + const error = await findByText('idv.failure.exceptions.post_office_search_error'); expect(error).to.exist(); }); }); @@ -134,7 +134,7 @@ describe('InPersonLocationStep', () => { await findByText('in_person_proofing.body.location.po_search.search_button'), ); - const error = await findByText('idv.failure.exceptions.internal_error'); + const error = await findByText('idv.failure.exceptions.post_office_search_error'); expect(error).to.exist(); }); }); diff --git a/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.tsx b/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.tsx index fab33db7c16..bbb685efc59 100644 --- a/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.tsx +++ b/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.tsx @@ -91,7 +91,7 @@ function InPersonLocationPostOfficeSearchStep({ onChange, toPreviousStep, regist <> {apiError && ( - {t('idv.failure.exceptions.internal_error')} + {t('idv.failure.exceptions.post_office_search_error')} )} {t('in_person_proofing.headings.po_search.location')} diff --git a/app/javascript/packs/mock-device-profiling.tsx b/app/javascript/packs/mock-device-profiling.tsx index 7bdc6bb478e..dbadddc9ec8 100644 --- a/app/javascript/packs/mock-device-profiling.tsx +++ b/app/javascript/packs/mock-device-profiling.tsx @@ -62,7 +62,7 @@ const CHAOS_OPTIONS: ChaosOption[] = [ ]; function MockDeviceProfilingOptions() { - const [selectedValue, setSelectedValue] = useState(''); + const [selectedValue, setSelectedValue] = useState('pass'); useEffect(() => { if (selectedValue === 'chaotic') { diff --git a/app/jobs/get_usps_proofing_results_job.rb b/app/jobs/get_usps_proofing_results_job.rb index 16dcb4d0aec..7327917e345 100644 --- a/app/jobs/get_usps_proofing_results_job.rb +++ b/app/jobs/get_usps_proofing_results_job.rb @@ -13,48 +13,6 @@ class GetUspsProofingResultsJob < ApplicationJob queue_as :long_running - def email_analytics_attributes(enrollment) - { - timestamp: Time.zone.now, - user_id: enrollment.user_id, - service_provider: enrollment.issuer, - wait_until: mail_delivery_params(enrollment.proofed_at)[:wait_until], - } - end - - def enrollment_analytics_attributes(enrollment, complete:) - { - enrollment_code: enrollment.enrollment_code, - enrollment_id: enrollment.id, - minutes_since_last_status_check: enrollment.minutes_since_last_status_check, - minutes_since_last_status_update: enrollment.minutes_since_last_status_update, - minutes_since_established: enrollment.minutes_since_established, - minutes_to_completion: complete ? enrollment.minutes_since_established : nil, - issuer: enrollment.issuer, - } - end - - def response_analytics_attributes(response) - return { response_present: false } unless response.present? - - { - fraud_suspected: response['fraudSuspected'], - primary_id_type: response['primaryIdType'], - secondary_id_type: response['secondaryIdType'], - failure_reason: response['failureReason'], - transaction_end_date_time: parse_usps_timestamp(response['transactionEndDateTime']), - transaction_start_date_time: parse_usps_timestamp(response['transactionStartDateTime']), - status: response['status'], - assurance_level: response['assuranceLevel'], - proofing_post_office: response['proofingPostOffice'], - proofing_city: response['proofingCity'], - proofing_state: response['proofingState'], - scan_count: response['scanCount'], - response_message: response['responseMessage'], - response_present: true, - } - end - def perform(_now) return true unless IdentityConfig.store.in_person_proofing_enabled @@ -66,6 +24,7 @@ def perform(_now) enrollments_in_progress: 0, enrollments_passed: 0, } + reprocess_delay_minutes = IdentityConfig.store. get_usps_proofing_results_job_reprocess_delay_minutes enrollments = InPersonEnrollment.needs_usps_status_check( @@ -93,53 +52,99 @@ def perform(_now) attr_accessor :enrollment_outcomes DEFAULT_EMAIL_DELAY_IN_HOURS = 1 + REQUEST_DELAY_IN_SECONDS = IdentityConfig.store. + get_usps_proofing_results_job_request_delay_milliseconds / MILLISECONDS_PER_SECOND - def check_enrollments(enrollments) - request_delay_in_seconds = IdentityConfig.store. - get_usps_proofing_results_job_request_delay_milliseconds / MILLISECONDS_PER_SECOND - proofer = UspsInPersonProofing::Proofer.new + def proofer + @proofer ||= UspsInPersonProofing::Proofer.new + end + def check_enrollments(enrollments) + last_enrollment_index = enrollments.length - 1 enrollments.each_with_index do |enrollment, idx| - # Add a unique ID for enrollments that don't have one - enrollment.update(unique_id: enrollment.usps_unique_id) if enrollment.unique_id.blank? + check_enrollment(enrollment) + # Sleep briefly after each call to USPS + sleep REQUEST_DELAY_IN_SECONDS if idx < last_enrollment_index + end + end - status_check_attempted_at = Time.zone.now - enrollment_outcomes[:enrollments_checked] += 1 - response = nil + def check_enrollment(enrollment) + # Add a unique ID for enrollments that don't have one + enrollment.update(unique_id: enrollment.usps_unique_id) if enrollment.unique_id.blank? - begin - response = proofer.request_proofing_results( - enrollment.unique_id, enrollment.enrollment_code - ) - rescue Faraday::BadRequestError => err - # 400 status code. This is used for some status updates and some common client errors - handle_bad_request_error(err, enrollment) - rescue Faraday::ClientError, Faraday::ServerError => err - # 4xx or 5xx status code. These are unexpected but will have some sort of - # response body that we can try to log data from - handle_client_or_server_error(err, enrollment) - rescue Faraday::Error => err - # Timeouts, failed connections, parsing errors, and other HTTP errors. These - # generally won't have a response body - handle_faraday_error(err, enrollment) - rescue StandardError => err - handle_standard_error(err, enrollment) - else - process_enrollment_response(enrollment, response) - ensure - # Record the attempt to update the enrollment - enrollment.update(status_check_attempted_at: status_check_attempted_at) - end + status_check_attempted_at = Time.zone.now + enrollment_outcomes[:enrollments_checked] += 1 + response = nil - # Sleep for a while before we attempt to make another call to USPS - sleep request_delay_in_seconds if idx < enrollments.length - 1 - end + response = proofer.request_proofing_results( + enrollment.unique_id, enrollment.enrollment_code + ) + rescue Faraday::BadRequestError => err + # 400 status code. This is used for some status updates and some common client errors + handle_bad_request_error(err, enrollment) + rescue Faraday::ClientError, Faraday::ServerError => err + # 4xx or 5xx status code. These are unexpected but will have some sort of + # response body that we can try to log data from + handle_client_or_server_error(err, enrollment) + rescue Faraday::Error => err + # Timeouts, failed connections, parsing errors, and other HTTP errors. These + # generally won't have a response body + handle_faraday_error(err, enrollment) + rescue StandardError => err + handle_standard_error(err, enrollment) + else + process_enrollment_response(enrollment, response) + ensure + # Record the attempt to update the enrollment + enrollment.update(status_check_attempted_at: status_check_attempted_at) end def analytics(user: AnonymousUser.new) Analytics.new(user: user, request: nil, session: {}, sp: nil) end + def email_analytics_attributes(enrollment) + { + timestamp: Time.zone.now, + user_id: enrollment.user_id, + service_provider: enrollment.issuer, + wait_until: mail_delivery_params(enrollment.proofed_at)[:wait_until], + } + end + + def enrollment_analytics_attributes(enrollment, complete:) + { + enrollment_code: enrollment.enrollment_code, + enrollment_id: enrollment.id, + minutes_since_last_status_check: enrollment.minutes_since_last_status_check, + minutes_since_last_status_update: enrollment.minutes_since_last_status_update, + minutes_since_established: enrollment.minutes_since_established, + minutes_to_completion: complete ? enrollment.minutes_since_established : nil, + issuer: enrollment.issuer, + } + end + + def response_analytics_attributes(response) + return { response_present: false } unless response.present? + + { + fraud_suspected: response['fraudSuspected'], + primary_id_type: response['primaryIdType'], + secondary_id_type: response['secondaryIdType'], + failure_reason: response['failureReason'], + transaction_end_date_time: parse_usps_timestamp(response['transactionEndDateTime']), + transaction_start_date_time: parse_usps_timestamp(response['transactionStartDateTime']), + status: response['status'], + assurance_level: response['assuranceLevel'], + proofing_post_office: response['proofingPostOffice'], + proofing_city: response['proofingCity'], + proofing_state: response['proofingState'], + scan_count: response['scanCount'], + response_message: response['responseMessage'], + response_present: true, + } + end + def handle_bad_request_error(err, enrollment) response_body = err.response_body response_message = response_body&.[]('responseMessage') diff --git a/app/jobs/inherited_proofing_job.rb b/app/jobs/inherited_proofing_job.rb deleted file mode 100644 index c4f8d5f79e9..00000000000 --- a/app/jobs/inherited_proofing_job.rb +++ /dev/null @@ -1,21 +0,0 @@ -class InheritedProofingJob < ApplicationJob - include Idv::InheritedProofing::ServiceProviderServices - include JobHelpers::StaleJobHelper - - queue_as :default - - discard_on JobHelpers::StaleJobHelper::StaleJobError - - def perform(service_provider, service_provider_data, uuid) - document_capture_session = DocumentCaptureSession.find_by(uuid: uuid) - - payload_hash = inherited_proofing_service_for( - service_provider, - service_provider_data: service_provider_data, - ).execute - - raise_stale_job! if stale_job?(enqueued_at) - - document_capture_session.store_proofing_result(payload_hash) - end -end diff --git a/app/presenters/completions_presenter.rb b/app/presenters/completions_presenter.rb index 47fbe124289..f913f3ab9d2 100644 --- a/app/presenters/completions_presenter.rb +++ b/app/presenters/completions_presenter.rb @@ -50,6 +50,11 @@ def heading if ial2_requested? if consent_has_expired? I18n.t('titles.sign_up.completion_consent_expired_ial2') + elsif reverified_after_consent? + I18n.t( + 'titles.sign_up.completion_reverified_consent', + sp: sp_name, + ) else I18n.t('titles.sign_up.completion_ial2', sp: sp_name) end @@ -71,6 +76,11 @@ def intro 'help_text.requested_attributes.ial2_consent_reminder_html', sp: sp_name, ) + elsif reverified_after_consent? + I18n.t( + 'help_text.requested_attributes.ial2_reverified_consent_info', + sp: sp_name, + ) else I18n.t( 'help_text.requested_attributes.ial2_intro_html', @@ -121,6 +131,10 @@ def consent_has_expired? completion_context == :consent_expired end + def reverified_after_consent? + completion_context == :reverified_after_consent + end + def displayable_attribute_keys sorted_attribute_mapping = if ial2_requested? SORTED_IAL2_ATTRIBUTE_MAPPING diff --git a/app/presenters/idv/inherited_proofing/inherited_proofing_presenter.rb b/app/presenters/idv/inherited_proofing/inherited_proofing_presenter.rb deleted file mode 100644 index cc74d37ba1c..00000000000 --- a/app/presenters/idv/inherited_proofing/inherited_proofing_presenter.rb +++ /dev/null @@ -1,29 +0,0 @@ -module Idv - module InheritedProofing - class InheritedProofingPresenter - attr_reader :service_provider - - def initialize(service_provider:) - @service_provider = service_provider - end - - def learn_more_phone_or_mail_url - 'https://www.va.gov/resources/managing-your-vagov-profile/' if va_inherited_proofing? - end - - def get_help_url - 'https://www.va.gov/resources/managing-your-vagov-profile/' if va_inherited_proofing? - end - - def contact_support_url - 'https://www.va.gov/resources/managing-your-vagov-profile/' if va_inherited_proofing? - end - - private - - def va_inherited_proofing? - service_provider == :va - end - end - end -end diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 802ba0e2ad7..83fff0f245f 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -567,68 +567,6 @@ def idv_verify_in_person_troubleshooting_option_clicked(flow_path:, in_person_ct ) end - # @param [Boolean] success - # @param [Hash] errors - # @param [String] flow_path Document capture path ("hybrid" or "standard") - # @param [String] step Document step user is on - # The user agrees to allow us to access their data via api call - def idv_inherited_proofing_agreement_submitted(success:, errors:, flow_path:, step:, **extra) - track_event( - 'IdV: inherited proofing agreement submitted', - success: success, - errors: errors, - flow_path: flow_path, - step: step, - **extra, - ) - end - - # @param [String] flow_path Document capture path ("hybrid" or "standard") - # @param [String] step Document step user is on - # The user visited the inherited proofing agreement page - def idv_inherited_proofing_agreement_visited(flow_path:, step:, **extra) - track_event( - 'IdV: inherited proofing agreement visited', - flow_path: flow_path, - step: step, - **extra, - ) - end - - # @param [Boolean] success - # @param [Hash] errors - # @param [String] flow_path Document capture path ("hybrid" or "standard") - # @param [String] step Document step user is on - # The user chooses to begin the inherited proofing process - def idv_inherited_proofing_get_started_submitted(success:, errors:, flow_path:, step:, **extra) - track_event( - 'IdV: inherited proofing get started submitted', - success: success, - errors: errors, - flow_path: flow_path, - step: step, - **extra, - ) - end - - # @param [String] flow_path Document capture path ("hybrid" or "standard") - # @param [String] step Document step user is on - # The user visited the inherited proofing get started step - def idv_inherited_proofing_get_started_visited(flow_path:, step:, **extra) - track_event( - 'IdV: inherited proofing get started visited', - flow_path: flow_path, - step: step, - **extra, - ) - end - - # Retry retrieving the user PII in the case where the first attempt fails - # in the agreement step, and the user initiates a "retry". - def idv_inherited_proofing_redo_retrieve_user_info_submitted(**extra) - track_event('IdV: inherited proofing retry retrieve user information submitted', **extra) - end - # @param [String] flow_path Document capture path ("hybrid" or "standard") # @param [String] in_person_cta_variant Variant testing bucket label # The user visited the in person proofing location step diff --git a/app/services/browser_support.rb b/app/services/browser_support.rb new file mode 100644 index 00000000000..c3847acd7d3 --- /dev/null +++ b/app/services/browser_support.rb @@ -0,0 +1,65 @@ +class BrowserSupport + @cache = LruRedux::Cache.new(1_000) + + BROWSERSLIST_TO_BROWSER_MAP = { + and_chr: ->(browser, version) { browser.android? && browser.chrome?(version) }, + and_uc: ->(browser, version) { browser.android? && browser.uc_browser?(version) }, + chrome: ->(browser, version) { browser.chrome?(version) }, + edge: ->(browser, version) { browser.edge?(version) }, + firefox: ->(browser, version) { browser.firefox?(version) }, + ios_saf: ->(browser, version) { browser.ios? && browser.safari?(version) }, + op_mini: ->(browser, version) { browser.opera_mini?(version) }, + opera: ->(browser, version) { browser.opera?(version) }, + safari: ->(browser, version) { browser.safari?(version) }, + samsung: ->(browser, version) { browser.samsung_browser?(version) }, + }.with_indifferent_access.freeze + + class << self + def supported?(user_agent) + return false if user_agent.nil? + return true if browser_support_config.nil? + + cache.getset(user_agent) do + matchers.any? { |matcher| matcher.call(BrowserCache.parse(user_agent)) } + end + end + + def clear_cache! + cache.clear + @matchers = nil + remove_instance_variable(:@browser_support_config) if defined?(@browser_support_config) + end + + private + + attr_reader :cache + + def matchers + @matchers ||= browser_support_config.flat_map do |config_entry| + key, version = config_entry.split(' ', 2) + browser_matcher = BROWSERSLIST_TO_BROWSER_MAP[key] + next [] if !browser_matcher + + low_version, _high_version = version.split('-', 2) + low_version = nil if !numeric?(low_version) + proc { |browser| browser_matcher.call(browser, low_version && ">= #{low_version}") } + end + end + + def numeric?(value) + Float(value) + true + rescue ArgumentError + false + end + + def browser_support_config + return @browser_support_config if defined?(@browser_support_config) + @browser_support_config = begin + JSON.parse(File.read(Rails.root.join('browsers.json'))) + rescue JSON::ParserError, Errno::ENOENT + nil + end + end + end +end diff --git a/app/services/flow/base_flow.rb b/app/services/flow/base_flow.rb index 6f5779794e7..8eb95e01a69 100644 --- a/app/services/flow/base_flow.rb +++ b/app/services/flow/base_flow.rb @@ -36,18 +36,22 @@ def step_handler(step) steps[step] || actions[step] end + def step_handler_instance(step) + @step_handler_instances ||= {} + @step_handler_instances[step] ||= step_handler(step)&.new(self) + end + def handle(step) @flow_session[:error_message] = nil - handler = step_handler(step) + handler = step_handler_instance(step) return failure("Unhandled step #{step}") unless handler wrap_send(handler) end def extra_view_variables(step) - handler = step_handler(step) + handler = step_handler_instance(step) return failure("Unhandled step #{step}") unless handler - obj = handler.new(self) - obj.extra_view_variables + handler.extra_view_variables end def extra_analytics_properties @@ -61,9 +65,8 @@ def flow_path private def wrap_send(handler) - obj = handler.new(self) - value = obj.base_call - form_response(obj, value) + value = handler.base_call + form_response(handler, value) end def form_response(obj, value) diff --git a/app/services/idv/actions/cancel_update_ssn_action.rb b/app/services/idv/actions/cancel_update_ssn_action.rb deleted file mode 100644 index d8b7b52b82d..00000000000 --- a/app/services/idv/actions/cancel_update_ssn_action.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Idv - module Actions - class CancelUpdateSsnAction < Idv::Steps::DocAuthBaseStep - def self.analytics_submitted_event - :idv_doc_auth_cancel_update_ssn_submitted - end - - def call - mark_step_complete(:ssn) if flow_session.dig(:pii_from_doc, :ssn) - end - end - end -end diff --git a/app/services/idv/actions/inherited_proofing/redo_retrieve_user_info_action.rb b/app/services/idv/actions/inherited_proofing/redo_retrieve_user_info_action.rb deleted file mode 100644 index a9fd7ed2c2e..00000000000 --- a/app/services/idv/actions/inherited_proofing/redo_retrieve_user_info_action.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Idv - module Actions - module InheritedProofing - class RedoRetrieveUserInfoAction < Idv::Steps::InheritedProofing::VerifyWaitStepShow - class << self - def analytics_submitted_event - :idv_inherited_proofing_redo_retrieve_user_info_submitted - end - end - - def call - enqueue_job unless api_call_already_in_progress? - - super - end - end - end - end -end diff --git a/app/services/idv/flows/doc_auth_flow.rb b/app/services/idv/flows/doc_auth_flow.rb index da38c8bfb53..539a1b707d6 100644 --- a/app/services/idv/flows/doc_auth_flow.rb +++ b/app/services/idv/flows/doc_auth_flow.rb @@ -9,7 +9,6 @@ class DocAuthFlow < Flow::BaseFlow link_sent: Idv::Steps::LinkSentStep, email_sent: Idv::Steps::EmailSentStep, document_capture: Idv::Steps::DocumentCaptureStep, - ssn: Idv::Steps::SsnStep, }.freeze STEP_INDICATOR_STEPS = [ @@ -34,9 +33,7 @@ class DocAuthFlow < Flow::BaseFlow ACTIONS = { cancel_send_link: Idv::Actions::CancelSendLinkAction, cancel_link_sent: Idv::Actions::CancelLinkSentAction, - cancel_update_ssn: Idv::Actions::CancelUpdateSsnAction, redo_address: Idv::Actions::RedoAddressAction, - redo_ssn: Idv::Actions::RedoSsnAction, redo_document_capture: Idv::Actions::RedoDocumentCaptureAction, verify_document_status: Idv::Actions::VerifyDocumentStatusAction, }.freeze diff --git a/app/services/idv/flows/inherited_proofing_flow.rb b/app/services/idv/flows/inherited_proofing_flow.rb deleted file mode 100644 index 2fc56d71103..00000000000 --- a/app/services/idv/flows/inherited_proofing_flow.rb +++ /dev/null @@ -1,42 +0,0 @@ -module Idv - module Flows - class InheritedProofingFlow < Flow::BaseFlow - STEPS = { - get_started: Idv::Steps::InheritedProofing::GetStartedStep, - agreement: Idv::Steps::InheritedProofing::AgreementStep, - verify_wait: Idv::Steps::InheritedProofing::VerifyWaitStep, - verify_info: Idv::Steps::InheritedProofing::VerifyInfoStep, - }.freeze - - OPTIONAL_SHOW_STEPS = { - verify_wait: Idv::Steps::InheritedProofing::VerifyWaitStepShow, - }.freeze - - STEP_INDICATOR_STEPS = [ - { name: :getting_started }, - { name: :verify_info }, - { name: :secure_account }, - ].freeze - - ACTIONS = { - redo_retrieve_user_info: Idv::Actions::InheritedProofing::RedoRetrieveUserInfoAction, - }.freeze - - attr_reader :idv_session - - def initialize(controller, session, name) - @idv_session = self.class.session_idv(session) - super(controller, STEPS, ACTIONS, session[name]) - - @flow_session ||= {} - @flow_session[:pii_from_user] ||= { uuid: current_user.uuid } - applicant = @idv_session['applicant'] || {} - @flow_session[:pii_from_user] = @flow_session[:pii_from_user].to_h.merge(applicant) - end - - def self.session_idv(session) - session[:idv] ||= {} - end - end - end -end diff --git a/app/services/idv/inherited_proofing/service_provider_forms.rb b/app/services/idv/inherited_proofing/service_provider_forms.rb deleted file mode 100644 index 51740b47ba3..00000000000 --- a/app/services/idv/inherited_proofing/service_provider_forms.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Idv - module InheritedProofing - module ServiceProviderForms - def inherited_proofing_form_for(service_provider, payload_hash:) - if service_provider == :va - return Idv::InheritedProofing::Va::Form.new payload_hash: payload_hash - end - - raise 'Inherited proofing form could not be identified' - end - end - end -end diff --git a/app/services/idv/inherited_proofing/service_provider_services.rb b/app/services/idv/inherited_proofing/service_provider_services.rb deleted file mode 100644 index 0dfadedc88b..00000000000 --- a/app/services/idv/inherited_proofing/service_provider_services.rb +++ /dev/null @@ -1,24 +0,0 @@ -module Idv - module InheritedProofing - module ServiceProviderServices - def inherited_proofing_service_for(service_provider, service_provider_data:) - inherited_proofing_service_class_for(service_provider).new(service_provider_data) - end - - def inherited_proofing_service_class_for(service_provider) - unless IdentityConfig.store.inherited_proofing_enabled - raise 'Inherited Proofing is not enabled' - end - - if service_provider == :va - if IdentityConfig.store.va_inherited_proofing_mock_enabled - return Idv::InheritedProofing::Va::Mocks::Service - end - return Idv::InheritedProofing::Va::Service - end - - raise 'Inherited proofing service class could not be identified' - end - end - end -end diff --git a/app/services/idv/inherited_proofing/service_providers.rb b/app/services/idv/inherited_proofing/service_providers.rb deleted file mode 100644 index 7bb6b6ae8e9..00000000000 --- a/app/services/idv/inherited_proofing/service_providers.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Idv - module InheritedProofing - # This module is temporary until InheritedProofing has a viable way - # of identifying/communicating identifying Service Providers - # modules - module ServiceProviders - VA = :va - SERVICE_PROVIDERS = %i[VA] - end - end -end diff --git a/app/services/idv/inherited_proofing/va/mocks/service.rb b/app/services/idv/inherited_proofing/va/mocks/service.rb deleted file mode 100644 index e1150a9528d..00000000000 --- a/app/services/idv/inherited_proofing/va/mocks/service.rb +++ /dev/null @@ -1,59 +0,0 @@ -module Idv - module InheritedProofing - module Va - module Mocks - class Service - VALID_AUTH_CODE = 'mocked-auth-code-for-testing'.freeze - - attr_reader :auth_code - - PAYLOAD_HASH = { - first_name: 'Fakey', - last_name: 'Fakerson', - address: { - street: '123 Fake St', - street2: 'Apt 235', - city: 'Faketown', - state: 'WA', - country: nil, - zip: '98037', - }, - phone: '2063119187', - birth_date: '2022-1-31', - ssn: '123456789', - mhv_data: { - mhvId: 99999999, - identityProofedMethod: 'IPA', - identityDocumentExist: true, - identityProofingDate: '2020-12-14', - identityDocumentInfo: { - primaryIdentityDocumentNumber: '88888888', - primaryIdentityDocumentType: 'StateIssuedId', - primaryIdentityDocumentCountry: 'United States', - primaryIdentityDocumentExpirationDate: '2222-03-30', - }, - }, - }.freeze - - ERROR_HASH = { - service_error: 'the server responded with status 401', - }.freeze - - def initialize(service_provider_data) - @auth_code = service_provider_data[:auth_code] - end - - def execute - invalid_auth_code ? ERROR_HASH : PAYLOAD_HASH - end - - private - - def invalid_auth_code - @auth_code != VALID_AUTH_CODE - end - end - end - end - end -end diff --git a/app/services/idv/inherited_proofing/va/service.rb b/app/services/idv/inherited_proofing/va/service.rb deleted file mode 100644 index 7e7c76a7e28..00000000000 --- a/app/services/idv/inherited_proofing/va/service.rb +++ /dev/null @@ -1,111 +0,0 @@ -module Idv - module InheritedProofing - module Va - # Encapsulates request, response, error handling, validation, etc. for calling - # the VA service to gain PII for a particular user that will be subsequently - # used to proof the user using inherited proofing. - class Service - BASE_URI = IdentityConfig.store.inherited_proofing_va_base_url - - attr_reader :auth_code - - def initialize(service_provider_data) - @auth_code = service_provider_data[:auth_code] - end - - # Calls the endpoint and returns the decrypted response. - def execute - raise 'The provided auth_code is blank?' if auth_code.blank? - - begin - response = request - return payload_to_hash decrypt_payload(response) if response.status == 200 - - service_error(not_200_service_error(response.status)) - rescue => error - service_error(error.message) - end - end - - private - - def service_error(message) - { service_error: message } - end - - def not_200_service_error(http_status) - # Under certain circumstances, Faraday may return a nil http status. - # https://lostisland.github.io/faraday/middleware/raise-error - if http_status.blank? - http_status = 'unavailable' - http_status_description = 'unavailable' - else - http_status_description = Rack::Utils::HTTP_STATUS_CODES[http_status] - end - "The service provider API returned an http status other than 200: " \ - "#{http_status} (#{http_status_description})" - end - - def request - connection.get(request_uri) { |req| req.headers = request_headers } - end - - def connection - Faraday.new do |conn| - conn.options.timeout = request_timeout - conn.options.read_timeout = request_timeout - conn.options.open_timeout = request_timeout - conn.options.write_timeout = request_timeout - conn.request :instrumentation, name: 'inherited_proofing.va' - - # raises errors on 4XX or 5XX responses - conn.response :raise_error - end - end - - def request_timeout - @request_timeout ||= IdentityConfig.store.doc_auth_s3_request_timeout - end - - def request_uri - @request_uri ||= "#{ URI(BASE_URI) }/inherited_proofing/user_attributes" - end - - def request_headers - { Authorization: "Bearer #{jwt_token}" } - end - - def jwt_token - JWT.encode(jwt_payload, private_key, jwt_encryption) - end - - def jwt_payload - { inherited_proofing_auth: auth_code, exp: jwt_expires } - end - - def private_key - @private_key ||= AppArtifacts.store.oidc_private_key - end - - def jwt_encryption - 'RS256' - end - - def jwt_expires - 1.day.from_now.to_i - end - - def decrypt_payload(response) - payload = JSON.parse(response.body)['data'] - JWE.decrypt(payload, private_key) if payload - end - - def payload_to_hash(decrypted_payload, default: nil) - return default unless decrypted_payload.present? - - JSON.parse(decrypted_payload, symbolize_names: true) - end - end - end - end -end diff --git a/app/services/idv/session.rb b/app/services/idv/session.rb index 6bea72abdef..91024db8fe8 100644 --- a/app/services/idv/session.rb +++ b/app/services/idv/session.rb @@ -96,10 +96,6 @@ def clear user_session.delete(:idv) end - def phone_confirmed? - vendor_phone_confirmation == true && user_phone_confirmation == true - end - def associate_in_person_enrollment_with_profile return unless in_person_enrollment? && current_user.establishing_in_person_enrollment current_user.establishing_in_person_enrollment.update(profile: profile) @@ -117,14 +113,6 @@ def create_gpo_entry @gpo_otp = confirmation_maker.otp end - def alive? - session.present? - end - - def address_mechanism_chosen? - vendor_phone_confirmation == true || address_verification_mechanism == 'gpo' - end - def user_phone_confirmation_session session_value = session[:user_phone_confirmation_session] return if session_value.blank? @@ -135,6 +123,30 @@ def user_phone_confirmation_session=(new_user_phone_confirmation_session) session[:user_phone_confirmation_session] = new_user_phone_confirmation_session.to_h end + def in_person_enrollment? + ProofingComponent.find_by(user: current_user)&.document_check == Idp::Constants::Vendors::USPS + end + + def verify_info_step_complete? + resolution_successful && profile_confirmation + end + + def address_step_complete? + if address_verification_mechanism == 'gpo' + true + else + phone_confirmed? + end + end + + def address_mechanism_chosen? + vendor_phone_confirmation == true || address_verification_mechanism == 'gpo' + end + + def phone_confirmed? + vendor_phone_confirmation == true && user_phone_confirmation == true + end + def invalidate_steps_after_ssn! # Guard against unvalidated attributes from in-person flow in review controller session[:applicant] = nil @@ -184,10 +196,6 @@ def build_profile_maker(user_password) ) end - def in_person_enrollment? - ProofingComponent.find_by(user: current_user)&.document_check == Idp::Constants::Vendors::USPS - end - def threatmetrix_failed_and_needs_review? failed_and_needs_review = true ok_no_review_needed = false diff --git a/app/services/idv/steps/doc_auth_base_step.rb b/app/services/idv/steps/doc_auth_base_step.rb index bbc63bf9e7d..fecf65db7e4 100644 --- a/app/services/idv/steps/doc_auth_base_step.rb +++ b/app/services/idv/steps/doc_auth_base_step.rb @@ -34,7 +34,8 @@ def extract_pii_from_doc(response, store_in_session: false) flow_session[:had_barcode_read_failure] = response.attention_with_barcode? if store_in_session - flow_session[:pii_from_doc] = flow_session[:pii_from_doc].to_h.merge(pii_from_doc) + flow_session[:pii_from_doc] ||= {} + flow_session[:pii_from_doc].merge!(pii_from_doc) idv_session.delete('applicant') end track_document_state(pii_from_doc[:state]) @@ -109,10 +110,6 @@ def document_capture_session_uuid_key :document_capture_session_uuid end - def inherited_proofing_verify_step_document_capture_session_uuid_key - :inherited_proofing_verify_step_document_capture_session_uuid - end - def verify_step_document_capture_session_uuid_key :idv_verify_step_document_capture_session_uuid end diff --git a/app/services/idv/steps/document_capture_step.rb b/app/services/idv/steps/document_capture_step.rb index ba0fe7059a4..c0d825651bf 100644 --- a/app/services/idv/steps/document_capture_step.rb +++ b/app/services/idv/steps/document_capture_step.rb @@ -16,7 +16,7 @@ def self.analytics_submitted_event def call handle_stored_result if !FeatureManagement.document_capture_async_uploads_enabled? - exit_flow_state_machine if IdentityConfig.store.doc_auth_ssn_controller_enabled + exit_flow_state_machine end def extra_view_variables @@ -42,7 +42,6 @@ def extra_view_variables def exit_flow_state_machine flow_session[:flow_path] = @flow.flow_path - redirect_to idv_ssn_url if @flow.instance_of?(Idv::Flows::DocAuthFlow) end def native_camera_ab_testing_variables diff --git a/app/services/idv/steps/in_person/address_step.rb b/app/services/idv/steps/in_person/address_step.rb index 43c58c72e87..7d642124708 100644 --- a/app/services/idv/steps/in_person/address_step.rb +++ b/app/services/idv/steps/in_person/address_step.rb @@ -20,18 +20,37 @@ def call def extra_view_variables { - pii: flow_session[:pii_from_user], - updating_address: flow_session[:pii_from_user].has_key?(:address1), + form:, + pii:, + updating_address:, } end private - def form_submit - Idv::InPerson::AddressForm.new.submit( - permit(*Idv::InPerson::AddressForm::ATTRIBUTES), + def updating_address + flow_session[:pii_from_user].has_key?(:address1) + end + + def pii + data = flow_session[:pii_from_user] + data = data.merge(flow_params) if params.has_key?(:in_person_address) + data.deep_symbolize_keys + end + + def flow_params + params.require(:in_person_address).permit( + *Idv::InPerson::AddressForm::ATTRIBUTES, ) end + + def form + @form ||= Idv::InPerson::AddressForm.new + end + + def form_submit + form.submit(flow_params) + end end 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 3afeb3f0d4c..84b7c15ecd7 100644 --- a/app/services/idv/steps/in_person/state_id_step.rb +++ b/app/services/idv/steps/in_person/state_id_step.rb @@ -23,32 +23,56 @@ def call end def extra_view_variables - parsed_dob = nil - if flow_session[:pii_from_user][:dob].instance_of? String - parsed_dob = Date.parse flow_session[:pii_from_user][:dob] - end - { - pii: flow_session[:pii_from_user], - parsed_dob: parsed_dob, - updating_state_id: flow_session[:pii_from_user].has_key?(:first_name), + form:, + pii:, + parsed_dob:, + updating_state_id:, } end private - def form_submit - Idv::StateIdForm.new(current_user).submit( - permit( - *Idv::StateIdForm::ATTRIBUTES, - dob: [ - :month, - :day, - :year, - ], - ), + def updating_state_id + flow_session[:pii_from_user].has_key?(:first_name) + end + + def parsed_dob + form_dob = pii[:dob] + if form_dob.instance_of?(String) + dob_str = form_dob + elsif form_dob.instance_of?(Hash) + dob_str = MemorableDateComponent.extract_date_param(form_dob) + end + Date.parse(dob_str) unless dob_str.nil? + rescue StandardError + # Catch date parsing errors + end + + def pii + data = flow_session[:pii_from_user] + data = data.merge(flow_params) if params.has_key?(:state_id) + data.deep_symbolize_keys + end + + def flow_params + params.require(:state_id).permit( + *Idv::StateIdForm::ATTRIBUTES, + dob: [ + :month, + :day, + :year, + ], ) end + + def form + @form ||= Idv::StateIdForm.new(current_user) + end + + def form_submit + form.submit(flow_params) + end end end end diff --git a/app/services/idv/steps/inherited_proofing/agreement_step.rb b/app/services/idv/steps/inherited_proofing/agreement_step.rb deleted file mode 100644 index b49e3d7ec7d..00000000000 --- a/app/services/idv/steps/inherited_proofing/agreement_step.rb +++ /dev/null @@ -1,32 +0,0 @@ -module Idv - module Steps - module InheritedProofing - class AgreementStep < VerifyBaseStep - include UserPiiJobInitiator - - delegate :controller, :idv_session, to: :@flow - STEP_INDICATOR_STEP = :getting_started - - def self.analytics_visited_event - :idv_inherited_proofing_agreement_visited - end - - def self.analytics_submitted_event - :idv_inherited_proofing_agreement_submitted - end - - def call - enqueue_job - end - - def form_submit - Idv::ConsentForm.new.submit(consent_form_params) - end - - def consent_form_params - params.require(:inherited_proofing).permit(:ial2_consent_given) - end - end - end - end -end diff --git a/app/services/idv/steps/inherited_proofing/get_started_step.rb b/app/services/idv/steps/inherited_proofing/get_started_step.rb deleted file mode 100644 index 7afedc41f9b..00000000000 --- a/app/services/idv/steps/inherited_proofing/get_started_step.rb +++ /dev/null @@ -1,20 +0,0 @@ -module Idv - module Steps - module InheritedProofing - class GetStartedStep < InheritedProofingBaseStep - STEP_INDICATOR_STEP = :getting_started - - def self.analytics_visited_event - :idv_inherited_proofing_get_started_visited - end - - def self.analytics_submitted_event - :idv_inherited_proofing_get_started_submitted - end - - def call - end - end - end - end -end diff --git a/app/services/idv/steps/inherited_proofing/user_pii_job_initiator.rb b/app/services/idv/steps/inherited_proofing/user_pii_job_initiator.rb deleted file mode 100644 index 3d39f3b0599..00000000000 --- a/app/services/idv/steps/inherited_proofing/user_pii_job_initiator.rb +++ /dev/null @@ -1,35 +0,0 @@ -module Idv - module Steps - module InheritedProofing - module UserPiiJobInitiator - private - - def enqueue_job - return if api_call_already_in_progress? - - create_document_capture_session( - inherited_proofing_verify_step_document_capture_session_uuid_key, - ).tap do |doc_capture_session| - doc_capture_session.create_proofing_session - - InheritedProofingJob.perform_later( - controller.inherited_proofing_service_provider, - controller.inherited_proofing_service_provider_data, - doc_capture_session.uuid, - ) - end - end - - def api_call_already_in_progress? - DocumentCaptureSession.find_by( - uuid: flow_session[inherited_proofing_verify_step_document_capture_session_uuid_key], - ).present? - end - - def delete_async - flow_session.delete(inherited_proofing_verify_step_document_capture_session_uuid_key) - end - end - end - end -end diff --git a/app/services/idv/steps/inherited_proofing/user_pii_managable.rb b/app/services/idv/steps/inherited_proofing/user_pii_managable.rb deleted file mode 100644 index 08521d7cbc3..00000000000 --- a/app/services/idv/steps/inherited_proofing/user_pii_managable.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Idv - module Steps - module InheritedProofing - module UserPiiManagable - def inherited_proofing_save_user_pii_to_session!(user_pii) - inherited_proofing_save_session!(user_pii) - inherited_proofing_skip_steps! - end - - private - - def inherited_proofing_save_session!(user_pii) - flow_session[:pii_from_user] = - flow_session[:pii_from_user].to_h.merge(user_pii) - # This is unnecessary, but added for completeness. Any subsequent FLOWS we - # might splice into will pull from idv_session['applicant'] and merge into - # flow_session[:pii_from_user] anyhow in their #initialize(r); any subsequent - # STEP FLOWS we might splice into will populate idv_session['applicant'] and - # ultimately get merged in to flow_session[:pii_from_user] as well. - idv_session['applicant'] = flow_session[:pii_from_user] - end - - def inherited_proofing_skip_steps! - idv_session['profile_confirmation'] = true - idv_session['vendor_phone_confirmation'] = false - idv_session['user_phone_confirmation'] = false - idv_session['address_verification_mechanism'] = 'phone' - idv_session['resolution_successful'] = 'phone' - end - end - end - end -end diff --git a/app/services/idv/steps/inherited_proofing/user_pii_retrievable.rb b/app/services/idv/steps/inherited_proofing/user_pii_retrievable.rb deleted file mode 100644 index 36323660088..00000000000 --- a/app/services/idv/steps/inherited_proofing/user_pii_retrievable.rb +++ /dev/null @@ -1,39 +0,0 @@ -module Idv - module Steps - module InheritedProofing - module UserPiiRetrievable - def inherited_proofing_user_pii - inherited_proofing_info[0] - end - - def inherited_proofing_form_response - inherited_proofing_info[1] - end - - private - - # This needs error handling. - def inherited_proofing_info - return @inherited_proofing_info if defined? @inherited_proofing_info - - payload_hash = inherited_proofing_service.execute.dup - form = inherited_proofing_form(payload_hash) - form_response = form.submit - - user_pii = {} - user_pii = form.user_pii if form_response.success? - - @inherited_proofing_info = [user_pii, form_response] - end - - def inherited_proofing_service - controller.inherited_proofing_service - end - - def inherited_proofing_form(payload_hash) - controller.inherited_proofing_form payload_hash - end - end - end - end -end diff --git a/app/services/idv/steps/inherited_proofing/verify_info_step.rb b/app/services/idv/steps/inherited_proofing/verify_info_step.rb deleted file mode 100644 index b54f4e26e91..00000000000 --- a/app/services/idv/steps/inherited_proofing/verify_info_step.rb +++ /dev/null @@ -1,31 +0,0 @@ -module Idv - module Steps - module InheritedProofing - class VerifyInfoStep < InheritedProofingBaseStep - STEP_INDICATOR_STEP = :verify_info - - def self.analytics_visited_event - :idv_doc_auth_verify_visited - end - - def self.analytics_submitted_event - :idv_doc_auth_verify_submitted - end - - def call - end - - def extra_view_variables - { - pii: pii, - step_url: method(:idv_inherited_proofing_step_url), - } - end - - def pii - flow_session[:pii_from_user] - end - end - end - end -end diff --git a/app/services/idv/steps/inherited_proofing/verify_wait_step.rb b/app/services/idv/steps/inherited_proofing/verify_wait_step.rb deleted file mode 100644 index 856d20a55e7..00000000000 --- a/app/services/idv/steps/inherited_proofing/verify_wait_step.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Idv - module Steps - module InheritedProofing - class VerifyWaitStep < InheritedProofingBaseStep - STEP_INDICATOR_STEP = :getting_started - - def self.analytics_visited_event - :idv_doc_auth_verify_wait_step_visited - end - - def call; end - end - end - end -end diff --git a/app/services/idv/steps/inherited_proofing/verify_wait_step_show.rb b/app/services/idv/steps/inherited_proofing/verify_wait_step_show.rb deleted file mode 100644 index 611c8d8731d..00000000000 --- a/app/services/idv/steps/inherited_proofing/verify_wait_step_show.rb +++ /dev/null @@ -1,114 +0,0 @@ -module Idv - module Steps - module InheritedProofing - class VerifyWaitStepShow < VerifyBaseStep - include UserPiiJobInitiator - include UserPiiManagable - include Idv::InheritedProofing::ServiceProviderForms - delegate :controller, :idv_session, to: :@flow - - STEP_INDICATOR_STEP = :getting_started - - def self.analytics_optional_step_event - :idv_doc_auth_optional_verify_wait_submitted - end - - def call - poll_with_meta_refresh(IdentityConfig.store.poll_rate_for_verify_in_seconds) - - process_async_state(async_state) - end - - private - - def process_async_state(current_async_state) - return if current_async_state.in_progress? - - if current_async_state.none? - mark_step_incomplete(:agreement) - elsif current_async_state.missing? - flash[:error] = I18n.t('idv.failure.timeout') - delete_async - mark_step_incomplete(:agreement) - @flow.analytics.idv_proofing_resolution_result_missing - elsif current_async_state.done? - async_state_done(current_async_state) - end - end - - def async_state - return ProofingSessionAsyncResult.none if dcs_uuid.nil? - return ProofingSessionAsyncResult.missing if document_capture_session.nil? - return ProofingSessionAsyncResult.missing if api_job_result.nil? - - api_job_result - end - - def async_state_done(_current_async_state) - service_provider = controller.inherited_proofing_service_provider - - form = inherited_proofing_form_for( - service_provider, - payload_hash: api_job_result[:result], - ) - form_response = form.submit - - delete_async - - if form_response.success? - inherited_proofing_save_user_pii_to_session!(form.user_pii) - mark_step_complete(:verify_wait) - elsif throttle.throttled? - idv_failure(form_response) - else - mark_step_complete(:agreement) - idv_failure(form_response) - end - - form_response - end - - def dcs_uuid - @dcs_uuid ||= - flow_session[inherited_proofing_verify_step_document_capture_session_uuid_key] - end - - def document_capture_session - @document_capture_session ||= DocumentCaptureSession.find_by(uuid: dcs_uuid) - end - - def api_job_result - document_capture_session.load_proofing_result - end - - # Base class overrides - - def throttle - @throttle ||= Throttle.new( - user: current_user, - throttle_type: :inherited_proofing, - ) - end - - def idv_failure_log_throttled - @flow.analytics.throttler_rate_limit_triggered( - throttle_type: throttle.throttle_type, - step_name: self.class.name, - ) - end - - def throttled_url - idv_inherited_proofing_errors_failure_url(flow: :inherited_proofing) - end - - def exception_url - idv_inherited_proofing_errors_failure_url(flow: :inherited_proofing) - end - - def warning_url - idv_inherited_proofing_errors_no_information_url(flow: :inherited_proofing) - end - end - end - end -end diff --git a/app/services/idv/steps/inherited_proofing_base_step.rb b/app/services/idv/steps/inherited_proofing_base_step.rb deleted file mode 100644 index 43c18f90889..00000000000 --- a/app/services/idv/steps/inherited_proofing_base_step.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Idv - module Steps - class InheritedProofingBaseStep < Flow::BaseStep - delegate :controller, :idv_session, to: :@flow - - def initialize(flow) - super(flow, :inherited_proofing) - end - - private - - def sp_session - session.fetch(:sp, {}) - end - end - end -end diff --git a/app/services/idv/steps/link_sent_step.rb b/app/services/idv/steps/link_sent_step.rb index 6a70a8815a5..d4bd0e779e7 100644 --- a/app/services/idv/steps/link_sent_step.rb +++ b/app/services/idv/steps/link_sent_step.rb @@ -28,16 +28,11 @@ def extra_view_variables private - def exit_flow_state_machine - flow_session[:flow_path] = @flow.flow_path - redirect_to idv_ssn_url - end - def handle_document_verification_success(get_results_response) save_proofing_components extract_pii_from_doc(get_results_response, store_in_session: true) mark_steps_complete - exit_flow_state_machine if IdentityConfig.store.doc_auth_ssn_controller_enabled + flow_session[:flow_path] = @flow.flow_path end def render_document_capture_cancelled diff --git a/app/services/idv/steps/ssn_step.rb b/app/services/idv/steps/ssn_step.rb deleted file mode 100644 index 97239d09048..00000000000 --- a/app/services/idv/steps/ssn_step.rb +++ /dev/null @@ -1,61 +0,0 @@ -module Idv - module Steps - class SsnStep < DocAuthBaseStep - STEP_INDICATOR_STEP = :verify_info - - include ThreatMetrixStepHelper - - def self.analytics_visited_event - :idv_doc_auth_ssn_visited - end - - def self.analytics_submitted_event - :idv_doc_auth_ssn_submitted - end - - def call - return invalid_state_response if invalid_state? - - flow_session[:pii_from_doc][:ssn] = ssn - - @flow.irs_attempts_api_tracker.idv_ssn_submitted( - ssn: ssn, - ) - - idv_session.delete('applicant') - - flow_session[:flow_path] = @flow.flow_path - end - - def extra_view_variables - { - updating_ssn: updating_ssn, - **threatmetrix_view_variables, - } - end - - private - - def form_submit - Idv::SsnFormatForm.new(current_user).submit(permit(:ssn)) - end - - def invalid_state_response - mark_step_incomplete(:document_capture) - FormResponse.new(success: false) - end - - def ssn - flow_params[:ssn] - end - - def invalid_state? - flow_session[:pii_from_doc].nil? - end - - def updating_ssn - flow_session.dig(:pii_from_doc, :ssn).present? - end - end - end -end diff --git a/app/services/idv/steps/threat_metrix_step_helper.rb b/app/services/idv/steps/threat_metrix_step_helper.rb index a5b7d98d7f4..62eae9de24a 100644 --- a/app/services/idv/steps/threat_metrix_step_helper.rb +++ b/app/services/idv/steps/threat_metrix_step_helper.rb @@ -46,6 +46,26 @@ def threatmetrix_iframe_url(session_id) session_id: session_id, ) end + + def log_irs_tmx_fraud_check_event(result) + return unless IdentityConfig.store.irs_attempt_api_track_tmx_fraud_check_event + return unless FeatureManagement.proofing_device_profiling_collecting_enabled? + success = result[:review_status] == 'pass' + + if !success && (tmx_summary_reason_code = result.dig( + :response_body, + :tmx_summary_reason_code, + )) + failure_reason = { + tmx_summary_reason_code: tmx_summary_reason_code, + } + end + + irs_attempts_api_tracker.idv_tmx_fraud_check( + success: success, + failure_reason: failure_reason, + ) + end end end end diff --git a/app/services/irs_attempts_api/tracker_events.rb b/app/services/irs_attempts_api/tracker_events.rb index a0b2e8ae9cf..4338a16b002 100644 --- a/app/services/irs_attempts_api/tracker_events.rb +++ b/app/services/irs_attempts_api/tracker_events.rb @@ -281,6 +281,18 @@ def idv_ssn_submitted(ssn:) ) end + # @param [Boolean] success + # @param [Hash>] failure_reason + # This event will capture the result of the TMX fraud check + # during Identity Verification + def idv_tmx_fraud_check(success:, failure_reason: nil) + track_event( + :idv_tmx_fraud_check, + success: success, + failure_reason: failure_reason, + ) + end + # @param [String] throttle_context - Either single-session or multi-session # Track when idv verification is rate limited during idv flow def idv_verification_rate_limited(throttle_context:) diff --git a/app/services/proofing/aamva/response/verification_response.rb b/app/services/proofing/aamva/response/verification_response.rb index a5784fcd868..c1e748f9f6c 100644 --- a/app/services/proofing/aamva/response/verification_response.rb +++ b/app/services/proofing/aamva/response/verification_response.rb @@ -33,9 +33,17 @@ def initialize(http_response) @missing_attributes = [] @verification_results = {} @http_response = http_response + @errors = [] + handle_http_error handle_soap_error + parse_response + + return if @errors.empty? + + error_message = @errors.join('; ') + raise VerificationError.new(error_message) end def reasons @@ -63,8 +71,7 @@ def success? def handle_http_error status = http_response.status - return if status == 200 - raise VerificationError, "Unexpected status code in response: #{status}" + @errors.push("Unexpected status code in response: #{status}") if status != 200 end def handle_missing_attribute(attribute_name) @@ -76,13 +83,13 @@ def handle_soap_error error_handler = SoapErrorHandler.new(http_response) return unless error_handler.error_present? - msg = error_handler.error_message - raise ::Proofing::TimeoutError, msg if mva_timeout?(msg) - raise VerificationError, msg + @errors.push(error_handler.error_message) end def node_for_match_indicator(match_indicator_name) REXML::XPath.first(rexml_document, "//#{match_indicator_name}") + rescue REXML::ParseException + nil end def parse_response @@ -104,7 +111,7 @@ def parse_response end def rexml_document - @rexml_document ||= REXML::Document.new(http_response.body) + return @rexml_document ||= REXML::Document.new(http_response.body) end def mva_timeout?(error_message) diff --git a/app/services/proofing/aamva/soap_error_handler.rb b/app/services/proofing/aamva/soap_error_handler.rb index e498bf0be24..b8bbaf704f2 100644 --- a/app/services/proofing/aamva/soap_error_handler.rb +++ b/app/services/proofing/aamva/soap_error_handler.rb @@ -9,6 +9,9 @@ class SoapErrorHandler def initialize(http_response) @document = REXML::Document.new(http_response.body) parse_error_message + rescue REXML::ParseException => e + @error_present = true + @error_message = e.to_s end def error_present? diff --git a/app/services/proofing/lexis_nexis/response.rb b/app/services/proofing/lexis_nexis/response.rb index 1037ddd2108..b81301ec63d 100644 --- a/app/services/proofing/lexis_nexis/response.rb +++ b/app/services/proofing/lexis_nexis/response.rb @@ -11,8 +11,6 @@ def initialize(response) end def verification_errors - return {} if verification_status == 'passed' - verification_error_parser.parsed_errors end diff --git a/app/services/proofing/lexis_nexis/verification_error_parser.rb b/app/services/proofing/lexis_nexis/verification_error_parser.rb index 3a5ce087e8d..dc80ab5176b 100644 --- a/app/services/proofing/lexis_nexis/verification_error_parser.rb +++ b/app/services/proofing/lexis_nexis/verification_error_parser.rb @@ -10,7 +10,7 @@ def initialize(response_body) end def parsed_errors - { base: base_error_message }.merge(product_error_messages) + { base: base_error_message }.merge(product_error_messages).compact end def verification_status @@ -27,9 +27,7 @@ def parse_base_error_message if verification_status == 'error' error_information = body.fetch('Information', {}).to_json "Response error with code '#{error_code}': #{error_information}" - elsif error_code.nil? - 'Verification failed without a reason code' - else + elsif error_code.present? "Verification failed with code: '#{error_code}'" end end diff --git a/app/services/proofing/mock/ddp_mock_client.rb b/app/services/proofing/mock/ddp_mock_client.rb index a656d9deb44..68a5d71a6a7 100644 --- a/app/services/proofing/mock/ddp_mock_client.rb +++ b/app/services/proofing/mock/ddp_mock_client.rb @@ -52,7 +52,7 @@ def proof(applicant) end def review_status(session_id:) - device_status = DeviceProfilingBackend.new.profiling_result(session_id) + device_status = DeviceProfilingBackend.new.profiling_result(session_id) || 'pass' case device_status when 'no_result' diff --git a/app/services/throttle.rb b/app/services/throttle.rb index 04adb68310d..b5c4a992c78 100644 --- a/app/services/throttle.rb +++ b/app/services/throttle.rb @@ -211,10 +211,6 @@ def self.load_throttle_config max_attempts: IdentityConfig.store.phone_confirmation_max_attempts, attempt_window: IdentityConfig.store.phone_confirmation_max_attempt_window_in_minutes, }, - inherited_proofing: { - max_attempts: IdentityConfig.store.inherited_proofing_max_attempts, - attempt_window: IdentityConfig.store.inherited_proofing_max_attempt_window_in_minutes, - }, phone_otp: { max_attempts: IdentityConfig.store.otp_delivery_blocklist_maxretry + 1, attempt_window: IdentityConfig.store.otp_delivery_blocklist_findtime, diff --git a/app/services/usps_in_person_proofing/enrollment_helper.rb b/app/services/usps_in_person_proofing/enrollment_helper.rb index 861fbcb31ba..01f9cb99b37 100644 --- a/app/services/usps_in_person_proofing/enrollment_helper.rb +++ b/app/services/usps_in_person_proofing/enrollment_helper.rb @@ -50,10 +50,10 @@ def create_usps_enrollment(enrollment, pii) applicant = UspsInPersonProofing::Applicant.new( { unique_id: unique_id, - first_name: pii['first_name'], - last_name: pii['last_name'], - address: address, - city: pii['city'], + first_name: transliterate(pii['first_name']), + last_name: transliterate(pii['last_name']), + address: transliterate(address), + city: transliterate(pii['city']), state: pii['state'], zip_code: pii['zipcode'], email: 'no-reply@login.gov', @@ -98,6 +98,21 @@ def handle_standard_error(err, enrollment) def analytics(user: AnonymousUser.new) Analytics.new(user: user, request: nil, session: {}, sp: nil) end + + def transliterate(value) + return value unless IdentityConfig.store.usps_ipp_transliteration_enabled + + result = transliterator.transliterate(value) + if result.unsupported_chars.present? + result.original + else + result.transliterated + end + end + + def transliterator + Transliterator.new + end end end end diff --git a/app/services/usps_in_person_proofing/transliterator.rb b/app/services/usps_in_person_proofing/transliterator.rb new file mode 100644 index 00000000000..dfe7315758e --- /dev/null +++ b/app/services/usps_in_person_proofing/transliterator.rb @@ -0,0 +1,45 @@ +module UspsInPersonProofing + class Transliterator + # This is the default. May not be able to override this in current version. + REPLACEMENT = '?'.freeze + + # Container to hold the results of transliteration + TransliterationResult = Struct.new( + # Was the value different after transliteration? + :changed?, + # Original value submtted for transliteration + :original, + # Transliterated value + :transliterated, + # Characters from the original that could not be transliterated + :unsupported_chars, + keyword_init: true, + ) + + # Transliterate values for usage in the USPS API. This will additionally strip/reduce + # excess whitespace and check for special characters that are unsupported by transliteration. + # Additional validation may be necessary depending on the specific field being transliterated. + # + # @param [String,#to_s] value The value to transliterate for USPS + # @return [TransliterationResult] The transliterated value + def transliterate(value) + stripped = value.to_s.gsub(/\s+/, ' ').strip + transliterated = I18n.transliterate(stripped, locale: :en) + + unsupported_chars = [] + unless stripped.count(REPLACEMENT) == transliterated.count(REPLACEMENT) + transliterated.chars.zip(stripped.chars).each do |val, stripped| + unsupported_chars.append(stripped) if val == REPLACEMENT && stripped != REPLACEMENT + end + end + + # Using struct instead of exception here to reduce likelihood of logging PII + TransliterationResult.new( + changed?: value != transliterated, + original: value, + transliterated: transliterated, + unsupported_chars: unsupported_chars, + ) + end + end +end diff --git a/app/views/idv/document_capture/show.html.erb b/app/views/idv/document_capture/show.html.erb new file mode 100644 index 00000000000..79002a05d0b --- /dev/null +++ b/app/views/idv/document_capture/show.html.erb @@ -0,0 +1,14 @@ +<%= render( + 'idv/shared/document_capture', + flow_session: flow_session, + flow_path: 'standard', + sp_name: decorated_session.sp_name, + failure_to_proof_url: idv_doc_auth_return_to_sp_url, + front_image_upload_url: front_image_upload_url, + back_image_upload_url: back_image_upload_url, + acuant_sdk_upgrade_a_b_testing_enabled: acuant_sdk_upgrade_a_b_testing_enabled, + use_alternate_sdk: use_alternate_sdk, + acuant_version: acuant_version, + in_person_cta_variant_testing_enabled: in_person_cta_variant_testing_enabled, + in_person_cta_variant_active: in_person_cta_variant_active, + ) %> \ No newline at end of file diff --git a/app/views/idv/in_person/address.html.erb b/app/views/idv/in_person/address.html.erb index 6097d0f4cc1..c7a9640b682 100644 --- a/app/views/idv/in_person/address.html.erb +++ b/app/views/idv/in_person/address.html.erb @@ -18,8 +18,8 @@

<%= simple_form_for( - :doc_auth, url: url_for, method: 'PUT', - html: { autocomplete: 'off' } + form, url: url_for, method: 'PUT', + html: { autocomplete: 'off' } ) do |f| %> <%= render ValidatedFieldComponent.new( form: f, diff --git a/app/views/idv/in_person/state_id.html.erb b/app/views/idv/in_person/state_id.html.erb index 3ba30bd9127..1d05c544bbb 100644 --- a/app/views/idv/in_person/state_id.html.erb +++ b/app/views/idv/in_person/state_id.html.erb @@ -25,7 +25,7 @@ <% end %> -<%= simple_form_for :doc_auth, +<%= simple_form_for form, url: url_for, method: 'put', html: { autocomplete: 'off', class: 'margin-y-5' } do |f| %> diff --git a/app/views/idv/inherited_proofing/_cancel.html.erb b/app/views/idv/inherited_proofing/_cancel.html.erb deleted file mode 100644 index 52947a558bc..00000000000 --- a/app/views/idv/inherited_proofing/_cancel.html.erb +++ /dev/null @@ -1,7 +0,0 @@ -<%# -locals: -* step: Current step, used in analytics logging. -%> -<%= render PageFooterComponent.new do %> - <%= link_to cancel_link_text, idv_inherited_proofing_cancel_path(step: local_assigns[:step]) %> -<% end %> diff --git a/app/views/idv/inherited_proofing/_verify.html.erb b/app/views/idv/inherited_proofing/_verify.html.erb deleted file mode 100644 index 3efa83cbd3b..00000000000 --- a/app/views/idv/inherited_proofing/_verify.html.erb +++ /dev/null @@ -1,70 +0,0 @@ -<%# -locals: - pii - user's information - step_url - generator for step URLs -%> -<% title t('inherited_proofing.headings.verify_information') %> - -<%= render PageHeadingComponent.new.with_content(t('titles.idv.verify_info')) %> -
-
-
-
-
<%= t('idv.form.first_name') %>:
-
<%= pii[:first_name] %>
-
-
-
<%= t('idv.form.last_name') %>:
-
<%= pii[:last_name] %>
-
-
-
<%= t('idv.form.dob') %>:
-
<%= pii[:dob] %>
-
-
-
-
-
-
-
<%= t('idv.form.address1') %>:
-
<%= pii[:address1] %>
-
- <% if pii[:address2].present? %> -
-
<%= t('idv.form.address2') %>:
-
<%= pii[:address2] %>
-
- <% end %> -
-
<%= t('idv.form.city') %>:
-
<%= pii[:city] %>
-
-
-
<%= t('idv.form.state') %>:
-
<%= pii[:state] %>
-
-
-
<%= t('idv.form.zipcode') %>:
-
<%= pii[:zipcode] %>
-
-
-
-
-
- <%= t('idv.form.ssn') %>: - <%= render( - 'shared/masked_text', - text: SsnFormatter.format(pii[:ssn]), - masked_text: SsnFormatter.format_masked(pii[:ssn]), - accessible_masked_text: t( - 'idv.accessible_labels.masked_ssn', - first_number: pii[:ssn][0], - last_number: pii[:ssn][-1], - ), - toggle_label: t('forms.ssn.show'), - ) %> -
-
-
- -<% javascript_packs_tag_once 'form-steps-wait' %> diff --git a/app/views/idv/inherited_proofing/agreement.html.erb b/app/views/idv/inherited_proofing/agreement.html.erb deleted file mode 100644 index e74f11a9206..00000000000 --- a/app/views/idv/inherited_proofing/agreement.html.erb +++ /dev/null @@ -1,42 +0,0 @@ -<% title t('inherited_proofing.headings.lets_go') %> - -<%= render AlertComponent.new( - type: :error, - class: [ - 'js-consent-form-alert', - 'margin-bottom-4', - flow_session[:error_message].blank? && 'display-none', - ].select(&:present?), - message: flow_session[:error_message].presence || t('errors.inherited_proofing.consent_form'), - ) %> - -<%= render PageHeadingComponent.new.with_content(t('inherited_proofing.headings.lets_go')) %> -

<%= t('inherited_proofing.info.lets_go') %>

-

<%= t('inherited_proofing.headings.verify_identity') %>

-

<%= t('inherited_proofing.info.verify_identity') %>

-

<%= t('inherited_proofing.headings.secure_account') %>

-

<%= t('inherited_proofing.info.secure_account') %>

- -<%= simple_form_for( - :inherited_proofing, - url: url_for, - method: 'put', - html: { autocomplete: 'off', class: 'margin-top-2 margin-bottom-5 js-consent-continue-form' }, - ) do |f| %> - <%= render ValidatedFieldComponent.new( - form: f, - name: :ial2_consent_given, - as: :boolean, - label: capture do %> - <%= t('inherited_proofing.instructions.consent', app_name: APP_NAME) %> - <%= new_window_link_to( - t('inherited_proofing.instructions.learn_more'), - MarketingSite.security_and_privacy_practices_url, - ) %> - <% end, - required: true, - ) %> - <%= f.submit t('inherited_proofing.buttons.continue'), class: 'margin-top-4' %> -<% end %> - -<%= render 'idv/inherited_proofing/cancel', step: 'agreement' %> diff --git a/app/views/idv/inherited_proofing/get_started.html.erb b/app/views/idv/inherited_proofing/get_started.html.erb deleted file mode 100644 index 93bd0b9552f..00000000000 --- a/app/views/idv/inherited_proofing/get_started.html.erb +++ /dev/null @@ -1,58 +0,0 @@ -<% title t('inherited_proofing.headings.welcome') %> - -<%= render JavascriptRequiredComponent.new( - header: t('idv.welcome.no_js_header'), - intro: t('idv.welcome.no_js_intro', sp_name: decorated_session.sp_name || t('inherited_proofing.info.no_sp_name')), - ) do %> - <%= render PageHeadingComponent.new.with_content(t('inherited_proofing.headings.welcome')) %> -

- <%= t('inherited_proofing.info.welcome_html', sp_name: decorated_session.sp_name || t('inherited_proofing.info.no_sp_name')) %> -

- -

- <%= t('inherited_proofing.instructions.verify_requirements') %> - <%= new_window_link_to(t('inherited_proofing.troubleshooting.options.learn_more_phone_or_mail'), 'https://www.va.gov/resources/managing-your-vagov-profile/') %> -

- - <%= simple_form_for :inherited_proofing, - url: url_for, - method: 'put', - html: { autocomplete: 'off', class: 'margin-y-5' } do |f| %> - <%= f.submit t('inherited_proofing.buttons.continue') %> - <% end %> - - <%= render( - 'shared/troubleshooting_options', - heading_tag: :h3, - heading: t('inherited_proofing.troubleshooting.headings.missing_required_items'), - options: [ - { - url: @presenter.learn_more_phone_or_mail_url, - text: t('inherited_proofing.troubleshooting.options.learn_more_phone_or_mail'), - new_tab: true, - }, - { - url: @presenter.get_help_url, - text: t( - 'inherited_proofing.troubleshooting.options.get_help', - sp_name: decorated_session.sp_name, - ), - new_tab: true, - }, - ].select(&:present?), - ) %> - -

<%= t('inherited_proofing.instructions.privacy') %>

-

- <%= t( - 'inherited_proofing.info.privacy_html', - app_name: APP_NAME, - link: new_window_link_to( - t('inherited_proofing.instructions.learn_more'), - MarketingSite.security_and_privacy_practices_url, - ), - ) %> -

- - <%= render 'shared/cancel', link: idv_inherited_proofing_cancel_path(step: 'get_started') %> -<% end %> diff --git a/app/views/idv/inherited_proofing/verify_info.html.erb b/app/views/idv/inherited_proofing/verify_info.html.erb deleted file mode 100644 index ee7321025aa..00000000000 --- a/app/views/idv/inherited_proofing/verify_info.html.erb +++ /dev/null @@ -1,26 +0,0 @@ -<%= render 'verify', pii: pii, step_url: step_url %> - -<%= simple_form_for :inherited_proofing, - url: url_for, - method: 'put', - html: { autocomplete: 'off', class: 'margin-y-5' } do |f| %> - <%= f.submit t('inherited_proofing.buttons.continue') %> -<% end %> - -<%= render( - 'shared/troubleshooting_options', - heading_tag: :h3, - heading: t('inherited_proofing.troubleshooting.headings.need_help_updating'), - options: [ - { - url: @presenter.get_help_url, - text: t( - 'inherited_proofing.troubleshooting.options.get_help', - sp_name: decorated_session.sp_name, - ), - new_tab: true, - }, - ].select(&:present?), - ) %> - -<%= render 'idv/inherited_proofing/cancel', step: 'verify_info' %> diff --git a/app/views/idv/inherited_proofing/verify_wait.html.erb b/app/views/idv/inherited_proofing/verify_wait.html.erb deleted file mode 100644 index 2936142e739..00000000000 --- a/app/views/idv/inherited_proofing/verify_wait.html.erb +++ /dev/null @@ -1,17 +0,0 @@ -<%= content_for(:meta_refresh) { @meta_refresh.to_s } %> -<% title t('inherited_proofing.headings.retrieval') %> - -<%= render PageHeadingComponent.new.with_content(t('inherited_proofing.headings.retrieval')) %> - -
- <%= image_tag( - asset_url('shield-spinner.gif'), - alt: '', - width: 120, - height: 120, - ) %> -
- -

<%= t('inherited_proofing.info.retrieval_time') %>

-

<%= t('inherited_proofing.info.retrieval_thanks') %>

-<%= render 'idv/inherited_proofing/cancel', step: 'verify_info' %> diff --git a/app/views/idv/inherited_proofing_cancellations/destroy.html.erb b/app/views/idv/inherited_proofing_cancellations/destroy.html.erb deleted file mode 100644 index 116524f15b5..00000000000 --- a/app/views/idv/inherited_proofing_cancellations/destroy.html.erb +++ /dev/null @@ -1,8 +0,0 @@ -<% title t('titles.inherited_proofing.cancelled') %> - -<%= render StatusPageComponent.new(status: :error) do |c| %> - <% c.header { t('inherited_proofing.cancel.headings.confirmation.hybrid') } %> - -

<%= t('inherited_proofing.cancel.instructions.switch_back') %>

- <%= image_tag(asset_url('inherited_proofing/switch.png'), width: 193, height: 109, alt: t('inherited_proofing.cancel.instructions.switch_back_image')) %> -<% end %> diff --git a/app/views/idv/inherited_proofing_cancellations/new.html.erb b/app/views/idv/inherited_proofing_cancellations/new.html.erb deleted file mode 100644 index 159f8363027..00000000000 --- a/app/views/idv/inherited_proofing_cancellations/new.html.erb +++ /dev/null @@ -1,54 +0,0 @@ -<% title t('titles.inherited_proofing.cancellation_prompt') %> - -<%= render StatusPageComponent.new(status: :warning) do |c| %> - <% c.header { t('inherited_proofing.cancel.headings.prompt.standard') } %> - -

<%= t('inherited_proofing.cancel.headings.start_over') %>

- -

<%= t('inherited_proofing.cancel.description.start_over') %>

- -
- <%= render ButtonComponent.new( - action: ->(**tag_options, &block) do - button_to(idv_inherited_proofing_session_path(step: @flow_step), **tag_options, &block) - end, - method: :delete, - big: true, - wide: true, - ).with_content(t('inherited_proofing.cancel.actions.start_over')) %> -
- <%= render ButtonComponent.new( - action: ->(**tag_options, &block) do - button_to(idv_inherited_proofing_cancel_path(step: @flow_step), **tag_options, &block) - end, - method: :put, - big: true, - wide: true, - outline: true, - ).with_content(t('inherited_proofing.cancel.actions.keep_going')) %> -
-
- -
- -

<%= @presenter.exit_heading %>

- - <% @presenter.exit_description.each do |p_content| %> -

<%= p_content %>

- <% end %> - -
- <%= render SpinnerButtonComponent.new( - action: ->(**tag_options, &block) do - button_to(idv_inherited_proofing_cancel_path(step: @flow_step, location: 'cancel'), **tag_options, &block) - end, - method: :delete, - big: true, - wide: true, - outline: true, - form: { data: { form_steps_wait: '' } }, - ).with_content(@presenter.exit_action_text) %> -
-<% end %> - -<% javascript_packs_tag_once 'form-steps-wait' %> diff --git a/app/views/idv/inherited_proofing_errors/failure.html.erb b/app/views/idv/inherited_proofing_errors/failure.html.erb deleted file mode 100644 index 1fc683429f7..00000000000 --- a/app/views/idv/inherited_proofing_errors/failure.html.erb +++ /dev/null @@ -1,29 +0,0 @@ -<%= render( - 'idv/shared/error', - title: t('inherited_proofing.errors.cannot_retrieve.title'), - heading: t('inherited_proofing.errors.cannot_retrieve.heading'), - ) do %> -

- <%= t('inherited_proofing.errors.cannot_retrieve.failure_info') %> -

- - <%= render( - 'shared/troubleshooting_options', - heading: t('inherited_proofing.troubleshooting.headings.need_assistance'), - options: [ - { - url: @presenter.get_help_url, - text: t( - 'inherited_proofing.troubleshooting.options.get_help', - sp_name: decorated_session.sp_name, - ), - new_tab: true, - }, - { - url: @presenter.contact_support_url, - text: t('idv.troubleshooting.options.contact_support', app_name: APP_NAME), - new_tab: true, - }, - ], - ) %> -<% end %> diff --git a/app/views/idv/inherited_proofing_errors/warning.html.erb b/app/views/idv/inherited_proofing_errors/warning.html.erb deleted file mode 100644 index 70448fbd81e..00000000000 --- a/app/views/idv/inherited_proofing_errors/warning.html.erb +++ /dev/null @@ -1,27 +0,0 @@ -<%= render( - 'idv/shared/error', - type: :warning, - title: t('inherited_proofing.errors.cannot_retrieve.title'), - heading: t('inherited_proofing.errors.cannot_retrieve.heading'), - ) do %> -

- <%= t('inherited_proofing.errors.cannot_retrieve.info') %> -

- - <%= button_to t('inherited_proofing.buttons.try_again'), idv_inherited_proofing_step_path(step: :redo_retrieve_user_info), method: :put, class: 'usa-button usa-button--big usa-button--wide' %> - - <%= render( - 'shared/troubleshooting_options', - heading: t('components.troubleshooting_options.default_heading'), - options: [ - { - url: @presenter.get_help_url, - text: t( - 'inherited_proofing.troubleshooting.options.get_help', - sp_name: decorated_session.sp_name, - ), - new_tab: true, - }, - ], - ) %> -<% end %> diff --git a/app/views/idv/otp_delivery_method/new.html.erb b/app/views/idv/otp_delivery_method/new.html.erb deleted file mode 100644 index 955324501a1..00000000000 --- a/app/views/idv/otp_delivery_method/new.html.erb +++ /dev/null @@ -1,79 +0,0 @@ -<% content_for(:pre_flash_content) do %> - <%= render StepIndicatorComponent.new( - steps: step_indicator_steps, - current_step: :verify_phone_or_address, - locale_scope: 'idv', - class: 'margin-x-neg-2 margin-top-neg-4 tablet:margin-x-neg-6 tablet:margin-top-neg-4', - ) %> -<% end %> - -<% title t('titles.doc_auth.otp_delivery') %> - -<%= render(VendorOutageAlertComponent.new(vendors: [:sms, :voice], context: :idv)) %> - -<%= render PageHeadingComponent.new.with_content(t('idv.titles.otp_delivery_method')) %> -

- <%= t('idv.messages.otp_delivery_method.phone_number_html', phone: @idv_phone) %> -

- - -<%= form_tag( - idv_otp_delivery_method_url, - autocomplete: 'off', class: 'margin-top-4', - method: :put - ) do |f| %> - -
- <% if phone_number_capabilities.supports_sms? %> - <%= radio_button_tag( - 'otp_delivery_preference', - :sms, - false, - disabled: VendorStatus.new.vendor_outage?(:sms), - class: 'usa-radio__input usa-radio__input--tile', - ) %> - - <% end %> - - <% if phone_number_capabilities.supports_voice? %> - <%= radio_button_tag( - 'otp_delivery_preference', - :voice, - false, - disabled: VendorStatus.new.vendor_outage?(:voice), - class: 'usa-radio__input usa-radio__input--tile', - ) %> - - <% end %> -
-
- <%= submit_tag(t('idv.buttons.send_confirmation_code'), class: 'usa-button usa-button--big usa-button--wide') %> -
-<% end %> - -<%= render( - 'shared/troubleshooting_options', - heading: t('components.troubleshooting_options.default_heading'), - options: [ - { - url: idv_phone_path(step: 'phone_otp_delivery_selection'), - text: t('idv.troubleshooting.options.change_phone_number'), - }, - gpo_letter_available && { - url: idv_gpo_path, - text: t('idv.troubleshooting.options.verify_by_mail'), - }, - ].select(&:present?), - ) %> - -<%= render 'idv/doc_auth/cancel', step: 'phone_otp_delivery_selection' %> diff --git a/app/views/idv/ssn/show.html.erb b/app/views/idv/ssn/show.html.erb index f744c8ccedb..3d15e363595 100644 --- a/app/views/idv/ssn/show.html.erb +++ b/app/views/idv/ssn/show.html.erb @@ -85,7 +85,7 @@ locals: <% end %> <% if updating_ssn %> - <%= render 'idv/shared/back', action: 'cancel_update_ssn' %> + <%= render 'idv/shared/back', fallback_path: idv_verify_info_path %> <% else %> <%= render 'idv/doc_auth/cancel', step: 'ssn' %> <% end %> diff --git a/app/views/idv/verify_info/show.html.erb b/app/views/idv/verify_info/show.html.erb index 7e4f4630b35..48843b1f3e7 100644 --- a/app/views/idv/verify_info/show.html.erb +++ b/app/views/idv/verify_info/show.html.erb @@ -54,7 +54,9 @@ locals:
<%= t('idv.form.dob') %>:
-
<%= @pii[:dob] %>
+
+ <%= I18n.l(Date.parse(@pii[:dob]), format: I18n.t('time.formats.event_date')) %> +
<% if @in_person_proofing %>
@@ -121,24 +123,13 @@ locals: toggle_label: t('forms.ssn.show'), ) %>
- <% if IdentityConfig.store.doc_auth_ssn_controller_enabled %> -
- <%= link_to( - t('idv.buttons.change_label'), - idv_ssn_url, - 'aria-label': t('idv.buttons.change_ssn_label'), - ) %> -
- <% else %> -
- <%= button_to( - idv_doc_auth_step_url(step: :redo_ssn), - method: :put, - class: 'usa-button usa-button--unstyled', - 'aria-label': t('idv.buttons.change_ssn_label'), - ) { t('idv.buttons.change_label') } %> -
- <% end %> +
+ <%= link_to( + t('idv.buttons.change_label'), + idv_ssn_url, + 'aria-label': t('idv.buttons.change_ssn_label'), + ) %> +
<%= render SpinnerButtonComponent.new( diff --git a/app/views/layouts/base.html.erb b/app/views/layouts/base.html.erb index fa05616080d..36f0f9eb0f8 100644 --- a/app/views/layouts/base.html.erb +++ b/app/views/layouts/base.html.erb @@ -54,7 +54,7 @@ ) %> - <% if IdentityConfig.store.newrelic_browser_key.present? && IdentityConfig.store.newrelic_browser_app_id.present? %> + <% if BrowserSupport.supported?(request.user_agent) && IdentityConfig.store.newrelic_browser_key.present? && IdentityConfig.store.newrelic_browser_app_id.present? %> <%= render 'shared/newrelic/browser_instrumentation' %> <% end %> diff --git a/app/views/users/phone_setup/index.html.erb b/app/views/users/phone_setup/index.html.erb index c9f02ff2fb1..14d3c223a6b 100644 --- a/app/views/users/phone_setup/index.html.erb +++ b/app/views/users/phone_setup/index.html.erb @@ -2,24 +2,16 @@ <%= render(VendorOutageAlertComponent.new(vendors: [:sms, :voice])) %> -<%= image_tag asset_url('2FA-voice.svg'), alt: '', width: 200, height: 113, class: 'margin-bottom-2' %> - <%= render PageHeadingComponent.new.with_content(t('titles.phone_setup')) %>

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

-

- <%= 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 %> + <%= t('two_factor_authentication.phone_info') %>

<%= simple_form_for( @new_phone_form, - html: { autocomplete: 'off', method: :patch }, - data: { international_phone_form: true }, + method: :post, + html: { autocomplete: 'off' }, url: phone_setup_path, ) do |f| %> @@ -33,22 +25,30 @@ <%= render 'users/shared/otp_delivery_preference_selection', form_obj: @new_phone_form %> - <% unless FeatureManagement.phone_recaptcha_enabled? %> - <%= link_to t('two_factor_authentication.mobile_terms_of_service'), - MarketingSite.messaging_practices_url, - class: 'display-block margin-bottom-4' %> - <% end %> <% 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: 'phone_setup', ).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 %> +

+<% unless FeatureManagement.phone_recaptcha_enabled? %> + <%= link_to t('two_factor_authentication.mobile_terms_of_service'), + MarketingSite.messaging_practices_url, + class: 'display-block margin-bottom-4' %> +<% end %> + <%= render 'shared/cancel_or_back_to_options' %> <% if FeatureManagement.phone_recaptcha_enabled? %> diff --git a/app/views/users/phones/add.html.erb b/app/views/users/phones/add.html.erb index ac0d0ef67a3..670b4f3cc4d 100644 --- a/app/views/users/phones/add.html.erb +++ b/app/views/users/phones/add.html.erb @@ -5,19 +5,13 @@ <%= render PageHeadingComponent.new.with_content(t('headings.add_info.phone')) %>

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

-

- <%= 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 %> + <%= t('two_factor_authentication.phone_info') %>

<%= simple_form_for( @new_phone_form, - html: { autocomplete: 'off', method: :post }, - data: { international_phone_form: true }, + method: :post, + html: { autocomplete: 'off' }, url: add_phone_path, ) do |f| %> @@ -41,6 +35,13 @@ ).with_content(t('forms.buttons.continue')) %> <% 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 %> diff --git a/app/views/users/shared/_otp_delivery_preference_selection.html.erb b/app/views/users/shared/_otp_delivery_preference_selection.html.erb index f2770757c5c..cc3bb484104 100644 --- a/app/views/users/shared/_otp_delivery_preference_selection.html.erb +++ b/app/views/users/shared/_otp_delivery_preference_selection.html.erb @@ -3,14 +3,11 @@ form_name_tag_sms = form_name + '_otp_delivery_preference_sms' form_name_tag_voice = form_name + '_otp_delivery_preference_voice' %> -
+
<%= t('two_factor_authentication.otp_delivery_preference.title') %> -

- <%= t('two_factor_authentication.otp_delivery_preference.instruction') %> -

<%= radio_button_tag( @@ -37,6 +34,9 @@
+

+ <%= t('two_factor_authentication.otp_delivery_preference.instruction') %> +

<%= javascript_packs_tag_once('otp-delivery-preference') %> diff --git a/app/views/users/verify_personal_key/new.html.erb b/app/views/users/verify_personal_key/new.html.erb index 41b70507399..4929b1f3fb2 100644 --- a/app/views/users/verify_personal_key/new.html.erb +++ b/app/views/users/verify_personal_key/new.html.erb @@ -1,8 +1,12 @@ -<%= simple_form_for('', url: create_verify_personal_key_path, method: 'post') do |f| %> -
-

- <%= t('forms.personal_key.title') %> -

+<%= simple_form_for('', url: create_verify_personal_key_path, method: 'post', html: { class: 'margin-top-8' }) do |f| %> +
+ <%= render AlertIconComponent.new( + icon_name: :personal_key, + class: 'alert-icon--centered-top', + ) %> + + <%= render PageHeadingComponent.new.with_content(t('forms.personal_key.title')) %> +

<%= t('forms.personal_key.instructions') %>

@@ -10,17 +14,15 @@ <%= render 'shared/personal_key_input', code: '', form: f %>
-
- <%= button_tag t('forms.buttons.continue'), type: 'submit', class: 'usa-button usa-button--wide' %> -
+ <%= f.submit t('forms.buttons.continue'), class: 'display-block margin-y-5' %> <% end %> -
- <%= t('forms.personal_key.alternative') %> - <%= button_to( - reactivate_account_path, method: :put, - class: 'usa-button usa-button--unstyled', form_class: 'display-inline-block padding-left-1' - ) { t('links.reverify') } %> -
+<%= t('forms.personal_key.alternative') %> +<%= render ButtonComponent.new( + action: ->(**tag_options, &block) do + button_to(reactivate_account_path, method: :put, **tag_options, &block) + end, + unstyled: true, + ).with_content(t('links.reverify')) %> <%= render 'shared/cancel', link: account_path %> diff --git a/config/application.yml.default b/config/application.yml.default index 788586fcf0a..4d730b64a56 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -71,7 +71,6 @@ doc_auth_combined_hybrid_handoff_enabled: false doc_auth_error_dpi_threshold: 290 doc_auth_error_sharpness_threshold: 40 doc_auth_error_glare_threshold: 40 -doc_auth_ssn_controller_enabled: false database_pool_extra_connections_for_worker: 4 database_pool_idp: 5 database_statement_timeout: 2_500 @@ -88,6 +87,7 @@ doc_auth_extend_timeout_by_minutes: 40 doc_capture_polling_enabled: true doc_auth_client_glare_threshold: 50 doc_auth_client_sharpness_threshold: 50 +doc_auth_document_capture_controller_enabled: false doc_auth_enable_presigned_s3_urls: false doc_auth_s3_request_timeout: 5 doc_auth_error_dpi_threshold: 290 @@ -132,6 +132,7 @@ idv_send_link_attempt_window_in_minutes: 10 idv_send_link_max_attempts: 5 ie11_support_end_date: '2022-12-31' idv_sp_required: false +in_person_capture_secondary_id_enabled: false in_person_cta_variant_testing_enabled: true in_person_cta_variant_testing_percents: '{"B":100}' in_person_email_reminder_early_benchmark_in_days: 11 @@ -143,11 +144,6 @@ in_person_results_delay_in_hours: 1 in_person_completion_survey_url: 'https://login.gov' in_person_verify_info_controller_enabled: false include_slo_in_saml_metadata: false -inherited_proofing_enabled: false -inherited_proofing_va_base_url: 'https://staging-api.va.gov' -inherited_proofing_max_attempts: 2 -inherited_proofing_max_attempt_window_in_minutes: 1 -va_inherited_proofing_mock_enabled: false irs_attempt_api_audience: 'https://irs.gov' irs_attempt_api_auth_tokens: '' irs_attempt_api_csp_id: 'LOGIN.gov' @@ -159,6 +155,7 @@ irs_attempt_api_event_ttl_seconds: 86400 irs_attempt_api_event_count_default: 1000 irs_attempt_api_event_count_max: 10000 irs_attempt_api_payload_size_logging_enabled: true +irs_attempt_api_track_tmx_fraud_check_event: false key_pair_generation_percent: 0 logins_per_ip_track_only_mode: false # LexisNexis ##################################################### @@ -333,6 +330,7 @@ usps_ipp_request_timeout: 10 usps_ipp_sponsor_id: '' usps_ipp_username: '' usps_mock_fallback: true +usps_ipp_transliteration_enabled: false get_usps_proofing_results_job_cron: '0/10 * * * *' get_usps_proofing_results_job_reprocess_delay_minutes: 5 get_usps_proofing_results_job_request_delay_milliseconds: 1000 @@ -373,8 +371,6 @@ development: hmac_fingerprinter_key_queue: '["11111111111111111111111111111111", "22222222222222222222222222222222"]' identity_pki_local_dev: true in_person_proofing_enabled: true - inherited_proofing_enabled: true - va_inherited_proofing_mock_enabled: true irs_attempt_api_public_key: MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyut9Uio5XxsIUVrXARqCoHvcMVYT0p6WyU1BnbhxLRW4Q60p+4Bn32vVOt9nzeih7qvauYM5M0PZdKEmwOHflqPP+ABfKhL+6jxBhykN5P5UY375wTFBJZ20Fx8jOJbRhJD02oUQ49YKlDu3MG5Y0ApyD4ER4WKgxuB2OdyQKd9vg2ZZa+P2pw1HkFPEin0h8KBUFBeLGDZni8PIJdHBP6dA+xbayGBxSM/8xQC0JIg6KlGTcLql37QJIhP2oSv0nAJNb6idFPAz0uMCQDQWKKWV5FUDCsFVH7VuQz8xUCwnPn/SdaratB+29bwUpVhgHXrHdJ0i8vjBEX7smD7pI8CcFHuVgACt86NMlBnNCVkwumQgZNAAxe2mJoYcotEWOnhCuMc6MwSj985bj8XEdFlbf4ny9QO9rETd5aYcwXBiV/T6vd637uvHb0KenghNmlb1Tv9LMj2b9ZwNc9C6oeCnbN2YAfxSDrb8Ik+yq4hRewOvIK7f0CcpZYDXK25aHXnHm306Uu53KIwMGf1mha5T5LWTNaYy5XFoMWHJ9E+AnU/MUJSrwCAITH/S0JFcna5Oatn70aTE9pISATsqB5Iz1c46MvdrxD8hPoDjT7x6/EO316DZrxQfJhjbWsCB+R0QxYLkXPHczhB2Z0HPna9xB6RbJHzph7ifDizhZoMCAwEAAQ== irs_attempt_api_public_key_id: key1 irs_attempt_api_enabled: true @@ -404,6 +400,7 @@ development: state_tracking_enabled: true telephony_adapter: test use_dashboard_service_providers: true + usps_ipp_transliteration_enabled: true usps_upload_sftp_directory: "/gsa_order" usps_upload_sftp_host: localhost usps_upload_sftp_password: test @@ -523,8 +520,6 @@ test: hmac_fingerprinter_key: a2c813d4dca919340866ba58063e4072adc459b767a74cf2666d5c1eef3861db26708e7437abde1755eb24f4034386b0fea1850a1cb7e56bff8fae3cc6ade96c hmac_fingerprinter_key_queue: '["old-key-one", "old-key-two"]' identity_pki_disabled: true - inherited_proofing_enabled: true - va_inherited_proofing_mock_enabled: false irs_attempt_api_auth_tokens: 'test-token-1,test-token-2' irs_attempt_api_public_key: MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyut9Uio5XxsIUVrXARqCoHvcMVYT0p6WyU1BnbhxLRW4Q60p+4Bn32vVOt9nzeih7qvauYM5M0PZdKEmwOHflqPP+ABfKhL+6jxBhykN5P5UY375wTFBJZ20Fx8jOJbRhJD02oUQ49YKlDu3MG5Y0ApyD4ER4WKgxuB2OdyQKd9vg2ZZa+P2pw1HkFPEin0h8KBUFBeLGDZni8PIJdHBP6dA+xbayGBxSM/8xQC0JIg6KlGTcLql37QJIhP2oSv0nAJNb6idFPAz0uMCQDQWKKWV5FUDCsFVH7VuQz8xUCwnPn/SdaratB+29bwUpVhgHXrHdJ0i8vjBEX7smD7pI8CcFHuVgACt86NMlBnNCVkwumQgZNAAxe2mJoYcotEWOnhCuMc6MwSj985bj8XEdFlbf4ny9QO9rETd5aYcwXBiV/T6vd637uvHb0KenghNmlb1Tv9LMj2b9ZwNc9C6oeCnbN2YAfxSDrb8Ik+yq4hRewOvIK7f0CcpZYDXK25aHXnHm306Uu53KIwMGf1mha5T5LWTNaYy5XFoMWHJ9E+AnU/MUJSrwCAITH/S0JFcna5Oatn70aTE9pISATsqB5Iz1c46MvdrxD8hPoDjT7x6/EO316DZrxQfJhjbWsCB+R0QxYLkXPHczhB2Z0HPna9xB6RbJHzph7ifDizhZoMCAwEAAQ== irs_attempt_api_public_key_id: key1 diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 7a0c2bee4c8..e1fce56b1e7 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -113,7 +113,8 @@ ignore_unused: # - common.brand ## Ignore these keys completely: -# ignore: +ignore: + - 'i18n.transliterate.rule.*' # - kaminari.* ## Sometimes, it isn't possible for i18n-tasks to match the key correctly, diff --git a/config/locales/errors/en.yml b/config/locales/errors/en.yml index 83688ef42fd..cb1d6c92a8a 100644 --- a/config/locales/errors/en.yml +++ b/config/locales/errors/en.yml @@ -34,9 +34,6 @@ en: security, we limit the number of times you can attempt to verify a document online.' general: Oops, something went wrong. Please try again. - inherited_proofing: - consent_form: Before you can continue, you must give us permission. Please check - the box below and then click continue.blah invalid_totp: Invalid code. Please try again. max_password_attempts_reached: You’ve entered too many incorrect passwords. You can reset your password using the “Forgot your password?” link. diff --git a/config/locales/errors/es.yml b/config/locales/errors/es.yml index bf923b3355f..eb5aa277e49 100644 --- a/config/locales/errors/es.yml +++ b/config/locales/errors/es.yml @@ -35,8 +35,6 @@ es: %{timeout}. Por su seguridad, limitamos el número de veces que puede intentar verificar un documento en línea.' general: '¡Oops! Algo salió mal. Inténtelo de nuevo.' - inherited_proofing: - consent_form: to be implemented invalid_totp: El código es inválido. Vuelva a intentarlo. max_password_attempts_reached: Ha ingresado demasiadas contraseñas incorrectas. Puede restablecer su contraseña usando el enlace “¿Olvidó su contraseña?”. diff --git a/config/locales/errors/fr.yml b/config/locales/errors/fr.yml index d2b4a55b0f0..b1649a805d0 100644 --- a/config/locales/errors/fr.yml +++ b/config/locales/errors/fr.yml @@ -39,8 +39,6 @@ fr: votre sécurité, nous limitons le nombre de fois où vous pouvez tenter de vérifier un document en ligne.' general: Oups, une erreur s’est produite. Veuillez essayer de nouveau. - inherited_proofing: - consent_form: to be implemented invalid_totp: Code non valide. Veuillez essayer de nouveau. max_password_attempts_reached: Vous avez inscrit des mots de passe incorrects un trop grand nombre de fois. Vous pouvez réinitialiser votre mot de passe en diff --git a/config/locales/help_text/en.yml b/config/locales/help_text/en.yml index d6ad8eaf5cb..a5169c9caaa 100644 --- a/config/locales/help_text/en.yml +++ b/config/locales/help_text/en.yml @@ -18,6 +18,8 @@ en: information with %{sp}. We’ll share this information: ' ial2_intro_html: '%{sp} needs to know who you are to connect your account. We’ll share this information with %{sp}: ' + ial2_reverified_consent_info: 'Because you verified your identity again, we need + your permission to share this information with %{sp}: ' phone: Phone number social_security_number: Social Security number verified_at: Updated on diff --git a/config/locales/help_text/es.yml b/config/locales/help_text/es.yml index c4f596f906c..21090ee05a0 100644 --- a/config/locales/help_text/es.yml +++ b/config/locales/help_text/es.yml @@ -19,6 +19,8 @@ es: información:' ial2_intro_html: '%{sp} necesita saber quién es para conectar su cuenta. Compartiremos esta información con el organismo asociado: ' + ial2_reverified_consent_info: 'Como volvió a verificar su identidad, necesitamos + su permiso para compartir esta información con %{sp}: ' phone: Teléfono social_security_number: Número de Seguro Social verified_at: Actualizado en diff --git a/config/locales/help_text/fr.yml b/config/locales/help_text/fr.yml index e39fae0ced1..3f3d539f854 100644 --- a/config/locales/help_text/fr.yml +++ b/config/locales/help_text/fr.yml @@ -21,6 +21,9 @@ fr: ial2_intro_html: '%{sp} a besoin de savoir qui vous êtes pour connecter votre compte. Nous partagerons ces informations avec l’agence partenaire:' + ial2_reverified_consent_info: 'Puisque vous avez à nouveau vérifié votre + identité, nous avons besoin de votre autorisation pour partager ces + informations avec %{sp}:' phone: Numéro de téléphone social_security_number: Numéro de sécurité sociale verified_at: Mis à jour le diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml index 061c99904a5..6efa7febe14 100644 --- a/config/locales/idv/en.yml +++ b/config/locales/idv/en.yml @@ -13,7 +13,6 @@ en: mail: resend: Send another letter send: Send a letter - send_confirmation_code: Continue cancel: actions: account_page: Go to account page @@ -72,6 +71,9 @@ en: exceptions: internal_error: There was an internal error processing your request. Please try again. link: please contact us + post_office_search_error: We are having technical difficulties at the moment. + Try searching for a Post Office again. If this issue continues, come + back later. text_html: Please try again. If you keep getting these errors, %{link}. phone: fail_html: 'Please try again in %{timeout}. For your security, @@ -152,9 +154,6 @@ en: timeframe_html: Letters are sent the next business day via USPS First Class Mail and typically take 3 to 7 business days to arrive. mail_sent: Your letter is on its way - otp_delivery_method: - phone_number_html: We’ll send a code to %{phone} to verify that - the phone number belongs to you. otp_delivery_method_description: If you entered a landline above, please select “Phone call” below. personal_key: This is your new personal key. Write it down and keep it in a safe place. You will need it if you ever lose your password. @@ -208,7 +207,6 @@ en: still_having_trouble: Still having trouble? options: add_new_photos: Add new photos of your state-issued ID - change_phone_number: Use a different phone number contact_support: Contact %{app_name} Support doc_capture_tips: More tips for adding photos of your ID get_help_at_sp: Get help at %{sp_name} diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml index ddb29176298..08d77ffb78d 100644 --- a/config/locales/idv/es.yml +++ b/config/locales/idv/es.yml @@ -14,7 +14,6 @@ es: mail: resend: Enviar otra carta send: Enviar una carta - send_confirmation_code: Continuar cancel: actions: account_page: Ir a la página de la cuenta @@ -77,6 +76,9 @@ es: internal_error: Se produjo un error interno al procesar tu solicitud. Por favor, inténtalo de nuevo. link: contáctanos + post_office_search_error: En este momento, estamos teniendo problemas técnicos. + Trate de buscar de nuevo una oficina de correos. Si el problema + continúa, regrese más tarde. text_html: Inténtalo de nuevo. Si sigues recibiendo estos errores, %{link}. phone: fail_html: 'Por favor, inténtelo de nuevo en %{timeout}. Por su @@ -162,9 +164,6 @@ es: USPS y suelen tardar entre 3 y 7 días hábiles en llegar. mail_sent: Su carta está en camino - otp_delivery_method: - phone_number_html: Enviaremos un código a %{phone} para - verificar que el número de teléfono le pertenece. otp_delivery_method_description: Si ha introducido un teléfono fijo más arriba, seleccione “Llamada telefónica” más abajo. personal_key: Esta es su nueva clave personal. Escríbala y guárdela en un lugar @@ -222,7 +221,6 @@ es: still_having_trouble: '¿Sigue teniendo dificultades?' options: add_new_photos: Añada nuevas fotos de su ID emitido por el estado - change_phone_number: Utiliza un número de teléfono diferente 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 get_help_at_sp: Obtenga ayuda en %{sp_name} diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml index d10f8582cd3..409b423caa6 100644 --- a/config/locales/idv/fr.yml +++ b/config/locales/idv/fr.yml @@ -14,7 +14,6 @@ fr: mail: resend: Envoyer une autre lettre send: Envoyer une lettre - send_confirmation_code: Continuer cancel: actions: account_page: Accéder à la page de votre compte @@ -81,6 +80,9 @@ fr: internal_error: Une erreur interne s’est produite lors du traitement de votre demande. Veuillez réessayer. link: contactez-nous + post_office_search_error: Nous connaissons des difficultés techniques en ce + moment. Essayez de chercher à nouveau un bureau de poste. Si le + problème persiste, revenez plus tard. text_html: Veuillez réessayer. Si vous continuez à recevoir ces erreurs, %{link} phone: fail_html: 'Veuillez réessayer dans %{timeout}. Pour votre @@ -171,9 +173,6 @@ fr: première classe de USPS et prennent généralement entre trois à sept jours ouvrables pour être reçues. mail_sent: Votre lettre est en route - otp_delivery_method: - phone_number_html: Nous enverrons un code à %{phone} pour - vérifier que le numéro de téléphone vous appartient. otp_delivery_method_description: Si vous avez saisi une ligne fixe ci-dessus, veuillez sélectionner « Appel téléphonique » ci-dessous. personal_key: Il s’agit de votre nouvelle clé personnelle. Notez-la et @@ -238,7 +237,6 @@ fr: options: add_new_photos: Ajoutez de nouvelles photos de votre carte d’identité délivrée par l’État. - change_phone_number: Utiliser un autre numéro de téléphone 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é get_help_at_sp: Demandez de l’aide à %{sp_name} diff --git a/config/locales/inherited_proofing/en.yml b/config/locales/inherited_proofing/en.yml deleted file mode 100644 index 38da46a43ab..00000000000 --- a/config/locales/inherited_proofing/en.yml +++ /dev/null @@ -1,71 +0,0 @@ ---- -en: - inherited_proofing: - buttons: - continue: Continue - try_again: Try again - cancel: - actions: - keep_going: No, keep going - start_over: Start over - description: - start_over: If you start over, you will restart this process from the beginning - and the information you entered will not be saved. - headings: - confirmation: - hybrid: You have cancelled uploading photos of your ID on this phone - prompt: - standard: Cancel verifying your identity? - start_over: Start over verifying your identity - instructions: - switch_back: Switch back to your computer to finish verifying your identity. - switch_back_image: Arrow pointing from phone to computer - errors: - cannot_retrieve: - failure_info: Please check back later. - heading: We could not retrieve your information from your Partner Agency - info: We are temporarily having trouble retrieving your information. Please try - again. - title: Couldn’t retrieve information - service_provider: - communication: 'communication was unsuccessful' - headings: - lets_go: How verifying your identity works - retrieval: We are retrieving your information from your Partner Agency - secure_account: Secure your account - verify_identity: Verify your identity - verify_information: Verify your information - welcome: Get started verifying your identity - info: - lets_go: 'Identity verification happens in two parts:' - no_sp_name: VA needs to make sure you are you - privacy_html: '%{app_name} is a secure, government website that adheres to the - highest standards in data protection. We only use your data to verify - your identity. %{link} about our privacy and security measures.' - retrieval_thanks: Thanks for your patience! - retrieval_time: This might take up to a minute. We’ll load the next step - automatically when it’s done. - 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. - verify_identity: We’ll retrieve your personal information from %{sp_name} and - ask you to verify it. - welcome_html: '%{sp_name} needs to make sure you are you - not someone - pretending to be you.' - instructions: - consent: By checking this box, you are letting login.gov ask for, use, keep, and - share your personal information. We will only use it to verify your - identity. - learn_more: Learn more - privacy: Our privacy and security standards - verify_requirements: We’ll call or text your phone number to verify your - identity. If we can’t verify your phone number, you can verify by mail - instead. - troubleshooting: - headings: - missing_required_items: Don’t have a phone number? Here’s how to get help - need_assistance: 'Need immediate assistance? Here’s how to get help:' - need_help_updating: Need to update your information? Here’s how to get help - options: - get_help: Get help at the %{sp_name} web site - learn_more_phone_or_mail: Learn more about verifying by phone or mail diff --git a/config/locales/inherited_proofing/es.yml b/config/locales/inherited_proofing/es.yml deleted file mode 100644 index d79cfd9ead4..00000000000 --- a/config/locales/inherited_proofing/es.yml +++ /dev/null @@ -1,76 +0,0 @@ ---- -es: - inherited_proofing: - buttons: - continue: Continuar - try_again: Inténtelo de nuevo - cancel: - actions: - keep_going: No, continuar - start_over: Empezar de nuevo - description: - start_over: Si empieza de nuevo, el proceso se reiniciará y la información que - haya ingresado se perderá. - headings: - confirmation: - hybrid: Ha cancelado la carga de fotos de su identificación en este teléfono - prompt: - standard: '¿Cancelar la verificación de su identidad?' - start_over: Empezar de nuevo a verificar su identidad - instructions: - switch_back: Regrese a su computadora para continuar con la verificación de su - identidad. - switch_back_image: Flecha que apunta del teléfono a la computadora - errors: - cannot_retrieve: - failure_info: to be implemented - heading: No hemos podido obtener su información de su agencia colaboradora - info: Estamos teniendo problemas temporalmente para obtener su información. - Inténtelo de nuevo. - title: to be implemented - service_provider: - communication: 'la comunicacion no tuvo exito' - headings: - lets_go: Cómo funciona la verificación de su identidad - retrieval: Estamos recuperando su información de su agencia colaboradora - secure_account: Asegure su cuenta - verify_identity: Verifique su identidad - verify_information: Verifique su información - welcome: Empiece con la verificación de su identidad - info: - lets_go: 'La verificación de la identidad se realiza en dos partes:' - no_sp_name: La agencia a la que está intentando acceder - privacy_html: '%{app_name} es un sitio web gubernamental seguro que cumple con - las normas más estrictas de protección de datos. Solo utilizamos sus - datos para verificar su identidad. %{link} sobre nuestras medidas de - privacidad y seguridad.' - retrieval_thanks: '¡Gracias por su paciencia!' - retrieval_time: Esto puede tardar hasta un minuto. Cargaremos el siguiente paso - automáticamente cuando haya terminado. - secure_account: Cifraremos 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. - verify_identity: Obtendremos su información personal de %{sp_name} y le - pediremos que la verifique. - welcome_html: '%{sp_name} necesita asegurarse de que sea usted y no alguien que - se haga pasar por usted.' - instructions: - consent: Al marcar esta casilla, usted permite que login.gov solicite, utilice, - conserve y comparta su información personal. Solo la utilizaremos para - verificar su identidad. - learn_more: Obtenga más información - privacy: Nuestras normas de privacidad y seguridad - verify_requirements: Te llamaremos o te enviaremos un mensaje de texto a tu - número de teléfono para verificar tu identidad. Si no podemos verificar - tu número de teléfono, puedes realizar la verificación por correo - postal. - troubleshooting: - headings: - missing_required_items: ¿No tienes número de teléfono? A continuación, te - indicamos cómo obtener ayuda - need_assistance: to be implemented - need_help_updating: ¿Necesita actualizar su información? A continuación le - indicamos cómo obtener ayuda - options: - get_help: Obtenga ayuda en %{sp_name} - learn_more_phone_or_mail: Más información sobre la verificación por teléfono o correo postal. diff --git a/config/locales/inherited_proofing/fr.yml b/config/locales/inherited_proofing/fr.yml deleted file mode 100644 index 140a6dc8650..00000000000 --- a/config/locales/inherited_proofing/fr.yml +++ /dev/null @@ -1,76 +0,0 @@ ---- -fr: - inherited_proofing: - buttons: - continue: Continuer - try_again: Réessayez - cancel: - actions: - keep_going: Non, continuer - start_over: Recommencer - description: - start_over: Si vous recommencez, vous reprendrez ce processus depuis le début et - les informations que vous avez saisies ne seront pas enregistrées. - headings: - confirmation: - hybrid: Vous avez annulé le téléchargement de vos photos d’identité sur ce - téléphone - prompt: - standard: Annuler la vérification de votre identité? - start_over: Recommencez la vérification de votre identité - instructions: - switch_back: Retournez sur votre ordinateur pour continuer à vérifier votre - identité. - switch_back_image: Flèche pointant du téléphone vers l’ordinateur - errors: - cannot_retrieve: - failure_info: to be implemented - heading: Nous n’avons pas pu récupérer vos informations dans votre agence - partenaire - info: Nous avons temporairement des difficultés à récupérer vos informations. - Veuillez réessayer. - title: to be implemented - service_provider: - communication: 'la communication a échoué' - headings: - lets_go: Comment fonctionne la vérification de votre identité - retrieval: Nous récupérons vos informations depuis votre agence partenaire. - secure_account: Sécuriser votre compte - verify_identity: Vérifier votre identité - verify_information: Vérifier votre information - welcome: Commencez à vérifier votre identité - info: - lets_go: 'La vérification de l’identité se fait en deux temps :' - no_sp_name: L’agence à laquelle vous essayez d’accéder - privacy_html: '%{app_name} est un site gouvernemental sécurisé qui respecte les - normes les plus strictes en matière de protection des données. Nous - n’utilisons vos données uniquement pour vérifier votre identité. %{link} - sur nos mesures de confidentialité et de sécurité.' - retrieval_thanks: Merci de votre patience! - retrieval_time: Cette opération peut prendre jusqu’à une minute. Nous chargerons - automatiquement l’étape suivante lorsque ce sera fait. - secure_account: Votre compte sera crypté avec votre mot de passe. Le cryptage - signifie que vos données sont protégées et que vous seul pourrez accéder - à vos informations ou les modifier. - verify_identity: Nous récupérerons vos informations personnelles sur %{sp_name} - et vous demanderons de les vérifier. - welcome_html: '%{sp_name} doit s’assurer que vous êtes bien vous, et non - quelqu’un qui se fait passer pour vous' - instructions: - consent: En cochant cette case, vous autorisez login.gov à demander, utiliser, - conserver et partager vos renseignements personnels. Nous ne les - utiliserons que pour vérifier votre identité. - learn_more: En savoir plus - privacy: Nos normes de confidentialité et de sécurité - verify_requirements: Nous allons vous appeler ou vous envoyer un SMS à partir de - votre contact pour vérifier votre identité. Si nous ne pouvons pas - vérifier votre numéro de téléphone, vous pouvez le faire par courriel. - troubleshooting: - headings: - missing_required_items: Vous n’avez pas de numéro de téléphone? Voici comment obtenir de l’aide - need_assistance: to be implemented - need_help_updating: Vous devez mettre à jour vos informations? Voici comment - obtenir de l’aide - options: - get_help: Obtenez de l’aide sur %{sp_name} - learn_more_phone_or_mail: En savoir plus sur la vérification par téléphone ou par courriel. diff --git a/config/locales/titles/en.yml b/config/locales/titles/en.yml index 783638a1ef9..2d898e0715e 100644 --- a/config/locales/titles/en.yml +++ b/config/locales/titles/en.yml @@ -13,7 +13,6 @@ en: address: Update your mailing address doc_capture: Add your ID link_sent: Link sent - otp_delivery: Receive a one-time code processing_images: Processing your images ssn: Enter your Social Security number switch_back: Switch back to your computer @@ -42,9 +41,6 @@ en: reset_password: Reset Password review: Re-enter your password verify_info: Verify your information - inherited_proofing: - cancellation_prompt: Cancel identity verification - cancelled: Identity verification is cancelled mfa_setup: suggest_second_mfa: You’ve added your first authentication method! Add a second method as a backup. @@ -58,7 +54,7 @@ en: confirm: Confirm the password for your account forgot: Reset password personal_key: Just in case - phone_setup: Send your one-time code via text message (SMS) or phone call + phone_setup: Get your one-time code piv_cac_login: add: Add your PIV or CAC new: Use your PIV/CAC to sign in to your account @@ -79,6 +75,7 @@ en: completion_ial2: Connect your verified information to %{sp} completion_new_attributes: '%{sp} is requesting new information' completion_new_sp: You are now signing in for the first time + completion_reverified_consent: Update your verified information with %{sp} confirmation: Continue to sign in spam_protection: Protecting against spam totp_setup: diff --git a/config/locales/titles/es.yml b/config/locales/titles/es.yml index 458620a62ee..27a3c09aa19 100644 --- a/config/locales/titles/es.yml +++ b/config/locales/titles/es.yml @@ -13,7 +13,6 @@ es: address: Actualice su dirección postal doc_capture: Agrega tu identificación link_sent: Enlace enviado - otp_delivery: Reciba un código de un solo uso processing_images: Procesando tus imágenes ssn: Ingresa tu número del seguro social switch_back: Regresar a tu computadora @@ -42,9 +41,6 @@ es: reset_password: Restablecer la contraseña review: Vuelve a ingresar tu contraseña verify_info: Verifica tu información - inherited_proofing: - cancellation_prompt: Cancela la verificación de identidad - cancelled: Se canceló la verificación de identidad mfa_setup: suggest_second_mfa: '¡Has agregado tu primer método de autenticación! Agrega un segundo método como respaldo.' @@ -58,8 +54,7 @@ es: confirm: Confirme la contraseña de su cuenta forgot: Restablecer la contraseña personal_key: Por si acaso - phone_setup: Envíe su código de un solo uso a través de un mensaje de texto - (SMS) o una llamada telefónica + phone_setup: Obtenga su código único piv_cac_login: add: Agregue su PIV o CAC new: Use su PIV / CAC para iniciar sesión en su cuenta @@ -81,6 +76,7 @@ es: completion_ial2: Conecte su información verificada a %{sp} completion_new_attributes: '%{sp} está solicitando nueva información' completion_new_sp: Acabas de iniciar sesión por primera vez + completion_reverified_consent: Actualice su información verificados con %{sp} confirmation: Continuar para iniciar sesión spam_protection: Protección contra el correo no deseado totp_setup: diff --git a/config/locales/titles/fr.yml b/config/locales/titles/fr.yml index 5175de061b2..0718e36a78d 100644 --- a/config/locales/titles/fr.yml +++ b/config/locales/titles/fr.yml @@ -13,7 +13,6 @@ fr: address: Mettez à jour votre adresse postale doc_capture: Ajoutez votre pièce d’identité link_sent: Lien envoyé - otp_delivery: Recevez un code à usage unique processing_images: Traitement de vos images ssn: Entrez votre numéro de sécurité sociale switch_back: Retournez sur votre ordinateur @@ -42,9 +41,6 @@ fr: reset_password: Réinitialisez le mot de passe review: Saisissez à nouveau votre mot de passe verify_info: Vérifiez vos informations - inherited_proofing: - cancellation_prompt: Annulez la vérification d’identité - cancelled: La vérification d’identité est annulée mfa_setup: suggest_second_mfa: Vous avez ajouté votre première méthode d’authentification ! Ajoutez-en une deuxième en guise de sauvegarde. @@ -58,8 +54,7 @@ fr: confirm: Confirmez le mot de passe de votre compte forgot: Réinitialisez le mot de passe personal_key: Juste au cas - phone_setup: Envoyez votre code à usage unique par message texte (SMS) ou par - appel téléphonique. + phone_setup: Obtenez votre code à usage unique piv_cac_login: add: Ajoutez votre PIV ou CAC new: Utilisez votre PIV / CAC pour vous connecter à votre compte @@ -81,6 +76,7 @@ fr: completion_ial2: Connectez vos informations vérifiées à %{sp} completion_new_attributes: '%{sp} demande de nouvelles informations' completion_new_sp: Vous vous connectez pour la première fois + completion_reverified_consent: Mettez à jour vos informations vérifiées avec %{sp} confirmation: Continuer à vous connecter spam_protection: Protection contre les pourriels totp_setup: diff --git a/config/locales/transliterate/en.yml b/config/locales/transliterate/en.yml new file mode 100644 index 00000000000..ff055805825 --- /dev/null +++ b/config/locales/transliterate/en.yml @@ -0,0 +1,23 @@ +en: + i18n: + transliterate: + rule: + # Convert okina to apostrophe + ʻ: "'" + # Convert quotation marks + ’: "'" + ‘: "'" + ‛: "'" + “: '"' + ‟: '"' + ”: '"' + # Convert hyphens + ‐: '-' + ‑: '-' + ‒: '-' + –: '-' + —: '-' + ﹘: '-' + # Convert number signs + ﹟: '#' + #: '#' diff --git a/config/locales/two_factor_authentication/en.yml b/config/locales/two_factor_authentication/en.yml index a9eba3bc0a2..cefa57a0e6d 100644 --- a/config/locales/two_factor_authentication/en.yml +++ b/config/locales/two_factor_authentication/en.yml @@ -81,20 +81,19 @@ en: wait_30d_opt_in: After 30 days, you can opt in and receive a security code to that phone number. otp_delivery_preference: - instruction: You can change this selection the next time you sign in. If you - entered a landline, please select “Phone call” below. + instruction: You can change this anytime. If you use a landline number, select + “Phone call.” landline_warning_html: The phone number entered appears to be a landline phone. Request a one-time code by %{phone_setup_path} instead. no_supported_options: We are unable to verify phone numbers from %{location} phone_call: phone call sms: Text message (SMS) sms_unsupported: We are unable to send text messages to phone numbers in %{location}. - title: How should we send you a code? + title: How you’ll get your code voice: Phone call voice_unsupported: We are unable to call phone numbers in %{location}. otp_make_default_number: - instruction: Make this phone number the default number where you receive text - message or phone calls. + instruction: Send text messages and calls to this number by default. label: Default phone number one_number_instruction: You must have more than one phone number added to select a default phone number. @@ -112,7 +111,7 @@ en: phone_fallback: question: Can’t use your phone? phone_fee_disclosure: Message and data rates may apply. - phone_info_html: We’ll send you a one-time code each time you sign in. + phone_info: We’ll send you a one-time code each time you sign in. phone_label: Phone number phone_verification: troubleshooting: @@ -158,12 +157,10 @@ en: piv_cac: Government employee ID piv_cac_info: PIV/CAC cards for government and military employees. Desktop only. sms: Text message / SMS - sms_info: Get your one-time code via text message / SMS. unused_backup_code: one: '(%{count} unused code)' other: '(%{count} unused codes)' voice: Phone call - voice_info: Get your one-time code via phone call. webauthn: Security key webauthn_info: A physical device, often shaped like a USB drive, that you plug in to your device. diff --git a/config/locales/two_factor_authentication/es.yml b/config/locales/two_factor_authentication/es.yml index ac1492c149a..1edd7c183eb 100644 --- a/config/locales/two_factor_authentication/es.yml +++ b/config/locales/two_factor_authentication/es.yml @@ -89,7 +89,7 @@ es: wait_30d_opt_in: Después de 30 días, podrá inscribirse y recibir un código de seguridad para ese número de teléfono. otp_delivery_preference: - instruction: Puede cambiar esta selección la próxima vez que inicie sesión. + instruction: Envíe mensajes de texto y llamadas a este número por defecto. landline_warning_html: Al parecer el número ingresado pertenece a un teléfono fijo. Mejor solicita un código de un solo uso por %{phone_setup_path}. @@ -97,7 +97,7 @@ es: phone_call: llamada telefónica sms: Mensaje de texto (SMS, sigla en inglés) sms_unsupported: No podemos enviar mensajes de texto a números de teléfono de %{location}. - title: '¿Cómo deberíamos enviarle un código?' + title: Cómo obtendrá su código voice: Llamada telefónica voice_unsupported: No podemos llamar a números de teléfono en %{location}. otp_make_default_number: @@ -120,8 +120,7 @@ es: phone_fallback: question: '¿No puede utilizar su teléfono?' phone_fee_disclosure: Se pueden aplicar tarifas por mensajes y datos. - phone_info_html: Le enviaremos un código de un solo uso cada vez que - ingrese. + phone_info: Le enviaremos un código de un solo uso cada vez que ingrese. phone_label: Número de teléfono phone_verification: troubleshooting: @@ -171,12 +170,10 @@ es: piv_cac_info: Credenciales PIV/CAC para empleados gubernamentales y del ejército. Únicamente versión de escritorio. sms: Mensaje de texto / SMS - sms_info: Obtenga su código de un solo uso a través de un mensaje de texto/SMS. unused_backup_code: one: '(%{count} código sin usar)' other: '(%{count} códigos sin usar)' voice: Llamada telefónica - voice_info: Obtenga su código de un solo uso a través de una llamada telefónica. webauthn: Clave de seguridad webauthn_info: Un dispositivo físico que suele tener la forma de una unidad USB y se conecta a su dispositivo. diff --git a/config/locales/two_factor_authentication/fr.yml b/config/locales/two_factor_authentication/fr.yml index 733257f448a..41de2c0ecb1 100644 --- a/config/locales/two_factor_authentication/fr.yml +++ b/config/locales/two_factor_authentication/fr.yml @@ -91,7 +91,7 @@ fr: wait_30d_opt_in: Après 30 jours, vous pouvez vous inscrire et recevoir un code de sécurité à ce numéro de téléphone. otp_delivery_preference: - instruction: Vous pouvez modifier cette sélection lors de votre prochaine connexion. + instruction: Envoyez des messages texte ainsi que des appels par défaut à ce numéro landline_warning_html: Le numéro de téléphone saisi semble être un téléphone fixe. Demandez plutôt un code à usage unique par %{phone_setup_path}. @@ -101,7 +101,7 @@ fr: sms: Message texte (SMS) sms_unsupported: Il est impossible d’envoyer des messages texte à des numéros de téléphone dans %{location}. - title: Comment devrions-nous vous envoyer un code? + title: Comment vous obtiendrez votre code voice: Appel téléphonique voice_unsupported: Il nous est impossible d’appeler des numéros de téléphone dans le %{location}. @@ -125,8 +125,8 @@ fr: phone_fallback: question: Vous ne pouvez pas utiliser votre téléphone? phone_fee_disclosure: Des messages et débits de données peuvent être appliqués. - phone_info_html: Nous vous enverrons un code à usage unique à chaque - fois que vous vous connecterez. + phone_info: Nous vous enverrons un code à usage unique à chaque fois que vous + vous connecterez. phone_label: Numéro de téléphone phone_verification: troubleshooting: @@ -175,12 +175,10 @@ fr: piv_cac_info: Cartes PIV/CAC pour les fonctionnaires et les militaires. Bureau uniquement. sms: SMS - sms_info: Recevez votre code à usage unique par message texte/SMS. unused_backup_code: one: '(%{count} code non utilisé)' other: '(%{count} codes non utilisés)' voice: Appel téléphonique - voice_info: Obtenez votre code à usage unique par appel téléphonique. webauthn: Clef de sécurité webauthn_info: Un appareil physique, souvent en forme de clé USB, que vous branchez sur votre appareil. diff --git a/config/routes.rb b/config/routes.rb index 20fb8449910..c79649eaadd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -307,8 +307,7 @@ post '/personal_key' => 'personal_key#update' get '/forgot_password' => 'forgot_password#new' post '/forgot_password' => 'forgot_password#update' - get '/otp_delivery_method' => 'otp_delivery_method#new' - put '/otp_delivery_method' => 'otp_delivery_method#create' + get '/document_capture' => 'document_capture#show' get '/ssn' => 'ssn#show' put '/ssn' => 'ssn#update' get '/verify_info' => 'verify_info#show' @@ -366,22 +365,6 @@ post '/confirmations' => 'personal_key#update' end - # Inherited Proofing (IP)-specific routes. - scope '/verify/inherited_proofing', module: 'idv', as: 'idv_inherited_proofing' do - # NOTE: cancellation routes need to be before any other IP - # routes in this scope. - delete '/session' => 'sessions#destroy' - get '/cancel' => 'inherited_proofing_cancellations#new', as: :cancel - put '/cancel' => 'inherited_proofing_cancellations#update' - delete '/cancel' => 'inherited_proofing_cancellations#destroy' - get '/' => 'inherited_proofing#index' - get '/:step' => 'inherited_proofing#show', as: :step - put '/:step' => 'inherited_proofing#update' - get '/return_to_sp' => 'inherited_proofing#return_to_sp' - get '/errors/no_information' => 'inherited_proofing_errors#warning' - get '/errors/failure' => 'inherited_proofing_errors#failure' - end - namespace :api do post '/verify/v2/document_capture' => 'verify/document_capture#create' delete '/verify/v2/document_capture_errors' => 'verify/document_capture_errors#delete' diff --git a/docs/FixingOpenSSLVersionProblem.md b/docs/FixingOpenSSLVersionProblem.md new file mode 100644 index 00000000000..b0184a4eda4 --- /dev/null +++ b/docs/FixingOpenSSLVersionProblem.md @@ -0,0 +1,82 @@ +# How to fix a recurrent problem with OpenSSL and some of our tests +This problem has happened when Ruby was built and linked against the +wrong version of OpenSSL. + +The procedure we have found that fixes the problem is to rebuild Ruby, +linked against the correct version of OpenSSL, then remove and +reinstall all of your gems. + +These instructions have been used successfully for environments +managed using `asdf`, `chruby` and `rbenv`. Details for each are +below. + +If you are using another Ruby version manager, the section on +`ruby-build` is likely your best starting point. Please add your +experience and any useful information to this document. + +# Details +- These instructions assume you're on a Mac; if not, you will have to + work out the equivalent directions based on these. + +- As of this writing, the correct Ruby version for login.gov is 3.2.0. + Use whatever the current version is. + +## One spec which shows the problem: + +`spec/features/openid_connect/openid_connect_spec.rb` + +The problem looks like: +``` +1) OpenID Connect receives an ID token with a kid that matches the certs endpoint + Failure/Error: JWT::JWK.import(certs_response[:keys].first).public_key + OpenSSL::PKey::PKeyError: + rsa#set_key= is incompatible with OpenSSL 3.0 +``` + +## Finding out where you have openssl 1.1 installed + +`brew --prefix openssl@1.1` + +If not present, run `brew install openssl@1.1` + +## Version manager specifics +Most version managers simply require that the correct version of Ruby +be installed, usually using `ruby-build`. + +### Rebuilding Ruby using `asdf` +`asdf` uses `ruby-build` under the covers, but supplies some +configuration of its own, so we must use `asdf` to (re-)install Ruby. + +Remove the existing Ruby version, if present: + +`asdf uninstall ruby 3.2.0` + +And re-install, using the correct OpenSSL installation: + +`RUBY_CONFIGURE_OPTS="--with-openssl-dir=$(brew --prefix openssl@1.1)" asdf install ruby 3.2.0` + +### Rebuilding Ruby using `chruby` +Use the `ruby-build` instructions; `chruby` doesn't require anything special. + +### Rebuilding Ruby using `rbenv` +Use the `ruby-build` instructions; `rbenv` doesn't require anything special. + +### Rebuilding Ruby using `ruby-build` +Make sure ruby-build is up to date + +`brew upgrade ruby-build` + +And then rebuild Ruby (this assumes your Rubies are in ~/.rubies) + +`RUBY_CONFIGURE_OPTS="--with-openssl-dir=$(brew --prefix openssl@1.1)" ruby-build 3.2.0 ~/.rubies/3.2.0` + +## Exiting your shell +After your Ruby is built, exit your shell and open a new one, to clear caches. + +## Removing all of your gems + +`gem uninstall -aIx` + +## Reinstalling your gems + +`bundle install` diff --git a/lib/identity_config.rb b/lib/identity_config.rb index e5982dfcfc6..714cf08c821 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -157,6 +157,7 @@ def self.build_store(config_map) config.add(:doc_auth_client_glare_threshold, type: :integer) config.add(:doc_auth_client_sharpness_threshold, type: :integer) config.add(:doc_auth_combined_hybrid_handoff_enabled, type: :boolean) + config.add(:doc_auth_document_capture_controller_enabled, type: :boolean) config.add(:doc_auth_enable_presigned_s3_urls, type: :boolean) config.add(:doc_auth_error_dpi_threshold, type: :integer) config.add(:doc_auth_error_glare_threshold, type: :integer) @@ -167,7 +168,6 @@ def self.build_store(config_map) config.add(:doc_auth_max_submission_attempts_before_native_camera, type: :integer) config.add(:doc_auth_max_capture_attempts_before_tips, type: :integer) config.add(:doc_auth_s3_request_timeout, type: :integer) - config.add(:doc_auth_ssn_controller_enabled, type: :boolean) config.add(:doc_auth_vendor, type: :string) config.add(:doc_auth_vendor_randomize, type: :boolean) config.add(:doc_auth_vendor_randomize_percent, type: :integer) @@ -211,6 +211,7 @@ def self.build_store(config_map) config.add(:idv_send_link_max_attempts, type: :integer) config.add(:idv_sp_required, type: :boolean) config.add(:ie11_support_end_date, type: :timestamp) + config.add(:in_person_capture_secondary_id_enabled, type: :boolean) config.add(:in_person_cta_variant_testing_enabled, type: :boolean) config.add(:in_person_cta_variant_testing_percents, type: :json) config.add(:in_person_email_reminder_early_benchmark_in_days, type: :integer) @@ -222,11 +223,6 @@ def self.build_store(config_map) config.add(:in_person_completion_survey_url, type: :string) config.add(:in_person_verify_info_controller_enabled, type: :boolean) config.add(:include_slo_in_saml_metadata, type: :boolean) - config.add(:inherited_proofing_enabled, type: :boolean) - config.add(:inherited_proofing_va_base_url, type: :string) - config.add(:inherited_proofing_max_attempts, type: :integer) - config.add(:inherited_proofing_max_attempt_window_in_minutes, type: :integer) - config.add(:va_inherited_proofing_mock_enabled, type: :boolean) config.add(:irs_attempt_api_audience) config.add(:irs_attempt_api_auth_tokens, type: :comma_separated_string_list) config.add(:irs_attempt_api_bucket_name, type: :string, allow_nil: true) @@ -239,6 +235,7 @@ def self.build_store(config_map) config.add(:irs_attempt_api_event_count_default, type: :integer) config.add(:irs_attempt_api_event_count_max, type: :integer) config.add(:irs_attempt_api_payload_size_logging_enabled, type: :boolean) + config.add(:irs_attempt_api_track_tmx_fraud_check_event, type: :boolean) config.add(:irs_attempt_api_public_key) config.add(:irs_attempt_api_public_key_id) config.add(:lexisnexis_base_url, type: :string) @@ -431,6 +428,7 @@ def self.build_store(config_map) config.add(:usps_ipp_username, type: :string) config.add(:usps_ipp_request_timeout, type: :integer) config.add(:usps_upload_enabled, type: :boolean) + config.add(:usps_ipp_transliteration_enabled, type: :boolean) config.add(:get_usps_proofing_results_job_cron, type: :string) config.add(:get_usps_proofing_results_job_reprocess_delay_minutes, type: :integer) config.add(:get_usps_proofing_results_job_request_delay_milliseconds, type: :integer) diff --git a/lib/session_encryptor.rb b/lib/session_encryptor.rb index 113a1b496b1..2ac918f8f7d 100644 --- a/lib/session_encryptor.rb +++ b/lib/session_encryptor.rb @@ -20,7 +20,6 @@ class SensitiveValueError < StandardError; end # personal keys are generated and stored in the session between requests, but are used # to decrypt PII bundles, so we treat them similarly to the PII itself. SENSITIVE_PATHS = [ - ['warden.user.user.session', 'idv/inherited_proofing'], ['warden.user.user.session', 'idv/doc_auth'], ['warden.user.user.session', 'idv/in_person'], ['warden.user.user.session', 'idv'], diff --git a/package.json b/package.json index e2df37ddbaa..4333a229151 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,10 @@ "lint:css": "stylelint 'app/assets/stylesheets/**/*.scss' 'app/javascript/**/*.scss'", "test": "mocha 'spec/javascripts/**/**spec.+(j|t)s?(x)' 'app/javascript/packages/**/*.spec.+(j|t)s?(x)'", "normalize-yaml": "normalize-yaml", + "generate-browsers-json": "./scripts/generate-browsers-json.js", "clean": "rm -rf public/packs/*", "prebuild": "yarn run clean", - "build": "webpack", + "build": "webpack && yarn generate-browsers-json", "build:css": "build-sass app/assets/stylesheets/*.css.scss --out-dir=app/assets/builds" }, "dependencies": { @@ -27,6 +28,7 @@ "@babel/register": "^7.15.3", "babel-loader": "^9.1.0", "babel-plugin-polyfill-corejs3": "^0.5.2", + "browserslist": "^4.21.5", "cleave.js": "^1.6.0", "core-js": "^3.21.1", "fast-glob": "^3.2.7", diff --git a/scripts/generate-browsers-json.js b/scripts/generate-browsers-json.js new file mode 100755 index 00000000000..f0d044a0ce1 --- /dev/null +++ b/scripts/generate-browsers-json.js @@ -0,0 +1,6 @@ +#!/usr/bin/env node + +const { writeFileSync } = require('fs'); +const browserslist = require('browserslist'); + +writeFileSync('./browsers.json', JSON.stringify(browserslist())); diff --git a/scripts/inherited_proofing/errorable.rb b/scripts/inherited_proofing/errorable.rb deleted file mode 100644 index 2c4bd406de6..00000000000 --- a/scripts/inherited_proofing/errorable.rb +++ /dev/null @@ -1,23 +0,0 @@ -module Scripts - module InheritedProofing - module Errorable - module_function - - def puts_message(message) - puts message - end - - def puts_success(message) - puts_message "Success: #{message}" - end - - def puts_warning(message) - puts_message "Warning: #{message}" - end - - def puts_error(message) - puts_message "Oops! An error occurred: #{message}" - end - end - end -end diff --git a/scripts/inherited_proofing/va/lexis_nexis/phone_finder.rb b/scripts/inherited_proofing/va/lexis_nexis/phone_finder.rb deleted file mode 100644 index 4f32e37e5f1..00000000000 --- a/scripts/inherited_proofing/va/lexis_nexis/phone_finder.rb +++ /dev/null @@ -1,28 +0,0 @@ -module Scripts - module InheritedProofing - module Va - module LexisNexis - module PhoneFinder - class << self - def call(user_pii) - proofer = if IdentityConfig.store.proofer_mock_fallback - Proofing::Mock::AddressMockClient.new - else - Proofing::LexisNexis::PhoneFinder::Proofer.new( - phone_finder_workflow: IdentityConfig.store.lexisnexis_phone_finder_workflow, - account_id: IdentityConfig.store.lexisnexis_account_id, - base_url: IdentityConfig.store.lexisnexis_base_url, - username: IdentityConfig.store.lexisnexis_username, - password: IdentityConfig.store.lexisnexis_password, - request_mode: IdentityConfig.store.lexisnexis_request_mode, - ) - end - - proofer.proof user_pii - end - end - end - end - end - end -end diff --git a/scripts/inherited_proofing/va/lexis_nexis/test_script.rb b/scripts/inherited_proofing/va/lexis_nexis/test_script.rb deleted file mode 100644 index 7a3d17c5262..00000000000 --- a/scripts/inherited_proofing/va/lexis_nexis/test_script.rb +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env ruby - -# rubocop:disable Layout/LineLength - -# Usage -# -# 1) Run with no ARGV and BASE_URI in the Idv::InheritedProofing::Va::Service -# will default to a base uri of: IdentityConfig.store.inherited_proofing_va_base_url -# and a private key using: AppArtifacts.store.oidc_private_key -# -# $ bin/rails r scripts/inherited_proofing/va/lexis_nexis/test_script.rb -# -# 2) To override BASE_URI in the Idv::InheritedProofing::Va::Service class -# and/or use a private key file, and/or force an error return. -# Where -u|-t|-f forces proofing to fail -# -# NOTE: Private key files are forced to be located at "#{Rails.root}/tmp". -# $ bin/rails r scripts/inherited_proofing/va/lexis_nexis/test_script.rb https://staging-api.va.gov private.key -u|-t|-f - -require_relative '../user_attributes/test_server' -require_relative './phone_finder' -require_relative '../../errorable' - -def error_phone_or_default(fail_option:, default_phone:) - return case fail_option - when '-u' - Scripts::InheritedProofing::Errorable.puts_message "Forcing unverifiable phone number failure (#{fail_option})" - Proofing::Mock::AddressMockClient::UNVERIFIABLE_PHONE_NUMBER - when '-t' - Scripts::InheritedProofing::Errorable.puts_message "Forcing proofer timeout failure (#{fail_option})" - Proofing::Mock::AddressMockClient::PROOFER_TIMEOUT_PHONE_NUMBER - when '-f' - Scripts::InheritedProofing::Errorable.puts_message "Forcing failed to contact phone number (#{fail_option})" - Proofing::Mock::AddressMockClient::FAILED_TO_CONTACT_PHONE_NUMBER - else - default_phone - end -end - -raise 'You must run this from the command-line!' unless $PROGRAM_NAME == __FILE__ - -Scripts::InheritedProofing::Errorable.puts_message "\nTesting call to Lexis Nexis Phone Finder - START" - -form, form_response = Scripts::InheritedProofing::Va::UserAttributes::TestServer.new( - auth_code: 'mocked-auth-code-for-testing', - base_uri: ARGV[0], - private_key_file: ARGV[1], -).run - -Scripts::InheritedProofing::Errorable.puts_message "Form response:\n\t#{form_response.to_h}" - -user_pii = form.payload_hash -user_pii[:uuid] = SecureRandom.uuid -# Lexis Nexis Phone Finder expects dob (as opposed to birth_date). -user_pii[:dob] = user_pii.delete(:birth_date) -user_pii[:phone] = error_phone_or_default fail_option: ARGV[2], default_phone: user_pii[:phone] -user_pii.delete(:address) -user_pii.delete(:mhv_data) - -Scripts::InheritedProofing::Errorable.puts_message "Calling Lexis Nexis Phone Finder using:\n\t#{user_pii}}" - -# Verify the user's pii. -address_proofer_results = Scripts::InheritedProofing::Va::LexisNexis::PhoneFinder.call(user_pii) - -Scripts::InheritedProofing::Errorable.puts_message "Call to Lexis Nexis Phone Finder complete. Results:\n\t#{address_proofer_results.to_h}" - -Scripts::InheritedProofing::Errorable.puts_message "\nTesting call to Lexis Nexis Phone Finder - END" - -Scripts::InheritedProofing::Errorable.puts_message "\nDone." -# rubocop:enable Layout/LineLength diff --git a/scripts/inherited_proofing/va/user_attributes/test_script.rb b/scripts/inherited_proofing/va/user_attributes/test_script.rb deleted file mode 100644 index 7586c86ee2e..00000000000 --- a/scripts/inherited_proofing/va/user_attributes/test_script.rb +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env ruby - -# Usage -# -# 1) Run with no ARGV and BASE_URI in the Idv::InheritedProofing::Va::Service -# will default to a base uri of: IdentityConfig.store.inherited_proofing_va_base_url -# and a private key using: AppArtifacts.store.oidc_private_key -# -# $ bin/rails r scripts/inherited_proofing/va/user_attributes/test_script.rb -# -# 2) To override BASE_URI in the Idv::InheritedProofing::Va::Service class -# and/or use a private key file: -# -# rubocop:disable Layout/LineLength -# NOTE: Private key files are forced to be located at "#{Rails.root}/tmp". -# $ bin/rails r scripts/inherited_proofing/va/user_attributes/test_script.rb https://staging-api.va.gov private.key -# rubocop:enable Layout/LineLength - -require_relative '../../../../app/services/idv/inherited_proofing/va/service' -require_relative '../../../../app/forms/idv/inherited_proofing/va/form' -require_relative '../../errorable' -require_relative './test_server' - -raise 'You must run this from the command-line!' unless $PROGRAM_NAME == __FILE__ - -Scripts::InheritedProofing::Errorable.puts_message "\nTesting call to VA API - START\n\n" - -_form, form_response = Scripts::InheritedProofing::Va::UserAttributes::TestServer.new( - auth_code: 'mocked-auth-code-for-testing', - base_uri: ARGV[0], - private_key_file: ARGV[1], -).run - -Scripts::InheritedProofing::Errorable.puts_message "Form response:\n\t#{form_response.to_h}" - -Scripts::InheritedProofing::Errorable.puts_message "\nTesting call to VA API - END" - -Scripts::InheritedProofing::Errorable.puts_message "\nDone." diff --git a/scripts/inherited_proofing/va/user_attributes/test_server.rb b/scripts/inherited_proofing/va/user_attributes/test_server.rb deleted file mode 100644 index ce5a65b2e9b..00000000000 --- a/scripts/inherited_proofing/va/user_attributes/test_server.rb +++ /dev/null @@ -1,88 +0,0 @@ -require_relative '../../errorable' - -module Scripts - module InheritedProofing - module Va - module UserAttributes - class TestServer < Idv::InheritedProofing::Va::Service - include Errorable - - attr_reader :base_uri - - def initialize(auth_code:, private_key_file: nil, base_uri: nil) - super({ auth_code: auth_code }) - - if private_key_file.present? - @private_key_file = force_tmp_private_key_file_name(private_key_file) - end - @base_uri = base_uri || BASE_URI - end - - def run - begin - # rubocop:disable Layout/LineLength - puts_message "Retrieving the user's PII from the VA using auth code: '#{auth_code}' at #{request_uri}..." - puts_message "Retrieved payload containing the user's PII from the VA:\n\tRetrieved user PII: #{user_pii}" - # rubocop:enable Layout/LineLength - - puts_message "Validating payload containing the user's PII from the VA..." - if form_response.success? - puts_success "Retrieved user PII is valid:\n\t#{user_pii}" - else - puts_error "Payload returned from the VA is invalid:" \ - "\n\t#{form.errors.full_messages}" - end - rescue => e - puts_error e.message - end - - [form, form_response] - end - - private - - attr_reader :private_key_file - - # Override - def request_uri - @request_uri ||= "#{ URI(@base_uri) }/inherited_proofing/user_attributes" - end - - def user_pii - @user_pii ||= execute - end - - def form - @form ||= Idv::InheritedProofing::Va::Form.new(payload_hash: user_pii) - end - - def form_response - @form_response ||= form.submit - end - - def private_key - if private_key_file? - if File.exist?(private_key_file) - return OpenSSL::PKey::RSA.new(File.read(private_key_file)) - else - puts_warning "private key file does not exist, using artifacts store: " \ - "#{private_key_file}" - end - end - - AppArtifacts.store.oidc_private_key - end - - def private_key_file? - @private_key_file.present? - end - - # Always ensure we're referencing files in the /tmp/ folder! - def force_tmp_private_key_file_name(private_key_file) - "#{Rails.root}/tmp/#{File.basename(private_key_file)}" - end - end - end - end - end -end diff --git a/scripts/va-api-test.rb b/scripts/va-api-test.rb deleted file mode 100755 index f99e9b6e97d..00000000000 --- a/scripts/va-api-test.rb +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env ruby - -require 'jwt' -require 'jwe' -require 'net/http' - -# Script to test connection to VA API, can be removed once we create code inside the IDV flow -class VaApiTest - def run - uri = URI 'https://staging-api.va.gov/inherited_proofing/user_attributes' - headers = { Authorization: "Bearer #{jwt_token}" } - - response = Net::HTTP.get_response(uri, headers) - decrypt_payload(response) - end - - private - - def jwt_token - payload = { inherited_proofing_auth: 'mocked-auth-code-for-testing', exp: 1.day.from_now.to_i } - JWT.encode(payload, private_key, 'RS256') - end - - def decrypt_payload(response) - payload = JSON.parse(response.body)['data'] - JWE.decrypt(payload, private_key) if payload - end - - def private_key - return AppArtifacts.store.oidc_private_key if private_key_store? - - OpenSSL::PKey::RSA.new(File.read(private_key_file)) - end - - # Returns true if a private key store should be used - # (as opposed to the private key file). - def private_key_store? - Identity::Hostdata.in_datacenter? || !private_key_file? - end - - def private_key_file? - File.exist?(private_key_file) - end - - def private_key_file - @private_key_file ||= 'tmp/va_ip.key' - end -end - -puts(VaApiTest.new.run || 'VaApiTest#run returned no output') if $PROGRAM_NAME == __FILE__ diff --git a/spec/browsers_json_spec.rb b/spec/browsers_json_spec.rb new file mode 100644 index 00000000000..6f4d69508cf --- /dev/null +++ b/spec/browsers_json_spec.rb @@ -0,0 +1,14 @@ +require 'rails_helper' + +RSpec.describe 'browsers.json' do + before(:all) { system 'make browsers.json' } + + subject(:browsers_json) { JSON.parse(File.read(Rails.root.join('browsers.json'))) } + + it 'includes only keys known to BrowserSupport' do + actual_keys = browsers_json.map { |entry| entry.split(' ', 2).first } + known_keys = BrowserSupport::BROWSERSLIST_TO_BROWSER_MAP.keys.map(&:to_s) + + expect(actual_keys - known_keys).to be_empty + end +end diff --git a/spec/controllers/concerns/idv_step_concern_spec.rb b/spec/controllers/concerns/idv_step_concern_spec.rb index 1ddf2d87459..e60cc8692e9 100644 --- a/spec/controllers/concerns/idv_step_concern_spec.rb +++ b/spec/controllers/concerns/idv_step_concern_spec.rb @@ -9,16 +9,16 @@ module Idv class StepController < ApplicationController include IdvStepConcern + + def show + render plain: 'Hello' + end end end describe '#confirm_idv_needed' do controller Idv::StepController do before_action :confirm_idv_needed - - def show - render plain: 'Hello' - end end before(:each) do @@ -55,4 +55,115 @@ def show end end end + + describe '#confirm_address_step_complete' do + controller Idv::StepController do + before_action :confirm_address_step_complete + end + + before(:each) do + sign_in(user) + routes.draw do + get 'show' => 'idv/step#show' + end + end + + context 'the user has completed phone confirmation' do + it 'does not redirect' do + idv_session.vendor_phone_confirmation = true + idv_session.user_phone_confirmation = true + + get :show + + expect(response.body).to eq('Hello') + expect(response.status).to eq(200) + end + end + + context 'the user has not confirmed their phone OTP' do + it 'redirects to OTP confirmation' do + idv_session.vendor_phone_confirmation = true + idv_session.user_phone_confirmation = false + + get :show + + expect(response).to redirect_to(idv_otp_verification_url) + end + end + + context 'the user has not confirmed their phone with the vendor' do + it 'redirects to phone confirmation' do + idv_session.vendor_phone_confirmation = false + idv_session.user_phone_confirmation = false + + get :show + + expect(response).to redirect_to(idv_otp_verification_url) + end + end + + context 'the user has selected GPO for address confirmation' do + it 'does not redirect' do + idv_session.address_verification_mechanism = 'gpo' + + get :show + + expect(response.body).to eq('Hello') + expect(response.status).to eq(200) + end + end + end + + describe '#confirm_verify_info_step_complete' do + controller Idv::StepController do + before_action :confirm_verify_info_step_complete + end + + before(:each) do + sign_in(user) + routes.draw do + get 'show' => 'idv/step#show' + end + end + + context 'the user has completed the verify info step' do + it 'does not redirect and renders the view' do + idv_session.profile_confirmation = true + idv_session.resolution_successful = 'phone' + + get :show + + expect(response.body).to eq('Hello') + expect(response.status).to eq(200) + end + end + + context 'the user has not completed the verify info step' do + it 'redirects to the remote verify info step' do + idv_session.profile_confirmation = nil + idv_session.resolution_successful = nil + + get :show + + expect(response).to redirect_to(idv_verify_info_url) + end + end + + context 'the user has not completed the verify info step with an in-person enrollment' do + it 'redirects to the in-person verify info step' do + idv_session.profile_confirmation = nil + idv_session.resolution_successful = nil + + ProofingComponent.find_or_create_by( + user: user, + ).update!( + document_check: Idp::Constants::Vendors::USPS, + ) + + get :show + + expect(response).to redirect_to(idv_in_person_verify_info_url) + end + end + end end diff --git a/spec/controllers/concerns/inherited_proofing_concern_spec.rb b/spec/controllers/concerns/inherited_proofing_concern_spec.rb deleted file mode 100644 index 5796827f075..00000000000 --- a/spec/controllers/concerns/inherited_proofing_concern_spec.rb +++ /dev/null @@ -1,92 +0,0 @@ -require 'rails_helper' - -RSpec.describe InheritedProofingConcern do - subject do - Class.new do - include InheritedProofingConcern - end.new - end - - before do - allow(IdentityConfig.store).to receive(:inherited_proofing_enabled).and_return(true) - allow(subject).to receive(:va_inherited_proofing_auth_code).and_return auth_code - end - - let(:auth_code) { Idv::InheritedProofing::Va::Mocks::Service::VALID_AUTH_CODE } - let(:payload_hash) { Idv::InheritedProofing::Va::Mocks::Service::PAYLOAD_HASH } - - describe '#inherited_proofing?' do - context 'when inherited proofing proofing is not effect' do - let(:auth_code) { nil } - - it 'returns false' do - expect(subject.inherited_proofing?).to eq false - end - end - - context 'when inherited proofing proofing is effect' do - it 'returns true' do - expect(subject.inherited_proofing?).to eq true - end - end - end - - describe '#inherited_proofing_service_provider' do - context 'when a service provider cannot be identified' do - before do - allow(subject).to receive(:va_inherited_proofing_auth_code).and_return nil - end - - it 'returns nil' do - expect(subject.inherited_proofing_service_provider).to eq nil - end - end - - context 'when a service provider can be identified' do - let(:va) { :va } - - it 'returns the service provider' do - expect(subject.inherited_proofing_service_provider).to eq va - end - end - end - - describe '#va_inherited_proofing?' do - context 'when the va auth code is present' do - it 'returns true' do - expect(subject.va_inherited_proofing?).to eq true - end - end - - context 'when the va auth code is not present' do - let(:auth_code) { nil } - - it 'returns false' do - expect(subject.va_inherited_proofing?).to eq false - end - end - end - - describe '#va_inherited_proofing_auth_code_params_key' do - it 'returns the correct va auth code url query param key' do - expect(subject.va_inherited_proofing_auth_code_params_key).to eq 'inherited_proofing_auth' - end - end - - describe '#inherited_proofing_service_provider_data' do - context 'when there is a va inherited proofing request' do - it 'returns the correct service provider-specific data' do - expect(subject.inherited_proofing_service_provider_data).to \ - eq({ auth_code: auth_code }) - end - end - - context 'when the inherited proofing request cannot be identified' do - let(:auth_code) { nil } - - it 'returns an empty hash' do - expect(subject.inherited_proofing_service_provider_data).to eq({}) - end - end - end -end diff --git a/spec/controllers/concerns/verify_sp_attributes_concern_spec.rb b/spec/controllers/concerns/verify_sp_attributes_concern_spec.rb index d6be336fdfa..1d6977ecd62 100644 --- a/spec/controllers/concerns/verify_sp_attributes_concern_spec.rb +++ b/spec/controllers/concerns/verify_sp_attributes_concern_spec.rb @@ -80,30 +80,6 @@ expect(consent_has_expired?).to eq(false) end end - - context 'when there is an active profile' do - let(:sp_session_identity) do - create(:service_provider_identity, last_consented_at: 15.days.ago, user: user) - end - - before do - create(:profile, :active, verified_at: verified_at, user: user) - end - - context 'when the active profile was verified after last_consented_at' do - let(:verified_at) { 5.days.ago } - it 'is true because the new verified data needs to be consented to sharing' do - expect(consent_has_expired?).to eq(true) - end - end - - context 'when the active profile was verified before last_consented_at' do - let(:verified_at) { 20.days.ago } - it 'is false' do - expect(consent_has_expired?).to eq(false) - end - end - end end describe '#consent_was_revoked?' do @@ -199,6 +175,23 @@ expect(needs_completion_screen_reason).to be_nil end end + + context 'when user is reverified' do + let(:verified_at) { 5.days.ago } + let(:sp_session_identity) do + build( + :service_provider_identity, + user: user, + last_consented_at: 15.days.ago, + ) + end + before do + create(:profile, :active, verified_at: verified_at, user: user) + end + it 'is reverified_after_consent' do + expect(needs_completion_screen_reason).to eq(:reverified_after_consent) + end + end end end @@ -208,4 +201,107 @@ end end end + + describe '#reverified_after_consent?' do + let(:sp_session_identity) { build(:service_provider_identity, user: user) } + let(:user) { build(:user) } + + before do + allow(controller).to receive(:current_user).and_return(user) + allow(controller).to receive(:sp_session_identity).and_return(sp_session_identity) + end + + subject(:reverified_after_consent?) do + controller.reverified_after_consent?(sp_session_identity) + end + + context 'when there is no sp_session_identity' do + let(:sp_session_identity) { nil } + it 'is false' do + expect(reverified_after_consent?).to eq(false) + end + end + + context 'when there is no last_consented_at' do + it 'is false' do + expect(reverified_after_consent?).to eq(false) + end + end + + context 'when last_consented_at within one year' do + let(:sp_session_identity) { build(:service_provider_identity, last_consented_at: 5.days.ago) } + + it 'is false' do + expect(reverified_after_consent?).to eq(false) + end + end + + context 'when the last_consented_at is older than a year ago' do + let(:sp_session_identity) do + build(:service_provider_identity, last_consented_at: 2.years.ago) + end + + it 'is false' do + expect(reverified_after_consent?).to eq(false) + end + end + + context 'when last_consented_at is nil but created_at is within a year' do + let(:sp_session_identity) do + build(:service_provider_identity, last_consented_at: nil, created_at: 4.days.ago) + end + + it 'is false' do + expect(reverified_after_consent?).to eq(false) + end + end + + context 'when last_consented_at is nil and created_at is older than a year' do + let(:sp_session_identity) do + build(:service_provider_identity, last_consented_at: nil, created_at: 4.years.ago) + end + + it 'is false' do + expect(reverified_after_consent?).to eq(false) + end + end + + context 'when the identity has been soft-deleted (consent has been revoked)' do + let(:sp_session_identity) do + build( + :service_provider_identity, + deleted_at: 1.day.ago, + last_consented_at: 2.years.ago, + ) + end + + it 'is false' do + expect(reverified_after_consent?).to eq(false) + end + end + + context 'when there is an active profile' do + let(:sp_session_identity) do + create(:service_provider_identity, last_consented_at: 15.days.ago, user: user) + end + + before do + create(:profile, :active, verified_at: verified_at, user: user) + end + + context 'when the active profile was verified after last_consented_at' do + let(:verified_at) { 5.days.ago } + it 'is true because the new verified data needs to be consented to sharing' do + expect(reverified_after_consent?).to eq(true) + end + end + + context 'when the active profile was verified before last_consented_at' do + let(:verified_at) { 20.days.ago } + it 'is false' do + expect(reverified_after_consent?).to eq(false) + end + end + end + end end diff --git a/spec/controllers/idv/doc_auth_controller_spec.rb b/spec/controllers/idv/doc_auth_controller_spec.rb index 3aadf2c879a..7170c1abfd5 100644 --- a/spec/controllers/idv/doc_auth_controller_spec.rb +++ b/spec/controllers/idv/doc_auth_controller_spec.rb @@ -142,7 +142,7 @@ it 'finishes the flow' do get :show, params: { step: 'welcome' } - expect(response).to redirect_to idv_review_url + expect(response).to redirect_to idv_ssn_url end end end @@ -155,19 +155,18 @@ result = { success: true, errors: {}, - step: 'ssn', + step: 'agreement', flow_path: 'standard', step_count: 1, - pii_like_keypaths: [[:errors, :ssn], [:error_details, :ssn]], irs_reproofing: false, analytics_id: 'Doc Auth', acuant_sdk_upgrade_ab_test_bucket: :default, } - put :update, params: { step: 'ssn', doc_auth: { step: 'ssn', ssn: '111-11-1111' } } + put :update, params: { step: 'agreement', doc_auth: { ial2_consent_given: '1' } } expect(@analytics).to have_received(:track_event).with( - 'IdV: doc auth ssn submitted', result + 'IdV: doc auth agreement submitted', result ) end @@ -176,20 +175,20 @@ allow_any_instance_of(Flow::BaseFlow).to \ receive(:flow_session).and_return(pii_from_doc: {}) - put :update, params: { step: 'ssn', doc_auth: { step: 'ssn', ssn: '666-66-6666' } } - put :update, params: { step: 'ssn', doc_auth: { step: 'ssn', ssn: '111-11-1111' } } + put :update, params: { step: 'agreement', doc_auth: { ial2_consent_given: '1' } } + put :update, params: { step: 'agreement', doc_auth: { ial2_consent_given: '1' } } expect(@analytics).to have_received(:track_event).with( - 'IdV: doc auth ssn submitted', + 'IdV: doc auth agreement submitted', hash_including( - step: 'ssn', + step: 'agreement', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, ), ) expect(@analytics).to have_received(:track_event).with( - 'IdV: doc auth ssn submitted', - hash_including(step: 'ssn', step_count: 2), + 'IdV: doc auth agreement submitted', + hash_including(step: 'agreement', step_count: 2), ) end @@ -233,7 +232,7 @@ it 'finishes the flow' do put :update, params: { step: 'ssn' } - expect(response).to redirect_to idv_review_url + expect(response).to redirect_to idv_ssn_url end end end diff --git a/spec/controllers/idv/document_capture_controller_spec.rb b/spec/controllers/idv/document_capture_controller_spec.rb new file mode 100644 index 00000000000..71f3d831ee0 --- /dev/null +++ b/spec/controllers/idv/document_capture_controller_spec.rb @@ -0,0 +1,103 @@ +require 'rails_helper' + +describe Idv::DocumentCaptureController do + include IdvHelper + + let(:flow_session) do + { 'document_capture_session_uuid' => 'fd14e181-6fb1-4cdc-92e0-ef66dad0df4e', + 'pii_from_doc' => Idp::Constants::MOCK_IDV_APPLICANT.dup, + :threatmetrix_session_id => 'c90ae7a5-6629-4e77-b97c-f1987c2df7d0', + :flow_path => 'standard' } + end + + let(:user) { build(:user) } + let(:service_provider) do + create( + :service_provider, + issuer: 'http://sp.example.com', + app_id: '123', + ) + end + + let(:default_sdk_version) { IdentityConfig.store.idv_acuant_sdk_version_default } + let(:alternate_sdk_version) { IdentityConfig.store.idv_acuant_sdk_version_alternate } + + before do + allow(subject).to receive(:flow_session).and_return(flow_session) + stub_sign_in(user) + end + + describe 'before_actions' do + it 'checks that feature flag is enabled' do + expect(subject).to have_actions( + :before, + :render_404_if_document_capture_controller_disabled, + ) + end + + it 'includes authentication before_action' do + expect(subject).to have_actions( + :before, + :confirm_two_factor_authenticated, + ) + end + end + + context 'when doc_auth_document_capture_controller_enabled' do + before do + allow(IdentityConfig.store).to receive(:doc_auth_document_capture_controller_enabled). + and_return(true) + stub_analytics + stub_attempts_tracker + allow(@analytics).to receive(:track_event) + end + + describe '#show' do + let(:analytics_name) { 'IdV: doc auth document_capture visited' } + let(:analytics_args) do + { + analytics_id: 'Doc Auth', + flow_path: 'standard', + irs_reproofing: false, + step: 'document capture', + step_count: 1, + } + end + + context '#show' do + it 'renders the show template' do + get :show + + expect(response).to render_template :show + end + + it 'sends analytics_visited event' do + get :show + + expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + end + + it 'sends correct step count to analytics' do + get :show + get :show + analytics_args[:step_count] = 2 + + expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + end + end + end + end + + context 'when doc_auth_document_capture_controller_enabled is false' do + before do + allow(IdentityConfig.store).to receive(:doc_auth_document_capture_controller_enabled). + and_return(false) + end + + it 'returns 404' do + get :show + + expect(response.status).to eq(404) + end + end +end diff --git a/spec/controllers/idv/image_uploads_controller_spec.rb b/spec/controllers/idv/image_uploads_controller_spec.rb index 56946a2834c..adc7a82cfae 100644 --- a/spec/controllers/idv/image_uploads_controller_spec.rb +++ b/spec/controllers/idv/image_uploads_controller_spec.rb @@ -73,7 +73,7 @@ any_args, ) - expect(@irs_attempts_api_tracker).not_to receive(:track_event).with( + expect(@irs_attempts_api_tracker).to receive(:track_event).with( :idv_document_upload_submitted, any_args, ) @@ -129,9 +129,21 @@ flow_path: 'standard', ) - expect(@irs_attempts_api_tracker).not_to receive(:track_event).with( + expect(@irs_attempts_api_tracker).to receive(:track_event).with( :idv_document_upload_submitted, - any_args, + { address: nil, + date_of_birth: nil, + document_back_image_filename: nil, + document_expiration: nil, + document_front_image_filename: nil, + document_image_encryption_key: nil, + document_issued: nil, + document_number: nil, + document_state: nil, + failure_reason: { front: ['The selection was not a valid file.'] }, + first_name: nil, + last_name: nil, + success: false }, ) expect(@analytics).not_to receive(:track_event).with( @@ -229,9 +241,27 @@ flow_path: 'standard', ) - expect(@irs_attempts_api_tracker).not_to receive(:track_event).with( + expect(@irs_attempts_api_tracker).to receive(:track_event).with( + :idv_document_upload_rate_limited, + ) + + # This is the last upload which triggers the rate limit, apparently. + # I do find this moderately confusing. + expect(@irs_attempts_api_tracker).to receive(:track_event).with( :idv_document_upload_submitted, - any_args, + { address: nil, + date_of_birth: nil, + document_back_image_filename: nil, + document_expiration: nil, + document_front_image_filename: nil, + document_image_encryption_key: nil, + document_issued: nil, + document_number: nil, + document_state: nil, + failure_reason: { limit: ['We could not verify your ID'] }, + first_name: nil, + last_name: nil, + success: false }, ) expect(@analytics).not_to receive(:track_event).with( diff --git a/spec/controllers/idv/inherited_proofing_cancellations_controller_spec.rb b/spec/controllers/idv/inherited_proofing_cancellations_controller_spec.rb deleted file mode 100644 index 86451d87906..00000000000 --- a/spec/controllers/idv/inherited_proofing_cancellations_controller_spec.rb +++ /dev/null @@ -1,245 +0,0 @@ -require 'rails_helper' - -shared_examples 'an HTML 404 not found' do - it 'returns a 404 not found' do - expect(response).to have_http_status(:not_found) - end -end - -describe Idv::InheritedProofingCancellationsController do - let(:step) { Idv::Flows::InheritedProofingFlow::STEPS.keys.first } - - describe 'before_actions' do - it 'includes before_actions from IdvSession' do - expect(subject).to have_actions(:before, :redirect_if_sp_context_needed) - end - - it 'includes before_actions from InheritedProofing404Concern' do - expect(subject).to have_actions(:before, :render_404_if_disabled) - end - - it 'includes before_actions from AllowlistedFlowStepConcern' do - expect(subject).to have_actions(:before, :flow_step!) - end - end - - describe '#new' do - let(:go_back_path) { '/path/to/return' } - - before do - allow(controller).to receive(:go_back_path).and_return(go_back_path) - end - - context 'when the inherited proofing feature flipper is turned off' do - before do - allow(IdentityConfig.store).to receive(:inherited_proofing_enabled).and_return(false) - stub_sign_in - end - - describe '#new' do - before do - get :new, params: { step: step } - end - - it_behaves_like 'an HTML 404 not found' - end - - describe '#update' do - before do - get :update, params: { step: step } - end - - it_behaves_like 'an HTML 404 not found' - end - - describe '#destroy' do - before do - get :destroy, params: { step: step } - end - - it_behaves_like 'an HTML 404 not found' - end - end - - context 'when the flow step is not in the allowed list' do - before do - stub_sign_in - end - - let(:step) { :not_found_step } - let(:expected_logger_warning) { "Flow step param \"#{step})\" was not whitelisted!" } - - describe '#new' do - before do - expect(Rails.logger).to receive(:warn).with(expected_logger_warning) - get :new, params: { step: step } - end - - it_behaves_like 'an HTML 404 not found' - end - - describe '#update' do - before do - expect(Rails.logger).to receive(:warn).with(expected_logger_warning) - get :update, params: { step: step } - end - - it_behaves_like 'an HTML 404 not found' - end - - describe '#destroy' do - before do - expect(Rails.logger).to receive(:warn).with(expected_logger_warning) - get :destroy, params: { step: step } - end - - it_behaves_like 'an HTML 404 not found' - end - end - - context 'when there is no session' do - it 'redirects to root' do - get :new - - expect(response).to redirect_to(root_url) - end - end - - context 'when there is a session' do - subject(:action) do - get :new, params: { step: step } - end - - before do - stub_sign_in - end - - it 'renders template' do - action - - expect(response).to render_template(:new) - end - - it 'stores go back path' do - action - - expect(controller.user_session[:idv][:go_back_path]).to eq(go_back_path) - end - - it 'tracks the event in analytics' do - stub_analytics - request.env['HTTP_REFERER'] = 'https://example.com/' - - expect(@analytics).to receive(:track_event).with( - 'IdV: cancellation visited', - request_came_from: 'users/sessions#new', - step: step.to_s, - proofing_components: nil, - analytics_id: nil, - ) - - action - end - end - end - - describe '#update' do - subject(:action) do - put :update, params: { step: step, cancel: 'true' } - end - - before do - stub_sign_in - end - - it 'redirects to idv_inherited_proofing_path' do - action - - expect(response).to redirect_to idv_inherited_proofing_url - end - - context 'when a go back path is stored in session' do - let(:go_back_path) { '/path/to/return' } - - before do - allow(controller).to receive(:user_session).and_return( - idv: { go_back_path: go_back_path }, - ) - end - - it 'redirects to go back path' do - action - - expect(response).to redirect_to go_back_path - end - end - - it 'tracks the event in analytics' do - stub_analytics - request.env['HTTP_REFERER'] = 'https://example.com/' - - expect(@analytics).to receive(:track_event).with( - 'IdV: cancellation go back', - request_came_from: 'users/sessions#new', - step: step.to_s, - proofing_components: nil, - analytics_id: nil, - ) - - action - end - end - - describe '#destroy' do - subject(:action) do - delete :destroy, params: { step: step } - end - - context 'when there is no session' do - it 'redirects to root' do - action - - expect(response).to redirect_to(root_url) - end - end - - context 'when there is a session' do - let(:user) { create(:user) } - - before do - stub_sign_in user - allow(controller).to receive(:user_session). - and_return(idv: { go_back_path: '/path/to/return' }) - end - - it 'destroys session' do - expect(controller).to receive(:cancel_session).once - - action - end - - it 'renders a json response with the redirect path set to account_path' do - action - - parsed_body = JSON.parse(response.body, symbolize_names: true) - expect(response).not_to render_template(:destroy) - expect(parsed_body).to eq({ redirect_url: account_path }) - end - - it 'tracks the event in analytics' do - stub_analytics - request.env['HTTP_REFERER'] = 'https://example.com/' - - expect(@analytics).to receive(:track_event).with( - 'IdV: cancellation confirmed', - request_came_from: 'users/sessions#new', - step: step.to_s, - proofing_components: nil, - analytics_id: nil, - ) - - action - end - end - end -end diff --git a/spec/controllers/idv/inherited_proofing_controller_spec.rb b/spec/controllers/idv/inherited_proofing_controller_spec.rb deleted file mode 100644 index 2fc7541ff39..00000000000 --- a/spec/controllers/idv/inherited_proofing_controller_spec.rb +++ /dev/null @@ -1,83 +0,0 @@ -require 'rails_helper' - -shared_examples 'the flow steps work correctly' do - describe '#index' do - it 'redirects to the first step' do - get :index - - expect(response).to redirect_to idv_inherited_proofing_step_url(step: :get_started) - end - - context 'when the inherited proofing feature flag is disabled' do - it 'returns 404 not found' do - allow(IdentityConfig.store).to receive(:inherited_proofing_enabled).and_return(false) - - get :index - - expect(response).to have_http_status(:not_found) - end - end - end - - describe '#show' do - it 'renders the correct template' do - expect(subject).to receive(:render).with( - template: 'layouts/flow_step', - locals: hash_including( - :flow_session, - flow_namespace: 'idv', - step_template: 'idv/inherited_proofing/get_started', - step_indicator: hash_including( - :steps, - current_step: :getting_started, - ), - ), - ).and_call_original - - get :show, params: { step: 'get_started' } - end - - it 'redirects to the configured next step' do - mock_next_step(:some_next_step) - get :show, params: { step: 'getting_started' } - - expect(response).to redirect_to idv_inherited_proofing_step_url(:some_next_step) - end - - it 'redirects to the first step if a non-existent step is given' do - get :show, params: { step: 'non_existent_step' } - - expect(response).to redirect_to idv_inherited_proofing_step_url(step: :get_started) - end - end -end - -def mock_next_step(step) - allow_any_instance_of(Idv::Flows::InheritedProofingFlow).to receive(:next_step).and_return(step) -end - -describe Idv::InheritedProofingController do - let(:sp) { nil } - let(:user) { build(:user) } - - before do - allow(controller).to receive(:current_sp).and_return(sp) - stub_sign_in(user) - end - - context 'when VA inherited proofing mock is enabled' do - before do - allow(IdentityConfig.store).to receive(:va_inherited_proofing_mock_enabled).and_return(true) - end - - it_behaves_like 'the flow steps work correctly' - end - - context 'when VA inherited proofing mock is not enabled' do - before do - allow(IdentityConfig.store).to receive(:va_inherited_proofing_mock_enabled).and_return(false) - end - - it_behaves_like 'the flow steps work correctly' - end -end diff --git a/spec/controllers/idv/otp_verification_controller_spec.rb b/spec/controllers/idv/otp_verification_controller_spec.rb index 8f7cbcd326a..e1dec31b047 100644 --- a/spec/controllers/idv/otp_verification_controller_spec.rb +++ b/spec/controllers/idv/otp_verification_controller_spec.rb @@ -46,7 +46,7 @@ it 'redirects to the delivery method path' do get :show - expect(response).to redirect_to(idv_otp_delivery_method_path) + expect(response).to redirect_to(idv_phone_url) end end @@ -76,7 +76,7 @@ it 'redirects to otp delivery method selection' do put :update, params: otp_code_param - expect(response).to redirect_to(idv_otp_delivery_method_path) + expect(response).to redirect_to(idv_phone_url) end end diff --git a/spec/controllers/idv/phone_controller_spec.rb b/spec/controllers/idv/phone_controller_spec.rb index c91fc19a7da..8c60e77ca28 100644 --- a/spec/controllers/idv/phone_controller_spec.rb +++ b/spec/controllers/idv/phone_controller_spec.rb @@ -17,7 +17,7 @@ expect(subject).to have_actions( :before, :confirm_two_factor_authenticated, - :confirm_idv_applicant_created, + :confirm_verify_info_step_complete, ) end end diff --git a/spec/controllers/idv/review_controller_spec.rb b/spec/controllers/idv/review_controller_spec.rb index a5782b567bc..80e1b1a203a 100644 --- a/spec/controllers/idv/review_controller_spec.rb +++ b/spec/controllers/idv/review_controller_spec.rb @@ -19,7 +19,9 @@ service_provider: nil, ) idv_session.profile_confirmation = true + idv_session.resolution_successful = 'phone' idv_session.vendor_phone_confirmation = true + idv_session.user_phone_confirmation = true idv_session.applicant = applicant.with_indifferent_access idv_session end @@ -34,8 +36,8 @@ expect(subject).to have_actions( :before, :confirm_two_factor_authenticated, - :confirm_idv_applicant_created, - :confirm_idv_steps_complete, + :confirm_verify_info_step_complete, + :confirm_address_step_complete, ) end @@ -68,63 +70,7 @@ def show it 'redirects to address step' do get :show - expect(response).to redirect_to idv_phone_path - end - end - end - - describe '#confirm_idv_phone_confirmed' do - controller do - before_action :confirm_idv_phone_confirmed - - def show - render plain: 'Hello' - end - end - - before(:each) do - stub_sign_in(user) - allow(subject).to receive(:idv_session).and_return(idv_session) - routes.draw do - get 'show' => 'idv/review#show' - end - end - - context 'user is verifying by mail' do - before do - allow(idv_session).to receive(:address_verification_mechanism).and_return('gpo') - end - - it 'does not redirect' do - get :show - - expect(response.body).to eq 'Hello' - end - end - - context 'user phone is confirmed' do - before do - allow(idv_session).to receive(:address_verification_mechanism).and_return('phone') - allow(idv_session).to receive(:phone_confirmed?).and_return(true) - end - - it 'does not redirect' do - get :show - - expect(response.body).to eq 'Hello' - end - end - - context 'user phone is not confirmed' do - before do - allow(idv_session).to receive(:address_verification_mechanism).and_return('phone') - allow(idv_session).to receive(:phone_confirmed?).and_return(false) - end - - it 'redirects to phone confirmation' do - get :show - - expect(response).to redirect_to idv_otp_verification_path + expect(response).to redirect_to idv_otp_verification_url end end end diff --git a/spec/controllers/idv/sessions_controller_spec.rb b/spec/controllers/idv/sessions_controller_spec.rb index faa34fca6b7..4b9bf2396e1 100644 --- a/spec/controllers/idv/sessions_controller_spec.rb +++ b/spec/controllers/idv/sessions_controller_spec.rb @@ -15,7 +15,6 @@ allow(subject).to receive(:idv_session).and_return(idv_session) controller.user_session['idv/doc_auth'] = flow_session controller.user_session['idv/in_person'] = flow_session - controller.user_session['idv/inherited_proofing'] = flow_session controller.user_session[:decrypted_pii] = pii end @@ -37,10 +36,6 @@ expect(controller.user_session['idv/in_person']).to be_blank end - it 'clears the idv/inherited_proofing session' do - expect(controller.user_session['idv/inherited_proofing']).to be_blank - end - it 'clears the decrypted_pii session' do expect(controller.user_session[:decrypted_pii]).to be_blank end diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb index 5a4509a1571..ef51bd651bd 100644 --- a/spec/controllers/idv/verify_info_controller_spec.rb +++ b/spec/controllers/idv/verify_info_controller_spec.rb @@ -60,6 +60,7 @@ stub_analytics stub_attempts_tracker allow(@analytics).to receive(:track_event) + allow(@irs_attempts_api_tracker).to receive(:track_event) end it 'renders the show template' do @@ -119,6 +120,14 @@ end end + it 'redirects to ssn controller when ssn info is missing' do + flow_session[:pii_from_doc][:ssn] = nil + + get :show + + expect(response).to redirect_to(idv_ssn_url) + end + context 'when the user is ssn throttled' do before do Throttle.new( @@ -129,21 +138,6 @@ ).increment_to_throttled! end - context 'when using new ssn controller' do - before do - allow(IdentityConfig.store).to receive(:doc_auth_ssn_controller_enabled). - and_return(true) - end - - it 'redirects to ssn controller when ssn info is missing' do - flow_session[:pii_from_doc][:ssn] = nil - - get :show - - expect(response).to redirect_to(idv_ssn_url) - end - end - it 'redirects to ssn failure url' do get :show @@ -179,6 +173,92 @@ get :show end end + + context 'when proofing_device_profiling is enabled' do + let(:idv_result) do + { + context: { + stages: { + threatmetrix: { + transaction_id: 1, + review_status: review_status, + response_body: { + tmx_summary_reason_code: ['Identity_Negative_History'], + }, + }, + }, + }, + errors: {}, + exception: nil, + success: true, + } + end + + let(:document_capture_session) do + document_capture_session = DocumentCaptureSession.create!(user: user) + document_capture_session.create_proofing_session + document_capture_session.store_proofing_result(idv_result) + document_capture_session + end + + let(:expected_failure_reason) { DocAuthHelper::SAMPLE_TMX_SUMMARY_REASON_CODE } + + before do + controller. + idv_session.verify_info_step_document_capture_session_uuid = document_capture_session.uuid + allow(IdentityConfig.store).to receive(:proofing_device_profiling).and_return(:enabled) + allow(IdentityConfig.store).to receive(:irs_attempt_api_track_tmx_fraud_check_event). + and_return(true) + end + + context 'when threatmetrix response is Pass' do + let(:review_status) { 'pass' } + + it 'it logs IRS idv_tmx_fraud_check event' do + expect(@irs_attempts_api_tracker).to receive(:idv_tmx_fraud_check).with( + success: true, + failure_reason: nil, + ) + get :show + end + end + + context 'when threatmetrix response is No Result' do + let(:review_status) { 'no_result' } + + it 'it logs IRS idv_tmx_fraud_check event' do + expect(@irs_attempts_api_tracker).to receive(:idv_tmx_fraud_check).with( + success: false, + failure_reason: expected_failure_reason, + ) + get :show + end + end + + context 'when threatmetrix response is Reject' do + let(:review_status) { 'reject' } + + it 'it logs IRS idv_tmx_fraud_check event' do + expect(@irs_attempts_api_tracker).to receive(:idv_tmx_fraud_check).with( + success: false, + failure_reason: expected_failure_reason, + ) + get :show + end + end + + context 'when threatmetrix response is Review' do + let(:review_status) { 'review' } + + it 'it logs IRS idv_tmx_fraud_check event' do + expect(@irs_attempts_api_tracker).to receive(:idv_tmx_fraud_check).with( + success: false, + failure_reason: expected_failure_reason, + ) + get :show + end + end + end end describe '#update' do diff --git a/spec/controllers/idv_controller_spec.rb b/spec/controllers/idv_controller_spec.rb index 8775f2e6003..d7f379b73a9 100644 --- a/spec/controllers/idv_controller_spec.rb +++ b/spec/controllers/idv_controller_spec.rb @@ -70,13 +70,6 @@ expect(response).to redirect_to idv_doc_auth_path end - it 'redirects to inherited proofing with a VA inherited proofing session' do - allow(controller).to receive(:va_inherited_proofing?).and_return(true) - - get :index - expect(response).to redirect_to idv_inherited_proofing_path - end - context 'no SP context' do let(:user) { build(:user, password: ControllerHelper::VALID_PASSWORD) } @@ -131,7 +124,6 @@ get :activated expect(response).to render_template(:activated) - expect(subject.idv_session.alive?).to eq false end end diff --git a/spec/controllers/openid_connect/authorization_controller_spec.rb b/spec/controllers/openid_connect/authorization_controller_spec.rb index 169aa997467..d0156405b7d 100644 --- a/spec/controllers/openid_connect/authorization_controller_spec.rb +++ b/spec/controllers/openid_connect/authorization_controller_spec.rb @@ -421,28 +421,6 @@ end end - context 'with an inherited_proofing_auth code' do - before do - params[inherited_proofing_auth_key] = inherited_proofing_auth_value - action - end - - let(:inherited_proofing_auth_key) { 'inherited_proofing_auth' } - let(:inherited_proofing_auth_value) { SecureRandom.hex } - let(:decorated_session) { controller.view_context.decorated_session } - - it 'persists the inherited_proofing_auth value' do - expect(decorated_session.request_url_params[inherited_proofing_auth_key]).to \ - eq inherited_proofing_auth_value - end - - it 'redirects to SP landing page with the request_id in the params' do - sp_request_id = ServiceProviderRequestProxy.last.uuid - - expect(response).to redirect_to new_user_session_url(request_id: sp_request_id) - end - end - it 'redirects to SP landing page with the request_id in the params' do action sp_request_id = ServiceProviderRequestProxy.last.uuid diff --git a/spec/features/idv/actions/redo_document_capture_action_spec.rb b/spec/features/idv/actions/redo_document_capture_action_spec.rb index 19dd5d685cd..55dde9e3f43 100644 --- a/spec/features/idv/actions/redo_document_capture_action_spec.rb +++ b/spec/features/idv/actions/redo_document_capture_action_spec.rb @@ -29,6 +29,9 @@ DocAuth::Mock::DocAuthMockClient.reset! attach_and_submit_images + expect(current_path).to eq(idv_ssn_path) + fill_out_ssn_form_with_ssn_that_fails_resolution + click_on t('forms.buttons.submit.update') expect(current_path).to eq(idv_verify_info_path) check t('forms.ssn.show') expect(page).to have_content(DocAuthHelper::SSN_THAT_FAILS_RESOLUTION) @@ -62,6 +65,9 @@ DocAuth::Mock::DocAuthMockClient.reset! attach_and_submit_images + expect(current_path).to eq(idv_ssn_path) + fill_out_ssn_form_with_ssn_that_fails_resolution + click_on t('forms.buttons.submit.update') expect(current_path).to eq(idv_verify_info_path) check t('forms.ssn.show') expect(page).to have_content(DocAuthHelper::SSN_THAT_FAILS_RESOLUTION) diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb index 1eb36c17a9b..ba1a8c83be5 100644 --- a/spec/features/idv/analytics_spec.rb +++ b/spec/features/idv/analytics_spec.rb @@ -26,7 +26,7 @@ 'IdV: doc auth ssn submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'ssn', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify visited' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify submitted' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, context: { adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil, drivers_license_check_info: nil }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' } } } } }, + 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, context: { adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil, drivers_license_check_info: nil }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' } } } } }, 'IdV: phone of record visited' => { proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis' } }, 'IdV: phone confirmation form' => { success: true, errors: {}, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis' }, otp_delivery_preference: 'sms' }, 'IdV: phone confirmation vendor' => { success: true, errors: {}, vendor: { exception: nil, vendor_name: 'AddressMock', transaction_id: 'address-mock-transaction-id-123', timed_out: false, reference: '' }, new_phone_added: false, proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', address_check: 'lexis_nexis_address' }, area_code: '202', country_code: 'US', phone_fingerprint: anything }, @@ -61,7 +61,7 @@ 'IdV: doc auth ssn submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'ssn', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify visited' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify submitted' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, context: { adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil, drivers_license_check_info: nil }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' } } } } }, + 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, context: { adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil, drivers_license_check_info: nil }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' } } } } }, 'IdV: phone of record visited' => { proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis' } }, 'IdV: USPS address letter requested' => { resend: false, proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis' } }, 'IdV: review info visited' => { address_verification_method: 'gpo', proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', address_check: 'gpo_letter' } }, diff --git a/spec/features/idv/cancel_spec.rb b/spec/features/idv/cancel_spec.rb index 793b2e734c2..25695e60558 100644 --- a/spec/features/idv/cancel_spec.rb +++ b/spec/features/idv/cancel_spec.rb @@ -92,7 +92,7 @@ expect(fake_analytics).to have_logged_event( 'IdV: cancellation visited', proofing_components: { document_check: 'mock', document_type: 'state_id' }, - request_came_from: 'idv/doc_auth#show', + request_came_from: 'idv/ssn#show', step: 'ssn', ) diff --git a/spec/features/idv/doc_auth/document_capture_spec.rb b/spec/features/idv/doc_auth/document_capture_spec.rb new file mode 100644 index 00000000000..40b20635272 --- /dev/null +++ b/spec/features/idv/doc_auth/document_capture_spec.rb @@ -0,0 +1,46 @@ +require 'rails_helper' + +feature 'doc auth document capture step', :js do + include IdvStepHelper + include DocAuthHelper + include ActionView::Helpers::DateHelper + + let(:max_attempts) { IdentityConfig.store.doc_auth_max_attempts } + let(:user) { user_with_2fa } + let(:doc_auth_enable_presigned_s3_urls) { false } + let(:fake_analytics) { FakeAnalytics.new } + let(:sp_name) { 'Test SP' } + before do + allow(IdentityConfig.store).to receive(:doc_auth_document_capture_controller_enabled). + and_return(true) + allow(IdentityConfig.store).to receive(:doc_auth_enable_presigned_s3_urls). + and_return(doc_auth_enable_presigned_s3_urls) + allow(Identity::Hostdata::EC2).to receive(:load). + and_return(OpenStruct.new(region: 'us-west-2', account_id: '123456789')) + allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics) + allow_any_instance_of(ServiceProviderSessionDecorator).to receive(:sp_name).and_return(sp_name) + + visit_idp_from_oidc_sp_with_ial2 + + sign_in_and_2fa_user(user) + complete_doc_auth_steps_before_document_capture_step + end + + it 'shows the new DocumentCapture page for desktop standard flow' do + visit(idv_document_capture_url) + expect(page).to have_current_path(idv_document_capture_url) + + expect(page).to have_content(t('doc_auth.headings.document_capture')) + expect(page).to have_content(t('step_indicator.flows.idv.verify_id')) + + expect(fake_analytics).to have_logged_event( + 'IdV: doc auth document_capture visited', + flow_path: 'standard', + step: 'document_capture', + step_count: 1, + analytics_id: 'Doc Auth', + irs_reproofing: false, + acuant_sdk_upgrade_ab_test_bucket: :default, + ) + end +end diff --git a/spec/features/idv/doc_auth/document_capture_step_spec.rb b/spec/features/idv/doc_auth/document_capture_step_spec.rb index a630d6094c0..a59ea8dece3 100644 --- a/spec/features/idv/doc_auth/document_capture_step_spec.rb +++ b/spec/features/idv/doc_auth/document_capture_step_spec.rb @@ -207,22 +207,8 @@ end end - context 'when new ssn controller is enabled' do - before do - allow(IdentityConfig.store).to receive(:doc_auth_ssn_controller_enabled). - and_return(true) - end - it 'redirects to ssn controller' do - expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_id')) - - attach_and_submit_images - - expect(page).to have_current_path(idv_ssn_url) - end - end - def next_step - idv_doc_auth_ssn_step + idv_ssn_url end def expect_costing_for_document diff --git a/spec/features/idv/doc_auth/link_sent_step_spec.rb b/spec/features/idv/doc_auth/link_sent_step_spec.rb index e3d5281e940..157a3ef8fe1 100644 --- a/spec/features/idv/doc_auth/link_sent_step_spec.rb +++ b/spec/features/idv/doc_auth/link_sent_step_spec.rb @@ -22,7 +22,7 @@ mock_doc_captured(user.id) click_idv_continue - expect(page).to have_current_path(idv_doc_auth_ssn_step) + expect(page).to have_current_path(idv_ssn_path) end it 'proceeds to the next page if the user does not have a phone' do @@ -32,7 +32,7 @@ mock_doc_captured(user.id) click_idv_continue - expect(page).to have_current_path(idv_doc_auth_ssn_step) + expect(page).to have_current_path(idv_ssn_path) end it 'does not proceed to the next page if the capture flow is incomplete' do @@ -80,7 +80,7 @@ mock_doc_captured(user.id) expect(page).to have_content(t('doc_auth.headings.ssn'), wait: 6) - expect(page).to have_current_path(idv_doc_auth_ssn_step) + expect(page).to have_current_path(idv_ssn_path) end end diff --git a/spec/features/idv/doc_auth/ssn_spec.rb b/spec/features/idv/doc_auth/ssn_spec.rb deleted file mode 100644 index d1d7c6b373d..00000000000 --- a/spec/features/idv/doc_auth/ssn_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'rails_helper' - -feature 'doc auth ssn step', :js do - include IdvStepHelper - include DocAuthHelper - include DocCaptureHelper - - before do - allow(IdentityConfig.store).to receive(:proofing_device_profiling).and_return(:enabled) - allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_org_id).and_return('test_org') - allow(IdentityConfig.store).to receive(:doc_auth_ssn_controller_enabled).and_return(true) - - sign_in_and_2fa_user - complete_doc_auth_steps_before_ssn_step - end - - it 'proceeds to the next page with valid info' do - expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_info')) - - fill_out_ssn_form_ok - - match = page.body.match(/session_id=(?[^"&]+)/) - session_id = match && match[:session_id] - expect(session_id).to be_present - - select 'Review', from: 'mock_profiling_result' - - expect(page.find_field(t('idv.form.ssn_label_html'))['aria-invalid']).to eq('false') - click_idv_continue - - expect(page).to have_current_path(idv_verify_info_url) - - profiling_result = Proofing::Mock::DeviceProfilingBackend.new.profiling_result(session_id) - expect(profiling_result).to eq('review') - end - - it 'does not proceed to the next page with invalid info' do - fill_out_ssn_form_fail - click_idv_continue - - expect(page.find_field(t('idv.form.ssn_label_html'))['aria-invalid']).to eq('true') - - expect(page).to have_current_path(idv_ssn_url) - end -end diff --git a/spec/features/idv/doc_auth/ssn_step_spec.rb b/spec/features/idv/doc_auth/ssn_step_spec.rb index b7c1269bf56..6bb05ad4506 100644 --- a/spec/features/idv/doc_auth/ssn_step_spec.rb +++ b/spec/features/idv/doc_auth/ssn_step_spec.rb @@ -27,7 +27,7 @@ expect(page.find_field(t('idv.form.ssn_label_html'))['aria-invalid']).to eq('false') click_idv_continue - expect(page).to have_current_path(idv_verify_info_path) + expect(page).to have_current_path(idv_verify_info_url) profiling_result = Proofing::Mock::DeviceProfilingBackend.new.profiling_result(session_id) expect(profiling_result).to eq('review') @@ -39,6 +39,6 @@ expect(page.find_field(t('idv.form.ssn_label_html'))['aria-invalid']).to eq('true') - expect(page).to have_current_path(idv_doc_auth_ssn_step) + expect(page).to have_current_path(idv_ssn_url) end end diff --git a/spec/features/idv/doc_auth/test_credentials_spec.rb b/spec/features/idv/doc_auth/test_credentials_spec.rb index a7c57d8818b..2117b529bdf 100644 --- a/spec/features/idv/doc_auth/test_credentials_spec.rb +++ b/spec/features/idv/doc_auth/test_credentials_spec.rb @@ -23,7 +23,7 @@ attach_file 'Back of your ID', File.expand_path('spec/fixtures/ial2_test_credential.yml') click_on 'Submit' - expect(page).to have_current_path(idv_doc_auth_ssn_step) + expect(page).to have_current_path(idv_ssn_path) fill_out_ssn_form_ok click_idv_continue diff --git a/spec/features/idv/doc_auth/verify_info_step_spec.rb b/spec/features/idv/doc_auth/verify_info_step_spec.rb index d4df7d5ca15..b344d7b5268 100644 --- a/spec/features/idv/doc_auth/verify_info_step_spec.rb +++ b/spec/features/idv/doc_auth/verify_info_step_spec.rb @@ -42,6 +42,9 @@ expect(page).to have_content(t('headings.verify')) expect(page).to have_content(t('step_indicator.flows.idv.verify_info')) + # DOB is in full month format (October) + expect(page).to have_text(t('date.month_names')[10]) + # SSN is masked until revealed expect(page).to have_text(DocAuthHelper::GOOD_SSN_MASKED) expect(page).not_to have_text(DocAuthHelper::GOOD_SSN) @@ -53,18 +56,33 @@ it 'allows the user to enter in a new address and displays updated info' do click_button t('idv.buttons.change_address_label') fill_in 'idv_form_zipcode', with: '12345' + fill_in 'idv_form_address2', with: 'Apt 3E' + click_button t('forms.buttons.submit.update') expect(page).to have_current_path(idv_verify_info_path) expect(page).to have_content('12345') + expect(page).to have_content('Apt 3E') + + click_idv_continue + + expect(fake_analytics).to have_logged_event( + 'IdV: doc auth verify proofing results', + hash_including(address_edited: true, address_line2_present: true), + ) end it 'allows the user to enter in a new ssn and displays updated info' do - click_button t('idv.buttons.change_ssn_label') + click_link t('idv.buttons.change_ssn_label') + expect(page).to have_current_path(idv_ssn_path) fill_in t('idv.form.ssn_label_html'), with: '900456789' click_button t('forms.buttons.submit.update') + expect(fake_analytics).to have_logged_event( + 'IdV: doc auth redo_ssn submitted', + ) + expect(page).to have_current_path(idv_verify_info_path) expect(page).to have_text('9**-**-***9') @@ -91,7 +109,7 @@ expect(DocAuthLog.find_by(user_id: user.id).aamva).to eq(true) expect(fake_analytics).to have_logged_event( 'IdV: doc auth verify proofing results', - hash_including(address_edited: false), + hash_including(address_edited: false, address_line2_present: false), ) end @@ -372,31 +390,4 @@ expect(page).to have_current_path(idv_phone_path) end end - - context 'with ssn_controller enabled' do - before do - allow(IdentityConfig.store).to receive(:doc_auth_ssn_controller_enabled). - and_return(true) - sign_in_and_2fa_user - complete_doc_auth_steps_before_verify_step - end - - it 'uses ssn controller to enter a new ssn and displays updated info' do - click_link t('idv.buttons.change_ssn_label') - expect(page).to have_current_path(idv_ssn_path) - - fill_in t('idv.form.ssn_label_html'), with: '900456789' - click_button t('forms.buttons.submit.update') - - expect(fake_analytics).to have_logged_event( - 'IdV: doc auth redo_ssn submitted', - ) - - expect(page).to have_current_path(idv_verify_info_path) - - expect(page).to have_text('9**-**-***9') - check t('forms.ssn.show') - expect(page).to have_text('900-45-6789') - end - end end diff --git a/spec/features/idv/doc_capture/document_capture_step_spec.rb b/spec/features/idv/doc_capture/document_capture_step_spec.rb index 515e70a4f34..96b44e0990c 100644 --- a/spec/features/idv/doc_capture/document_capture_step_spec.rb +++ b/spec/features/idv/doc_capture/document_capture_step_spec.rb @@ -62,7 +62,7 @@ using_doc_capture_session { attach_and_submit_images } click_idv_continue - expect(page).to have_current_path(idv_doc_auth_ssn_step) + expect(page).to have_current_path(idv_ssn_path) expect(fake_analytics).to have_logged_event( 'IdV: doc auth document_capture submitted', hash_including( @@ -98,7 +98,7 @@ click_button t('forms.buttons.continue') end - expect(page).to have_current_path(idv_doc_auth_ssn_step, wait: 10) + expect(page).to have_current_path(idv_ssn_path, wait: 10) end end @@ -119,7 +119,7 @@ end click_idv_continue - expect(page).to have_current_path(idv_doc_auth_ssn_step) + expect(page).to have_current_path(idv_ssn_path) end it 'does not advance original session with errors' do @@ -156,7 +156,7 @@ click_button t('forms.buttons.continue') end - expect(page).to have_current_path(idv_doc_auth_ssn_step, wait: 10) + expect(page).to have_current_path(idv_ssn_path, wait: 10) end end end diff --git a/spec/features/idv/hybrid_flow_spec.rb b/spec/features/idv/hybrid_flow_spec.rb index 867f9552699..a1e2049191c 100644 --- a/spec/features/idv/hybrid_flow_spec.rb +++ b/spec/features/idv/hybrid_flow_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe 'Hybrid Flow' do +describe 'Hybrid Flow', :allow_net_connect_on_start do include IdvHelper include DocAuthHelper @@ -40,6 +40,7 @@ perform_in_browser(:desktop) do expect(page).to_not have_content(t('doc_auth.headings.text_message'), wait: 10) + expect(page).to have_current_path(idv_ssn_path) fill_out_ssn_form_ok click_idv_continue @@ -60,55 +61,6 @@ end end - context 'with ssn controller enabled' do - before do - allow(IdentityConfig.store).to receive(:doc_auth_ssn_controller_enabled).and_return(true) - end - - it 'proofs and hands off to mobile', js: true do - user = nil - - perform_in_browser(:desktop) do - user = sign_in_and_2fa_user - complete_doc_auth_steps_before_send_link_step - fill_in :doc_auth_phone, with: '415-555-0199' - click_idv_continue - - expect(page).to have_content(t('doc_auth.headings.text_message')) - end - - expect(@sms_link).to be_present - - perform_in_browser(:mobile) do - visit @sms_link - attach_and_submit_images - expect(page).to have_text(t('doc_auth.instructions.switch_back')) - end - - perform_in_browser(:desktop) do - expect(page).to_not have_content(t('doc_auth.headings.text_message'), wait: 10) - expect(page).to have_current_path(idv_ssn_path) - - fill_out_ssn_form_ok - click_idv_continue - - expect(page).to have_content(t('headings.verify')) - click_idv_continue - - fill_out_phone_form_ok - verify_phone_otp - - fill_in t('idv.form.password'), with: Features::SessionHelper::VALID_PASSWORD - click_idv_continue - - acknowledge_and_confirm_personal_key - - expect(page).to have_current_path(account_path) - expect(page).to have_content(t('headings.account.verified_account')) - end - end - end - it 'shows the waiting screen correctly after cancelling from mobile and restarting', js: true do user = nil diff --git a/spec/features/idv/inherited_proofing/agreement_step_spec.rb b/spec/features/idv/inherited_proofing/agreement_step_spec.rb deleted file mode 100644 index 47e05fb191d..00000000000 --- a/spec/features/idv/inherited_proofing/agreement_step_spec.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'rails_helper' - -feature 'inherited proofing agreement' do - include InheritedProofingHelper - - before do - allow(IdentityConfig.store).to receive(:va_inherited_proofing_mock_enabled).and_return true - allow_any_instance_of(Idv::InheritedProofingController).to \ - receive(:va_inherited_proofing?).and_return true - allow_any_instance_of(Idv::InheritedProofingController).to \ - receive(:va_inherited_proofing_auth_code).and_return auth_code - end - - let(:auth_code) { Idv::InheritedProofing::Va::Mocks::Service::VALID_AUTH_CODE } - - def expect_ip_verify_info_step - expect(page).to have_current_path(idv_ip_verify_info_step) - end - - def expect_inherited_proofing_first_step - expect(page).to have_current_path(idv_inherited_proofing_agreement_step) - end - - context 'when JS is enabled', :js do - before do - sign_in_and_2fa_user - complete_inherited_proofing_steps_before_agreement_step - end - - it 'shows an inline error if the user clicks continue without giving consent' do - click_continue - expect_inherited_proofing_first_step - expect(page).to have_content(t('forms.validation.required_checkbox')) - end - - it 'allows the user to continue after checking the checkbox' do - check t('inherited_proofing.instructions.consent', app_name: APP_NAME) - click_continue - - expect_ip_verify_info_step - end - - context 'when clicking on the Cancel link' do - it 'redirects to the Cancellation UI' do - click_link t('links.cancel') - expect(page).to have_current_path(idv_inherited_proofing_cancel_path(step: :agreement)) - end - end - end - - context 'when JS is disabled' do - before do - sign_in_and_2fa_user - complete_inherited_proofing_steps_before_agreement_step - end - - it 'shows the notice if the user clicks continue without giving consent' do - click_continue - - expect_inherited_proofing_first_step - expect(page).to have_content(t('errors.doc_auth.consent_form')) - end - - it 'allows the user to continue after checking the checkbox' do - check t('inherited_proofing.instructions.consent', app_name: APP_NAME) - click_continue - - expect_ip_verify_info_step - end - - context 'when clicking on the Cancel link' do - it 'redirects to the Cancellation UI' do - click_link t('links.cancel') - expect(page).to have_current_path(idv_inherited_proofing_cancel_path(step: :agreement)) - end - end - end -end diff --git a/spec/features/idv/inherited_proofing/analytics_spec.rb b/spec/features/idv/inherited_proofing/analytics_spec.rb deleted file mode 100644 index 43462bb74e0..00000000000 --- a/spec/features/idv/inherited_proofing/analytics_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'rails_helper' - -feature 'Inherited Proofing Analytics Regression', js: true do - include InheritedProofingHelper - - let(:user) { user_with_2fa } - let(:fake_analytics) { FakeAnalytics.new } - let(:auth_code) { Idv::InheritedProofing::Va::Mocks::Service::VALID_AUTH_CODE } - # rubocop:disable Layout/LineLength - let(:happy_path_events) do - { - 'IdV: inherited proofing get started visited' => { flow_path: 'standard', step: 'get_started', step_count: 1, analytics_id: 'Inherited Proofing', irs_reproofing: false }, - 'IdV: inherited proofing get started submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'get_started', step_count: 1, analytics_id: 'Inherited Proofing', irs_reproofing: false }, - 'IdV: inherited proofing agreement visited' => { flow_path: 'standard', step: 'agreement', step_count: 1, analytics_id: 'Inherited Proofing', irs_reproofing: false }, - 'IdV: inherited proofing agreement submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'agreement', step_count: 1, analytics_id: 'Inherited Proofing', irs_reproofing: false }, - 'IdV: doc auth verify_wait visited' => { flow_path: 'standard', step: 'verify_wait', step_count: 1, analytics_id: 'Inherited Proofing', irs_reproofing: false }, - 'IdV: doc auth optional verify_wait submitted' => { success: true, errors: {}, step: 'verify_wait_step_show', analytics_id: 'Inherited Proofing' }, - 'IdV: doc auth verify visited' => { flow_path: 'standard', step: 'verify_info', step_count: 1, analytics_id: 'Inherited Proofing', irs_reproofing: false }, - 'IdV: doc auth verify submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'verify_info', step_count: 1, analytics_id: 'Inherited Proofing', irs_reproofing: false }, - } - end - # rubocop:enable Layout/LineLength - - before do - allow_any_instance_of(ApplicationController).to receive(:analytics) do |controller| - fake_analytics.user = controller.analytics_user - fake_analytics - end - - allow(IdentityConfig.store).to receive(:va_inherited_proofing_mock_enabled).and_return true - allow_any_instance_of(Idv::InheritedProofingController).to \ - receive(:va_inherited_proofing?).and_return true - allow_any_instance_of(Idv::InheritedProofingController).to \ - receive(:va_inherited_proofing_auth_code).and_return auth_code - end - - context 'Happy path' do - before do - sign_in_and_2fa_user - complete_all_inherited_proofing_steps_to_handoff - end - - it 'records all of the events' do - happy_path_events.each do |event, attributes| - expect(fake_analytics).to have_logged_event(event, attributes) - end - end - end -end diff --git a/spec/features/idv/inherited_proofing/get_started_step_spec.rb b/spec/features/idv/inherited_proofing/get_started_step_spec.rb deleted file mode 100644 index 35222000f68..00000000000 --- a/spec/features/idv/inherited_proofing/get_started_step_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'rails_helper' - -feature 'inherited proofing get started' do - include InheritedProofingHelper - - before do - allow(IdentityConfig.store).to receive(:va_inherited_proofing_mock_enabled).and_return true - allow_any_instance_of(Idv::InheritedProofingController).to \ - receive(:va_inherited_proofing?).and_return true - allow_any_instance_of(Idv::InheritedProofingController).to \ - receive(:va_inherited_proofing_auth_code).and_return auth_code - end - - let(:auth_code) { Idv::InheritedProofing::Va::Mocks::Service::VALID_AUTH_CODE } - - def expect_ip_get_started_step - expect(page).to have_current_path(idv_ip_get_started_step) - end - - def expect_inherited_proofing_get_started_step - expect(page).to have_current_path(idv_ip_get_started_step) - end - - context 'when JS is enabled', :js do - before do - sign_in_and_2fa_user - complete_inherited_proofing_steps_before_get_started_step - end - - context 'when clicking on the Cancel link' do - it 'redirects to the Cancellation UI' do - click_link t('links.cancel') - expect(page).to have_current_path(idv_inherited_proofing_cancel_path(step: :get_started)) - end - end - end - - context 'when JS is disabled' do - before do - sign_in_and_2fa_user - complete_inherited_proofing_steps_before_get_started_step - end - - context 'when clicking on the Cancel link' do - it 'redirects to the Cancellation UI' do - click_link t('links.cancel') - expect(page).to have_current_path(idv_inherited_proofing_cancel_path(step: :get_started)) - end - end - end -end diff --git a/spec/features/idv/inherited_proofing/inherited_proofing_cancel_spec.rb b/spec/features/idv/inherited_proofing/inherited_proofing_cancel_spec.rb deleted file mode 100644 index cf594bd0c6c..00000000000 --- a/spec/features/idv/inherited_proofing/inherited_proofing_cancel_spec.rb +++ /dev/null @@ -1,151 +0,0 @@ -require 'rails_helper' - -feature 'inherited proofing cancel process', :js do - include InheritedProofingWithServiceProviderHelper - - before do - allow(IdentityConfig.store).to receive(:va_inherited_proofing_mock_enabled).and_return true - send_user_from_service_provider_to_login_gov_openid_connect user, inherited_proofing_auth - end - - let!(:user) { user_with_2fa } - let(:inherited_proofing_auth) { Idv::InheritedProofing::Va::Mocks::Service::VALID_AUTH_CODE } - - context 'from the "Get started verifying your identity" view, and clicking the "Cancel" link' do - before do - complete_steps_up_to_inherited_proofing_get_started_step user - end - - it 'should have current path equal to the Getting Started page' do - expect(page).to have_current_path(idv_inherited_proofing_step_path(step: :get_started)) - end - - context 'when clicking the "Start Over" button from the "Cancel" view' do - before do - click_link t('links.cancel') - expect(page).to have_current_path(idv_inherited_proofing_cancel_path(step: :get_started)) - end - - it 'redirects the user back to the start of the Inherited Proofing process' do - click_button t('inherited_proofing.cancel.actions.start_over') - expect(page).to have_current_path(idv_inherited_proofing_step_path(step: :get_started)) - end - end - - context 'when clicking the "No, keep going" button from the "Cancel" view' do - before do - click_link t('links.cancel') - expect(page).to have_current_path(idv_inherited_proofing_cancel_path(step: :get_started)) - end - - it 'redirects the user back to where the user left off in the Inherited Proofing process' do - click_button t('inherited_proofing.cancel.actions.keep_going') - expect(page).to have_current_path(idv_inherited_proofing_step_path(step: :get_started)) - end - end - - context 'when clicking the "Exit Login.gov" button from the "Cancel" view' do - before do - click_link t('links.cancel') - expect(page).to have_current_path(idv_inherited_proofing_cancel_path(step: :get_started)) - end - - it 'redirects the user back to the service provider website' do - click_button t('idv.cancel.actions.exit', app_name: APP_NAME) - expect(page).to have_current_path(/\/auth\/result\?/) - end - end - end - - context 'from the "How verifying your identify works" view, and clicking the "Cancel" link' do - before do - complete_steps_up_to_inherited_proofing_how_verifying_step user - end - - it 'should have current path equal to the How Verifying (agreement step) page' do - expect(page).to have_current_path(idv_inherited_proofing_step_path(step: :agreement)) - end - - context 'when clicking the "Start Over" button from the "Cancel" view' do - before do - click_link t('links.cancel') - expect(page).to have_current_path(idv_inherited_proofing_cancel_path(step: :agreement)) - end - - it 'redirects the user back to the start of the Inherited Proofing process' do - click_button t('inherited_proofing.cancel.actions.start_over') - expect(page).to have_current_path(idv_inherited_proofing_step_path(step: :get_started)) - end - end - - context 'when clicking the "No, keep going" button from the "Cancel" view' do - before do - click_link t('links.cancel') - expect(page).to have_current_path(idv_inherited_proofing_cancel_path(step: :agreement)) - end - - it 'redirects the user back to where the user left off in the Inherited Proofing process' do - click_button t('inherited_proofing.cancel.actions.keep_going') - expect(page).to have_current_path(idv_inherited_proofing_step_path(step: :agreement)) - end - end - - context 'when clicking the "Exit Login.gov" button from the "Cancel" view' do - before do - click_link t('links.cancel') - expect(page).to have_current_path(idv_inherited_proofing_cancel_path(step: :agreement)) - end - - it 'redirects the user back to the service provider website' do - click_button t('idv.cancel.actions.exit', app_name: APP_NAME) - expect(page).to have_current_path(/\/auth\/result\?/) - end - end - end - - context 'from the "Verify your information..." view, and clicking the "Cancel" link' do - before do - complete_steps_up_to_inherited_proofing_verify_your_info_step user - end - - it 'should have current path equal to the Verify your information (verify_info step) page' do - expect(page).to have_current_path(idv_inherited_proofing_step_path(step: :verify_info)) - end - - context 'when clicking the "Start Over" button from the "Cancel" view' do - before do - click_link t('links.cancel') - expect(page).to have_current_path(idv_inherited_proofing_cancel_path(step: :verify_info)) - end - - it 'redirects the user back to the start of the Inherited Proofing process' do - click_button t('inherited_proofing.cancel.actions.start_over') - expect(page).to have_current_path(idv_inherited_proofing_step_path(step: :get_started)) - end - end - - context 'when clicking the "No, keep going" button from the "Cancel" view' do - before do - click_link t('links.cancel') - expect(page).to have_current_path(idv_inherited_proofing_cancel_path(step: :verify_info)) - end - - it 'redirects the user back to where the user left off in the Inherited Proofing process' do - click_button t('inherited_proofing.cancel.actions.keep_going') - expect(page).to have_current_path(idv_inherited_proofing_step_path(step: :verify_info)) - end - end - - context 'when clicking the "Exit Login.gov" button from the "Cancel" view' do - before do - click_link t('links.cancel') - expect(page).to have_current_path(idv_inherited_proofing_cancel_path(step: :verify_info)) - end - - it 'redirects the user back to the service provider website' do - click_button t('idv.cancel.actions.exit', app_name: APP_NAME) - expect(page).to have_current_path(/\/auth\/result\?/) - end - end - end -end diff --git a/spec/features/idv/inherited_proofing/verify_info_step_spec.rb b/spec/features/idv/inherited_proofing/verify_info_step_spec.rb deleted file mode 100644 index fe1a14ea848..00000000000 --- a/spec/features/idv/inherited_proofing/verify_info_step_spec.rb +++ /dev/null @@ -1,64 +0,0 @@ -require 'rails_helper' - -feature 'inherited proofing verify info' do - include InheritedProofingHelper - include_context 'va_user_context' - - before do - allow(IdentityConfig.store).to receive(:va_inherited_proofing_mock_enabled).and_return true - allow_any_instance_of(Idv::InheritedProofingController).to \ - receive(:va_inherited_proofing?).and_return true - allow_any_instance_of(Idv::InheritedProofingController).to \ - receive(:va_inherited_proofing_auth_code).and_return auth_code - @decorated_session = instance_double(ServiceProviderSessionDecorator) - allow(@decorated_session).to receive(:sp_name).and_return(sp_name) - allow(@decorated_session).to receive(:sp_logo_url).and_return('') - allow_any_instance_of(Idv::InheritedProofingController).to \ - receive(:decorated_session).and_return(@decorated_session) - sign_in_and_2fa_user - complete_inherited_proofing_steps_before_verify_step - end - - let(:sp_name) { 'VA.gov' } - let(:auth_code) { Idv::InheritedProofing::Va::Mocks::Service::VALID_AUTH_CODE } - - describe 'page content' do - it 'renders the Continue button' do - expect(page).to have_button(t('inherited_proofing.buttons.continue')) - end - - it 'renders content' do - expect(page).to have_content(t('titles.idv.verify_info')) - expect(page).to have_link( - t( - 'inherited_proofing.troubleshooting.options.get_help', - sp_name: sp_name, - ), - ) - end - end - - describe 'user info' do - it "displays the user's personal information" do - expect(page).to have_text user_attributes[:first_name] - expect(page).to have_text user_attributes[:last_name] - expect(page).to have_text user_attributes[:birth_date] - end - - it "displays the user's address" do - expect(page).to have_text user_attributes[:address][:street] - expect(page).to have_text user_attributes[:address][:city] - expect(page).to have_text user_attributes[:address][:state] - expect(page).to have_text user_attributes[:address][:zip] - end - - it "obfuscates the user's ssn" do - expect(page).to have_text '1**-**-***9' - end - - it "can display the user's ssn when selected" do - check 'Show Social Security number' - expect(page).to have_text '123-45-6789' - end - end -end diff --git a/spec/features/idv/inherited_proofing/verify_wait_step_spec.rb b/spec/features/idv/inherited_proofing/verify_wait_step_spec.rb deleted file mode 100644 index 3c17b33d889..00000000000 --- a/spec/features/idv/inherited_proofing/verify_wait_step_spec.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'rails_helper' - -feature 'inherited proofing verify wait', :js do - include InheritedProofingWithServiceProviderHelper - - before do - allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics) - allow(IdentityConfig.store).to receive(:va_inherited_proofing_mock_enabled).and_return(true) - send_user_from_service_provider_to_login_gov_openid_connect(user, inherited_proofing_auth) - end - - let!(:user) { user_with_2fa } - let(:inherited_proofing_auth) { Idv::InheritedProofing::Va::Mocks::Service::VALID_AUTH_CODE } - let(:fake_analytics) { FakeAnalytics.new } - - context 'when on the "How verifying your identify works" page, ' \ - 'and the user clicks the "Continue" button' do - before do - complete_steps_up_to_inherited_proofing_we_are_retrieving_step user - end - - context 'when there are no service-related errors' do - it 'displays the "Verify your information" page' do - expect(page).to have_current_path( - idv_inherited_proofing_step_path(step: :verify_info), - ) - end - end - - context 'when there are service-related errors on the first attempt' do - let(:inherited_proofing_auth) { 'invalid-auth-code' } - - it 'displays the warning page and allows retries' do - expect(page).to have_current_path( - idv_inherited_proofing_errors_no_information_path(flow: :inherited_proofing), - ) - expect(page).to have_selector(:link_or_button, t('inherited_proofing.buttons.try_again')) - end - end - - context 'when there are service-related errors on the second attempt' do - let(:inherited_proofing_auth) { 'invalid-auth-code' } - - it 'redirects to the error page, prohibits retries and logs the event' do - click_button t('inherited_proofing.buttons.try_again') - expect(page).to have_current_path( - idv_inherited_proofing_errors_failure_url(flow: :inherited_proofing), - ) - expect(fake_analytics).to have_logged_event( - 'Throttler Rate Limit Triggered', - throttle_type: :inherited_proofing, - step_name: Idv::Actions::InheritedProofing::RedoRetrieveUserInfoAction.name, - ) - end - end - end - - context 'when the async state is missing during polling' do - before do - allow_any_instance_of(ProofingSessionAsyncResult).to receive(:missing?).and_return(true) - complete_steps_up_to_inherited_proofing_we_are_retrieving_step user - end - - it 'redirects back to the agreement step and logs the event' do - expect(page).to have_current_path( - idv_inherited_proofing_step_path(step: :agreement), - ) - expect(fake_analytics).to have_logged_event( - 'Proofing Resolution Result Missing', - ) - end - end -end 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 fd0e1858d05..6f408fb9760 100644 --- a/spec/features/idv/steps/in_person/verify_info_spec.rb +++ b/spec/features/idv/steps/in_person/verify_info_spec.rb @@ -28,7 +28,11 @@ expect(page).to have_content(t('headings.verify')) expect(page).to have_text(InPersonHelper::GOOD_FIRST_NAME) expect(page).to have_text(InPersonHelper::GOOD_LAST_NAME) - expect(page).to have_text(InPersonHelper::GOOD_DOB) + i18n_dob = I18n.l( + Date.parse(InPersonHelper::GOOD_DOB), + format: I18n.t('time.formats.event_date'), + ) + expect(page).to have_text(i18n_dob) expect(page).to have_text(InPersonHelper::GOOD_STATE_ID_NUMBER) expect(page).to have_text(InPersonHelper::GOOD_ADDRESS1) expect(page).to have_text(InPersonHelper::GOOD_CITY) diff --git a/spec/features/idv/threatmetrix_pending_spec.rb b/spec/features/idv/threatmetrix_pending_spec.rb index 0201fd47ffd..ad042d4fbd7 100644 --- a/spec/features/idv/threatmetrix_pending_spec.rb +++ b/spec/features/idv/threatmetrix_pending_spec.rb @@ -2,10 +2,27 @@ RSpec.feature 'Users pending threatmetrix review', :js do include IdvStepHelper + include OidcAuthHelper + include IrsAttemptsApiTrackingHelper + include DocAuthHelper before do allow(IdentityConfig.store).to receive(:proofing_device_profiling).and_return(:enabled) allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_org_id).and_return('test_org') + allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled).and_return(true) + allow(IdentityConfig.store).to receive(:irs_attempt_api_track_tmx_fraud_check_event). + and_return(true) + mock_irs_attempts_api_encryption_key + end + + let(:service_provider) do + create( + :service_provider, + active: true, + redirect_uris: ['http://localhost:7654/auth/result'], + ial: 2, + irs_attempts_api_enabled: true, + ) end scenario 'users pending threatmetrix see sad face screen and cannot perform idv' do @@ -49,4 +66,53 @@ expect(current_path).to eq('/auth/result') end + + scenario 'users threatmetrix Pass, it logs idv_tmx_fraud_check event', :js do + freeze_time do + complete_all_idv_steps_with(threatmetrix: 'Pass') + expect_irs_event(expected_success: true, expected_failure_reason: nil) + end + end + + scenario 'users pending threatmetrix Reject, it logs idv_tmx_fraud_check event', :js do + freeze_time do + expect_pending_failure_reason(threatmetrix: 'Reject') + end + end + + scenario 'users pending threatmetrix Review, it logs idv_tmx_fraud_check event', :js do + freeze_time do + expect_pending_failure_reason(threatmetrix: 'Review') + end + end + + scenario 'users pending threatmetrix No Result, it logs idv_tmx_fraud_check event', :js do + freeze_time do + expect_pending_failure_reason(threatmetrix: 'No Result') + end + end + + def expect_pending_failure_reason(threatmetrix:) + complete_all_idv_steps_with(threatmetrix: threatmetrix) + expect(page).to have_content(t('idv.failure.setup.heading')) + expect(page).to have_current_path(idv_setup_errors_path) + expect_irs_event( + expected_success: false, + expected_failure_reason: DocAuthHelper::SAMPLE_TMX_SUMMARY_REASON_CODE, + ) + end + + def expect_irs_event(expected_success:, expected_failure_reason:) + event_name = 'idv-tmx-fraud-check' + events = irs_attempts_api_tracked_events(timestamp: Time.zone.now) + received_event_types = events.map(&:event_type) + + idv_tmx_fraud_check_event = events.find { |x| x.event_type == event_name } + failure_reason = idv_tmx_fraud_check_event.event_metadata[:failure_reason] + success = idv_tmx_fraud_check_event.event_metadata[:success] + + expect(received_event_types).to include event_name + expect(failure_reason).to eq expected_failure_reason.as_json + expect(success).to eq expected_success + end end diff --git a/spec/features/services/idv/inherited_proofing/va/mocks/service_spec.rb b/spec/features/services/idv/inherited_proofing/va/mocks/service_spec.rb deleted file mode 100644 index 88d8c7d429f..00000000000 --- a/spec/features/services/idv/inherited_proofing/va/mocks/service_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'rails_helper' - -RSpec.describe 'Inherited Proofing VA API Proofer Service' do - subject(:form) { Idv::InheritedProofing::Va::Form.new(payload_hash: proofer_results) } - - let(:proofer_results) do - Idv::InheritedProofing::Va::Mocks::Service.new({ auth_code: auth_code }).execute - end - let(:auth_code) { Idv::InheritedProofing::Va::Mocks::Service::VALID_AUTH_CODE } - - context 'when used with the VA Inherited Proofing Response Form' do - it 'works as expected' do - expect(form.submit.success?).to eq true - end - end -end diff --git a/spec/fixtures/proofing/lexis_nexis/ddp/error_response.json b/spec/fixtures/proofing/lexis_nexis/ddp/error_response.json index d36ca4063bf..0de8a11bf99 100644 --- a/spec/fixtures/proofing/lexis_nexis/ddp/error_response.json +++ b/spec/fixtures/proofing/lexis_nexis/ddp/error_response.json @@ -2,5 +2,6 @@ "error_detail": "service_type", "request_id":"1234-abcd", "request_result":"fail_invalid_parameter", - "review_status":"REVIEW_STATUS" + "review_status":"REVIEW_STATUS", + "tmx_summary_reason_code": ["Identity_Negative_History"] } diff --git a/spec/fixtures/proofing/lexis_nexis/ddp/successful_redacted_response.json b/spec/fixtures/proofing/lexis_nexis/ddp/successful_redacted_response.json index 85b43947a59..add352a3a54 100644 --- a/spec/fixtures/proofing/lexis_nexis/ddp/successful_redacted_response.json +++ b/spec/fixtures/proofing/lexis_nexis/ddp/successful_redacted_response.json @@ -6,5 +6,6 @@ "summary_risk_score": "-6", "tmx_risk_rating": "neutral", "fraudpoint.score": "500", - "first_name": "[redacted]" + "first_name": "[redacted]", + "tmx_summary_reason_code": ["Identity_Negative_History"] } diff --git a/spec/fixtures/proofing/lexis_nexis/ddp/successful_response.json b/spec/fixtures/proofing/lexis_nexis/ddp/successful_response.json index 4f63de45a75..a408987796b 100644 --- a/spec/fixtures/proofing/lexis_nexis/ddp/successful_response.json +++ b/spec/fixtures/proofing/lexis_nexis/ddp/successful_response.json @@ -6,5 +6,6 @@ "summary_risk_score": "-6", "tmx_risk_rating": "neutral", "fraudpoint.score": "500", - "first_name": "WARNING! YOU SHOULD NEVER SEE THIS PII FIELD IN THE LOGS" + "first_name": "WARNING! YOU SHOULD NEVER SEE THIS PII FIELD IN THE LOGS", + "tmx_summary_reason_code": ["Identity_Negative_History"] } diff --git a/spec/forms/idv/inherited_proofing/base_form_spec.rb b/spec/forms/idv/inherited_proofing/base_form_spec.rb deleted file mode 100644 index 871142de3eb..00000000000 --- a/spec/forms/idv/inherited_proofing/base_form_spec.rb +++ /dev/null @@ -1,182 +0,0 @@ -require 'rails_helper' - -RSpec.describe Idv::InheritedProofing::BaseForm do - subject { form_object } - - let(:form_class) do - Class.new(Idv::InheritedProofing::BaseForm) do - class << self - def required_fields; [] end - - def optional_fields; [] end - end - - def user_pii; {} end - end - end - - let(:form_object) do - form_class.new(payload_hash: payload_hash) - end - - let(:payload_hash) do - { - first_name: 'Henry', - last_name: 'Ford', - phone: '12222222222', - birth_date: '2000-01-01', - ssn: '111223333', - address: { - street: '1234 Model Street', - street2: 'Suite A', - city: 'Detroit', - state: 'MI', - country: 'United States', - zip: '12345', - }, - } - end - - describe '#initialize' do - subject { form_class } - - context 'when .user_pii is not overridden' do - subject do - Class.new(Idv::InheritedProofing::BaseForm) do - class << self - def required_fields; [] end - - def optional_fields; [] end - end - end - end - - it 'raises an error' do - expected_error = 'Override this method and return a user PII Hash' - expect { subject.new(payload_hash: payload_hash).user_pii }.to raise_error(expected_error) - end - end - end - - describe 'class methods' do - describe '.model_name' do - it 'returns the right model name' do - expect(described_class.model_name).to eq 'IdvInheritedProofingBaseForm' - end - end - end - - describe '#initialize' do - context 'when passing an invalid payload hash' do - context 'when not a Hash' do - let(:payload_hash) { :x } - - it 'raises an error' do - expect { subject }.to raise_error 'payload_hash is not a Hash' - end - end - - context 'when nil?' do - let(:payload_hash) { nil } - - it_behaves_like 'the hash is blank?' - end - - context 'when empty?' do - let(:payload_hash) { {} } - - it_behaves_like 'the hash is blank?' - end - end - - context 'when passing a valid payload hash' do - it 'raises no errors' do - expect { subject }.to_not raise_error - end - end - end - - describe '#validate' do - subject do - Class.new(Idv::InheritedProofing::BaseForm) do - class << self - def required_fields; %i[required] end - - def optional_fields; %i[optional] end - end - - def user_pii; {} end - end.new(payload_hash: payload_hash) - end - - let(:payload_hash) do - { - required: 'Required', - optional: 'Optional', - } - end - - context 'with valid payload data' do - it 'returns true' do - expect(subject.validate).to eq true - end - end - - context 'with invalid payload data' do - context 'when the payload has unrecognized fields' do - let(:payload_hash) do - { - xrequired: 'xRequired', - xoptional: 'xOptional', - } - end - - let(:expected_error_messages) do - [ - # Required field presence - 'Required field is missing', - 'Optional field is missing', - ] - end - - it 'returns true' do - expect(subject.validate).to eq true - end - end - - context 'when the payload has missing required field data' do - let(:payload_hash) do - { - required: nil, - optional: '', - } - end - - it 'returns true' do - expect(subject.validate).to eq true - end - - it 'returns no errors because no data validations take place by default' do - subject.validate - expect(subject.errors.full_messages).to eq [] - end - end - end - end - - describe '#submit' do - it 'returns a FormResponse object' do - expect(subject.submit).to be_kind_of FormResponse - end - - describe 'before returning' do - after do - subject.submit - end - - it 'calls #valid?' do - expect(subject).to receive(:valid?).once - end - end - end -end diff --git a/spec/forms/idv/inherited_proofing/va/form_spec.rb b/spec/forms/idv/inherited_proofing/va/form_spec.rb deleted file mode 100644 index ad734ab37c1..00000000000 --- a/spec/forms/idv/inherited_proofing/va/form_spec.rb +++ /dev/null @@ -1,317 +0,0 @@ -require 'rails_helper' - -RSpec.describe Idv::InheritedProofing::Va::Form do - subject(:form) { described_class.new payload_hash: payload_hash } - - let(:required_fields) { %i[first_name last_name birth_date ssn address_street address_zip] } - let(:optional_fields) do - %i[phone address_street2 address_city address_state address_country service_error] - end - - let(:payload_hash) do - { - first_name: 'Henry', - last_name: 'Ford', - phone: '12222222222', - birth_date: '2000-01-01', - ssn: '111223333', - address: { - street: '1234 Model Street', - street2: 'Suite A', - city: 'Detroit', - state: 'MI', - country: 'United States', - zip: '12345', - }, - } - end - - describe 'constants' do - describe 'FIELDS' do - it 'returns all the fields' do - expect(described_class::FIELDS).to match_array required_fields + optional_fields - end - end - - describe 'REQUIRED_FIELDS' do - it 'returns the required fields' do - expect(described_class::REQUIRED_FIELDS).to match_array required_fields - end - end - - describe 'OPTIONAL_FIELDS' do - it 'returns the optional fields' do - expect(described_class::OPTIONAL_FIELDS).to match_array optional_fields - end - end - end - - describe 'class methods' do - describe '.model_name' do - it 'returns the right model name' do - expect(described_class.model_name).to eq 'IdvInheritedProofingVaForm' - end - end - end - - describe '#initialize' do - context 'when passing an invalid payload hash' do - context 'when not a Hash' do - let(:payload_hash) { :x } - - it 'raises an error' do - expect { form }.to raise_error 'payload_hash is not a Hash' - end - end - - context 'when nil?' do - let(:payload_hash) { nil } - - it_behaves_like 'the hash is blank?' - end - - context 'when empty?' do - let(:payload_hash) { {} } - - it_behaves_like 'the hash is blank?' - end - end - - context 'when passing a valid payload hash' do - it 'raises no errors' do - expect { form }.to_not raise_error - end - end - end - - describe '#validate' do - context 'with valid payload data' do - it 'returns true' do - expect(form.validate).to be true - end - end - - context 'with invalid payload data' do - context 'when the payload has missing fields' do - let(:payload_hash) do - { - xfirst_name: 'Henry', - xlast_name: 'Ford', - xphone: '12222222222', - xbirth_date: '2000-01-01', - xssn: '111223333', - xaddress: { - xstreet: '1234 Model Street', - xstreet2: 'Suite A', - xcity: 'Detroit', - xstate: 'MI', - xcountry: 'United States', - xzip: '12345', - }, - } - end - - let(:expected_error_messages) do - [ - 'First name Please fill in this field.', - 'Last name Please fill in this field.', - 'Birth date Please fill in this field.', - 'Ssn Please fill in this field.', - 'Address street Please fill in this field.', - 'Address zip Please fill in this field.', - ] - end - - it 'returns false' do - expect(form.validate).to be false - end - - it 'adds the correct error messages for missing fields' do - subject.validate - expect(form.errors.full_messages).to match_array expected_error_messages - end - end - - context 'when the payload has missing required field data' do - let(:payload_hash) do - { - first_name: nil, - last_name: '', - phone: nil, - birth_date: '', - ssn: nil, - address: { - street: '', - street2: nil, - city: '', - state: nil, - country: '', - zip: nil, - }, - } - end - - let(:expected_error_messages) do - [ - # Required field data presence - 'First name Please fill in this field.', - 'Last name Please fill in this field.', - 'Birth date Please fill in this field.', - 'Ssn Please fill in this field.', - 'Address street Please fill in this field.', - 'Address zip Please fill in this field.', - ] - end - - it 'returns false' do - expect(form.validate).to be false - end - - it 'adds the correct error messages for required fields that are missing data' do - subject.validate - expect(form.errors.full_messages).to match_array expected_error_messages - end - end - - context 'when the payload has missing optional field data' do - let(:payload_hash) do - { - first_name: 'x', - last_name: 'x', - phone: nil, - birth_date: '01/01/2022', - ssn: '123456789', - address: { - street: 'x', - street2: nil, - city: '', - state: nil, - country: '', - zip: '12345', - }, - } - end - - it 'returns true' do - expect(form.validate).to be true - end - end - - context 'when there is a service-related error' do - before do - subject.validate - end - - let(:payload_hash) { { service_error: 'service error' } } - - it 'returns false' do - expect(form.valid?).to be false - end - - it 'adds a user-friendly model error' do - expect(form.errors.full_messages).to \ - match_array ['Service provider communication was unsuccessful'] - end - end - end - end - - describe '#submit' do - context 'with an invalid payload' do - context 'when the payload has invalid field data' do - let(:payload_hash) do - { - first_name: nil, - last_name: '', - phone: nil, - birth_date: '', - ssn: nil, - address: { - street: '', - street2: nil, - city: '', - state: nil, - country: '', - zip: nil, - }, - } - end - - let(:expected_errors) do - { - # Required field data presence - first_name: ['Please fill in this field.'], - last_name: ['Please fill in this field.'], - birth_date: ['Please fill in this field.'], - ssn: ['Please fill in this field.'], - address_street: ['Please fill in this field.'], - address_zip: ['Please fill in this field.'], - } - end - - it 'returns a FormResponse indicating the correct errors and status' do - form_response = subject.submit - expect(form_response.success?).to be false - expect(form_response.errors).to match_array expected_errors - expect(form_response.extra).to eq({}) - end - end - end - - context 'with a valid payload' do - it 'returns a FormResponse indicating the no errors and successful status' do - form_response = subject.submit - expect(form_response.success?).to be true - expect(form_response.errors).to eq({}) - expect(form_response.extra).to eq({}) - end - end - - context 'when there is a service-related error' do - let(:payload_hash) { { service_error: 'service error' } } - - it 'adds the unfiltered error to the FormResponse :extra Hash' do - form_response = subject.submit - expect(form_response.success?).to be false - expect(form_response.errors).to \ - eq({ service_provider: ['communication was unsuccessful'] }) - expect(form_response.extra).to eq({ service_error: 'service error' }) - end - end - end - - describe '#user_pii' do - let(:expected_user_pii) do - { - first_name: subject.first_name, - last_name: subject.last_name, - dob: subject.birth_date, - ssn: subject.ssn, - phone: subject.phone, - address1: subject.address_street, - city: subject.address_city, - state: subject.address_state, - zipcode: subject.address_zip, - } - end - it 'returns the correct user pii' do - expect(form.user_pii).to eq expected_user_pii - end - end - - describe '#service_error?' do - context 'when there is a service-related error' do - let(:payload_hash) { { service_error: 'service error' } } - - it 'returns true' do - expect(form.service_error?).to be true - end - end - - context 'when there is not a service-related error' do - it 'returns false' do - expect(form.service_error?).to be false - end - end - end -end diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb index fe11f2cec91..ef3f2601abe 100644 --- a/spec/i18n_spec.rb +++ b/spec/i18n_spec.rb @@ -16,6 +16,7 @@ class BaseTask { key: 'account.navigation.menu', locales: %i[fr] }, # "Menu" is "Menu" in French { key: 'doc_auth.headings.photo', locales: %i[fr] }, # "Photo" is "Photo" in French { key: /^i18n\.locale\./ }, # Show locale options translated as that language + { key: /^i18n\.transliterate\./ }, # Approximate non-ASCII characters in ASCII { key: /^countries/ }, # Some countries have the same name across languages { key: 'links.contact', locales: %i[fr] }, # "Contact" is "Contact" in French { key: 'simple_form.no', locales: %i[es] }, # "No" is "No" in Spanish @@ -89,7 +90,9 @@ def allowed_untranslated_key?(locale, key) missing_interpolation_argument_keys = [] i18n.data[i18n.base_locale].select_keys do |key, _node| - next if i18n.t(key).is_a?(Array) || i18n.t(key).nil? + if key.start_with?('i18n.transliterate.rule.') || i18n.t(key).is_a?(Array) || i18n.t(key).nil? + next + end interpolation_arguments = i18n.locales.map do |locale| extract_interpolation_arguments i18n.t(key, locale) @@ -109,20 +112,23 @@ def allowed_untranslated_key?(locale, key) i18n_file = full_path.sub("#{root_dir}/", '') describe i18n_file do - it 'has only lower_snake_case keys' do - keys = flatten_hash(YAML.load_file(full_path)).keys + # Transliteration includes special characters by definition, so it could fail the below checks + if !full_path.match?(%(/config/locales/transliterate/)) + it 'has only lower_snake_case keys' do + keys = flatten_hash(YAML.load_file(full_path)).keys - bad_keys = keys.reject { |key| key =~ /^[a-z0-9_.]+$/ } + bad_keys = keys.reject { |key| key =~ /^[a-z0-9_.]+$/ } - expect(bad_keys).to be_empty - end + expect(bad_keys).to be_empty + end - it 'has only has XML-safe identifiers (keys start with a letter)' do - keys = flatten_hash(YAML.load_file(full_path)).keys + it 'has only has XML-safe identifiers (keys start with a letter)' do + keys = flatten_hash(YAML.load_file(full_path)).keys - bad_keys = keys.select { |key| key.split('.').any? { |part| part =~ /^[0-9]/ } } + bad_keys = keys.select { |key| key.split('.').any? { |part| part =~ /^[0-9]/ } } - expect(bad_keys).to be_empty + expect(bad_keys).to be_empty + end end it 'has correctly-formatted interpolation values' do diff --git a/spec/jobs/get_usps_proofing_results_job_spec.rb b/spec/jobs/get_usps_proofing_results_job_spec.rb index a39addc7f90..bd17b08c838 100644 --- a/spec/jobs/get_usps_proofing_results_job_spec.rb +++ b/spec/jobs/get_usps_proofing_results_job_spec.rb @@ -166,9 +166,10 @@ allow(job).to receive(:analytics).and_return(job_analytics) allow(IdentityConfig.store).to receive(:get_usps_proofing_results_job_reprocess_delay_minutes). and_return(reprocess_delay_minutes) - allow(IdentityConfig.store). - to receive(:get_usps_proofing_results_job_request_delay_milliseconds). - and_return(request_delay_ms) + stub_const( + 'GetUspsProofingResultsJob::REQUEST_DELAY_IN_SECONDS', + request_delay_ms / GetUspsProofingResultsJob::MILLISECONDS_PER_SECOND, + ) stub_request_token if respond_to?(:pending_enrollment) pending_enrollment.update(enrollment_established_at: 3.days.ago) diff --git a/spec/jobs/inherited_proofing_job_spec.rb b/spec/jobs/inherited_proofing_job_spec.rb deleted file mode 100644 index 839d292cd33..00000000000 --- a/spec/jobs/inherited_proofing_job_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'rails_helper' - -RSpec.describe InheritedProofingJob, type: :job do - include Idv::InheritedProofing::ServiceProviderServices - include Idv::InheritedProofing::ServiceProviderForms - include_context 'va_api_context' - - let(:document_capture_session) { DocumentCaptureSession.new(result_id: SecureRandom.hex) } - let(:service_provider_data) { { auth_code: 'mocked-auth-code-for-testing' } } - let(:service_provider) { Idv::InheritedProofing::ServiceProviders::VA } - - before do - allow(IdentityConfig.store).to receive(:va_inherited_proofing_mock_enabled).and_return true - allow_any_instance_of(Idv::InheritedProofing::ServiceProviderServices).to \ - receive(:inherited_proofing?).and_return true - end - - describe '#perform' do - it 'calls api for user data and stores in document capture session' do - document_capture_session.create_doc_auth_session - - InheritedProofingJob.perform_now( - service_provider, - service_provider_data, - document_capture_session.uuid, - ) - - result = document_capture_session.load_proofing_result[:result] - - expect(result).to be_present - expect(result).to include(last_name: 'Fakerson') - end - end -end diff --git a/spec/presenters/completions_presenter_spec.rb b/spec/presenters/completions_presenter_spec.rb index 48e75c79b70..2265157fbbe 100644 --- a/spec/presenters/completions_presenter_spec.rb +++ b/spec/presenters/completions_presenter_spec.rb @@ -224,6 +224,27 @@ end end + context 'user has reverified since last consent for sp' do + let(:identities) do + [ + build( + :service_provider_identity, + service_provider: current_sp.issuer, + last_consented_at: 2.months.ago, + ), + ] + end + let(:completion_context) { :reverified_after_consent } + it 'renders the reverified IAL2 consent intro message' do + expect(presenter.intro).to eq( + I18n.t( + 'help_text.requested_attributes.ial2_reverified_consent_info', + sp: current_sp.friendly_name, + ), + ) + end + end + context 'when consent has not expired' do it 'renders the standard intro message' do expect(presenter.intro).to eq( diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index c0d8fc9e9c1..2aec91ab96f 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -155,4 +155,18 @@ class Analytics # Consider any browser console logging as a failure. raise BrowserConsoleLogError.new(javascript_errors) if javascript_errors.present? end + + config.around(:each, allow_net_connect_on_start: true) do |example| + # Avoid "Too many open files - socket(2)" error on some local machines + WebMock.allow_net_connect!(net_http_connect_on_start: true) + example.run + WebMock.disable_net_connect!( + allow: [ + /localhost/, + /127\.0\.0\.1/, + /codeclimate.com/, # For uploading coverage reports + /chromedriver\.storage\.googleapis\.com/, # For fetching a chromedriver binary + ], + ) + end end diff --git a/spec/services/browser_support_spec.rb b/spec/services/browser_support_spec.rb new file mode 100644 index 00000000000..d545eb22b14 --- /dev/null +++ b/spec/services/browser_support_spec.rb @@ -0,0 +1,127 @@ +require 'rails_helper' + +RSpec.describe BrowserSupport do + before { BrowserSupport.clear_cache! } + + describe '.supported?' do + let(:user_agent) do + # Chrome v110 + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like ' \ + 'Gecko) Chrome/110.0.0.0 Safari/537.36' + end + + subject(:supported) { BrowserSupport.supported?(user_agent) } + + context 'with browser support config file missing' do + before do + expect(File).to receive(:read).once.with(Rails.root.join('browsers.json')). + and_raise(Errno::ENOENT.new) + end + + it { expect(supported).to eq(true) } + + it 'memoizes result of parsing browser config' do + BrowserSupport.supported?('a') + BrowserSupport.supported?('b') + end + end + + context 'with invalid support config' do + before do + expect(File).to receive(:read).once.with(Rails.root.join('browsers.json')). + and_return('invalid') + end + + it { expect(supported).to eq(true) } + + it 'memoizes result of parsing browser config' do + BrowserSupport.supported?('a') + BrowserSupport.supported?('b') + end + end + + context 'with valid browser support config' do + before do + allow(BrowserSupport).to receive(:browser_support_config). + and_return(['chrome 109', 'and_chr 108', 'ios_saf 14.5-14.8', 'op_mini all']) + end + + context 'with nil user agent' do + let(:user_agent) { nil } + + it { expect(supported).to eq(false) } + end + + context 'with supported user agent' do + let(:user_agent) do + # Chrome v110 + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like ' \ + 'Gecko) Chrome/110.0.0.0 Safari/537.36' + end + + it { expect(supported).to eq(true) } + end + + context 'with unsupported user agent' do + let(:user_agent) do + # Chrome v108 + 'Mozilla/5.0 (X11; CrOS x86_64 8172.45.0) AppleWebKit/537.36 (KHTML, like Gecko) ' \ + 'Chrome/108.0.2704.64 Safari/537.36' + end + + it { expect(supported).to eq(false) } + end + + context 'with user agent for non-numeric version test' do + let(:user_agent) do + # Opera v12 + 'Opera/9.80 (Android; Opera Mini/36.2.2254/119.132; U; id) Presto/2.12.423 Version/12.16)' + end + + it { expect(supported).to eq(true) } + end + + context 'with user agent for version range test' do + context 'below version range' do + let(:user_agent) do + # Safari v11.2 + 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, ' \ + 'like Gecko) Version/11.2 Mobile/15E148 Safari/604.1' + end + + it { expect(supported).to eq(false) } + end + + context 'within version range' do + let(:user_agent) do + # Safari v14.6 + 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, ' \ + 'like Gecko) Mobile/15E148 Version/14.6 Safari/605.1.15 AlohaBrowser/3.1.5' + end + + it { expect(supported).to eq(true) } + end + + context 'above version range' do + let(:user_agent) do + # Safari v16.3 + 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML' \ + ', like Gecko) Version/16.3 Mobile/15E148 Safari/604.1' + end + + it { expect(supported).to eq(true) } + end + end + end + + context 'with user agent for platform-specific version support' do + let(:user_agent) do + # Android Chrome v108 + 'Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) ' \ + 'Chrome/108.0.5481.153 Mobile Safari/537.36' + end + + it { expect(supported).to eq(true) } + end + end +end diff --git a/spec/services/idv/inherited_proofing/service_provider_forms_spec.rb b/spec/services/idv/inherited_proofing/service_provider_forms_spec.rb deleted file mode 100644 index bbad02b360e..00000000000 --- a/spec/services/idv/inherited_proofing/service_provider_forms_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'rails_helper' - -RSpec.describe Idv::InheritedProofing::ServiceProviderForms do - subject do - Class.new do - include Idv::InheritedProofing::ServiceProviderForms - end.new - end - - let(:service_provider) { :va } - let(:payload_hash) { Idv::InheritedProofing::Va::Mocks::Service::PAYLOAD_HASH } - - describe '#inherited_proofing_form_for' do - context 'when there is a va inherited proofing request' do - it 'returns the correct form' do - expect( - subject.inherited_proofing_form_for( - service_provider, - payload_hash: payload_hash, - ), - ).to \ - be_kind_of Idv::InheritedProofing::Va::Form - end - end - - context 'when the inherited proofing request cannot be identified' do - let(:service_provider) { :unknown_service_provider } - - it 'raises an error' do - expect do - subject.inherited_proofing_form_for(service_provider, payload_hash: payload_hash) - end.to \ - raise_error 'Inherited proofing form could not be identified' - end - end - end -end diff --git a/spec/services/idv/inherited_proofing/service_provider_services_spec.rb b/spec/services/idv/inherited_proofing/service_provider_services_spec.rb deleted file mode 100644 index 76380bf2374..00000000000 --- a/spec/services/idv/inherited_proofing/service_provider_services_spec.rb +++ /dev/null @@ -1,101 +0,0 @@ -require 'rails_helper' - -RSpec.describe Idv::InheritedProofing::ServiceProviderServices do - subject do - Class.new do - include Idv::InheritedProofing::ServiceProviderServices - end.new - end - - let(:service_provider) { :va } - let(:service_provider_data) { { auth_code: auth_code } } - let(:auth_code) { Idv::InheritedProofing::Va::Mocks::Service::VALID_AUTH_CODE } - - describe '#inherited_proofing_service_class_for' do - context 'when va inherited proofing is disabled' do - before do - allow(IdentityConfig.store).to receive(:inherited_proofing_enabled).and_return(false) - end - - it 'raises an error' do - expect do - subject.inherited_proofing_service_class_for(service_provider) - end.to raise_error 'Inherited Proofing is not enabled' - end - end - - context 'when there is a va inherited proofing request' do - context 'when va mock proofing is turned on' do - before do - allow(IdentityConfig.store).to \ - receive(:va_inherited_proofing_mock_enabled).and_return(true) - end - - it 'returns the correct service provider service class' do - expect(subject.inherited_proofing_service_class_for(service_provider)).to \ - eq Idv::InheritedProofing::Va::Mocks::Service - end - end - - context 'when va mock proofing is turned off' do - before do - allow(IdentityConfig.store).to \ - receive(:va_inherited_proofing_mock_enabled).and_return(false) - end - - it 'returns the correct service provider service class' do - expect(subject.inherited_proofing_service_class_for(service_provider)).to \ - eq Idv::InheritedProofing::Va::Service - end - end - end - - context 'when the inherited proofing class cannot be identified' do - let(:service_provider) { :unknown_service_provider } - - it 'raises an error' do - expect do - subject.inherited_proofing_service_class_for(service_provider) - end.to raise_error 'Inherited proofing service class could not be identified' - end - end - end - - describe '#inherited_proofing_service_for' do - context 'when there is a va inherited proofing request' do - context 'when va mock proofing is turned on' do - before do - allow(IdentityConfig.store).to \ - receive(:va_inherited_proofing_mock_enabled).and_return(true) - end - - it 'returns the correct service provider service class' do - expect( - subject.inherited_proofing_service_for( - service_provider, - service_provider_data: service_provider_data, - ), - ).to \ - be_kind_of Idv::InheritedProofing::Va::Mocks::Service - end - end - - context 'when va mock proofing is turned off' do - before do - allow(IdentityConfig.store).to \ - receive(:va_inherited_proofing_mock_enabled).and_return(false) - end - - it 'returns the correct service provider service class' do - expect( - subject.inherited_proofing_service_for( - service_provider, - service_provider_data: service_provider_data, - ), - ).to \ - be_kind_of Idv::InheritedProofing::Va::Service - end - end - end - end -end diff --git a/spec/services/idv/inherited_proofing/va/mocks/service_spec.rb b/spec/services/idv/inherited_proofing/va/mocks/service_spec.rb deleted file mode 100644 index 865383e874b..00000000000 --- a/spec/services/idv/inherited_proofing/va/mocks/service_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'rails_helper' - -RSpec.describe Idv::InheritedProofing::Va::Mocks::Service do - subject { described_class.new({ auth_code: auth_code }) } - let(:auth_code) { described_class::VALID_AUTH_CODE } - - describe '#initialize' do - it 'sets #auth_code' do - expect(subject.auth_code).to eq auth_code - end - end - - describe '#execute' do - context 'when auth_code is valid' do - it 'returns a Hash' do - expect(subject.execute).to eq(described_class::PAYLOAD_HASH) - end - end - - context 'with auth code is invalid' do - let(:auth_code) { "invalid-#{described_class::VALID_AUTH_CODE}" } - - it 'returns an error' do - expect(subject.execute).to eq(described_class::ERROR_HASH) - end - end - end -end diff --git a/spec/services/idv/inherited_proofing/va/service_spec.rb b/spec/services/idv/inherited_proofing/va/service_spec.rb deleted file mode 100644 index 7a5dece157b..00000000000 --- a/spec/services/idv/inherited_proofing/va/service_spec.rb +++ /dev/null @@ -1,124 +0,0 @@ -require 'rails_helper' - -RSpec.shared_examples 'an invalid auth code error is raised' do - it 'raises an error' do - expect { subject.execute }.to raise_error 'The provided auth_code is blank?' - end -end - -RSpec.describe Idv::InheritedProofing::Va::Service do - include_context 'va_api_context' - include_context 'va_user_context' - - subject(:service) { described_class.new(auth_code: auth_code) } - - before do - allow(service).to receive(:private_key).and_return(private_key) - end - - it { respond_to :execute } - - it do - expect(service.send(:private_key)).to eq private_key - end - - describe '#execute' do - context 'when the auth code is valid' do - let(:auth_code) { 'mocked-auth-code-for-testing' } - - it 'makes an authenticated request' do - freeze_time do - stub = stub_request(:get, request_uri). - with(headers: request_headers). - to_return(status: 200, body: '{}', headers: {}) - - service.execute - - expect(stub).to have_been_requested.once - end - end - - it 'decrypts the response' do - freeze_time do - stub_request(:get, request_uri). - with(headers: request_headers). - to_return(status: 200, body: encrypted_user_attributes, headers: {}) - - expect(service.execute).to eq user_attributes - end - end - end - - context 'when the auth code is invalid' do - context 'when an empty? string' do - let(:auth_code) { '' } - - it_behaves_like 'an invalid auth code error is raised' - end - - context 'when an nil?' do - let(:auth_code) { nil } - - it_behaves_like 'an invalid auth code error is raised' - end - end - - context 'when a request error is raised' do - before do - allow(service).to receive(:request).and_raise('Boom!') - end - - it 'rescues and returns the error' do - expect(service.execute).to eq({ service_error: 'Boom!' }) - end - end - - context 'when a decryption error is raised' do - it 'rescues and returns the error' do - freeze_time do - stub_request(:get, request_uri). - with(headers: request_headers). - to_return(status: 200, body: 'xyz', headers: {}) - - expect(service.execute[:service_error]).to match(/unexpected token at 'xyz'/) - end - end - end - - context 'when a non-200 error is raised' do - it 'rescues and returns the error' do - freeze_time do - stub_request(:get, request_uri). - with(headers: request_headers). - to_return(status: 302, body: encrypted_user_attributes, headers: {}) - - expect(service.execute.to_s).to \ - match(/The service provider API returned an http status other than 200/) - end - end - - context 'when http status is unavailable (nil)' do - before do - allow_any_instance_of(Faraday::Response).to receive(:status).and_return(nil) - end - - let(:expected_error) do - { - service_error: 'The service provider API returned an http status other than 200: ' \ - 'unavailable (unavailable)', - } - end - - it 'rescues and returns the error' do - freeze_time do - stub_request(:get, request_uri). - with(headers: request_headers). - to_return(status: nil, body: encrypted_user_attributes, headers: {}) - - expect(service.execute).to eq expected_error - end - end - end - end - end -end diff --git a/spec/services/idv/steps/in_person/address_step_spec.rb b/spec/services/idv/steps/in_person/address_step_spec.rb index c4d89ee306e..cfadab1c5c2 100644 --- a/spec/services/idv/steps/in_person/address_step_spec.rb +++ b/spec/services/idv/steps/in_person/address_step_spec.rb @@ -2,7 +2,7 @@ describe Idv::Steps::InPerson::AddressStep do let(:submitted_values) { {} } - let(:params) { { doc_auth: submitted_values } } + let(:params) { ActionController::Parameters.new({ in_person_address: submitted_values }) } let(:user) { build(:user) } let(:service_provider) { create(:service_provider) } let(:controller) do diff --git a/spec/services/idv/steps/in_person/state_id_step_spec.rb b/spec/services/idv/steps/in_person/state_id_step_spec.rb index 3d1ea03fd11..f3767209e18 100644 --- a/spec/services/idv/steps/in_person/state_id_step_spec.rb +++ b/spec/services/idv/steps/in_person/state_id_step_spec.rb @@ -2,7 +2,7 @@ describe Idv::Steps::InPerson::StateIdStep do let(:submitted_values) { {} } - let(:params) { { doc_auth: submitted_values } } + let(:params) { ActionController::Parameters.new({ state_id: submitted_values }) } let(:user) { build(:user) } let(:service_provider) { create(:service_provider) } let(:controller) do @@ -78,6 +78,7 @@ let(:dob) { '1972-02-23' } let(:first_name) { 'First name' } let(:pii_from_user) { flow.flow_session[:pii_from_user] } + let(:params) { ActionController::Parameters.new } context 'first name and dob are set' do it 'returns extra view variables' do diff --git a/spec/services/idv/steps/ssn_step_spec.rb b/spec/services/idv/steps/ssn_step_spec.rb deleted file mode 100644 index 75ea852f83d..00000000000 --- a/spec/services/idv/steps/ssn_step_spec.rb +++ /dev/null @@ -1,110 +0,0 @@ -require 'rails_helper' - -describe Idv::Steps::SsnStep do - include Rails.application.routes.url_helpers - - let(:user) { build(:user) } - let(:params) { { doc_auth: {} } } - let(:session) { { sp: { issuer: service_provider.issuer } } } - let(:attempts_api) { IrsAttemptsApiTrackingHelper::FakeAttemptsTracker.new } - let(:service_provider) do - create( - :service_provider, - issuer: 'http://sp.example.com', - app_id: '123', - ) - end - let(:controller) do - instance_double( - 'controller', - session: session, - current_user: user, - params: params, - analytics: FakeAnalytics.new, - irs_attempts_api_tracker: attempts_api, - url_options: {}, - request: double( - 'request', - headers: { - 'X-Amzn-Trace-Id' => amzn_trace_id, - }, - ), - ) - end - let(:amzn_trace_id) { SecureRandom.uuid } - - let(:pii_from_doc) do - { - first_name: Faker::Name.first_name, - } - end - - let(:flow) do - Idv::Flows::DocAuthFlow.new(controller, {}, 'idv/doc_auth').tap do |flow| - flow.flow_session = { - pii_from_doc: pii_from_doc, - } - end - end - - subject(:step) do - Idv::Steps::SsnStep.new(flow) - end - - describe '#call' do - context 'with valid ssn' do - let(:ssn) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN[:ssn] } - let(:params) { { doc_auth: { ssn: ssn } } } - - it 'merges ssn into pii session value' do - step.call - - expect(flow.flow_session[:pii_from_doc][:ssn]).to eq(ssn) - end - - it 'logs attempts api event' do - expect(attempts_api).to receive(:idv_ssn_submitted).with( - ssn: ssn, - ) - step.call - end - - context 'with existing session applicant' do - let(:session) { super().merge(idv: { 'applicant' => {} }) } - - it 'clears applicant' do - step.call - - expect(session[:idv]['applicant']).to be_blank - end - end - - it 'adds a threatmetrix session id to flow session' do - step.extra_view_variables - expect(flow.flow_session[:threatmetrix_session_id]).to_not eq(nil) - end - - it 'does not change threatmetrix_session_id when updating ssn' do - step.call - session_id = flow.flow_session[:threatmetrix_session_id] - step.extra_view_variables - expect(flow.flow_session[:threatmetrix_session_id]).to eq(session_id) - end - end - - context 'when pii_from_doc is not present' do - let(:flow) do - Idv::Flows::DocAuthFlow.new(controller, {}, 'idv/doc_auth').tap do |flow| - flow.flow_session = { 'Idv::Steps::DocumentCaptureStep' => true } - end - end - - it 'marks previous step as incomplete' do - expect(flow.flow_session['Idv::Steps::DocumentCaptureStep']).to eq true - result = step.call - expect(flow.flow_session['Idv::Steps::DocumentCaptureStep']).to eq nil - expect(result.success?).to eq false - end - end - end -end diff --git a/spec/services/proofing/aamva/response/verification_response_spec.rb b/spec/services/proofing/aamva/response/verification_response_spec.rb index 5f5aeff4348..39ff9c207ba 100644 --- a/spec/services/proofing/aamva/response/verification_response_spec.rb +++ b/spec/services/proofing/aamva/response/verification_response_spec.rb @@ -49,7 +49,7 @@ it 'raises a VerificationError' do expect { subject }.to raise_error( Proofing::Aamva::VerificationError, - 'Unexpected status code in response: 504', + /Unexpected status code in response: 504/, ) end end diff --git a/spec/services/proofing/aamva/verification_client_spec.rb b/spec/services/proofing/aamva/verification_client_spec.rb index 46348a8a73e..1412531714a 100644 --- a/spec/services/proofing/aamva/verification_client_spec.rb +++ b/spec/services/proofing/aamva/verification_client_spec.rb @@ -19,11 +19,13 @@ subject(:verification_client) { described_class.new(AamvaFixtures.example_config) } describe '#send_verification_request' do - it 'should get the auth token from the auth client' do + before do auth_client = instance_double(Proofing::Aamva::AuthenticationClient) allow(auth_client).to receive(:fetch_token).and_return('ThisIsTheToken') allow(Proofing::Aamva::AuthenticationClient).to receive(:new).and_return(auth_client) + end + it 'gets the auth token from the auth client' do verification_stub = stub_request(:post, AamvaFixtures.example_config.verification_url). to_return(body: AamvaFixtures.verification_response, status: 200). with do |request| @@ -37,45 +39,149 @@ expect(verification_stub).to have_been_requested end + end - context 'when verification is successful' do - it 'should return a successful response' do - auth_client = instance_double(Proofing::Aamva::AuthenticationClient) - allow(auth_client).to receive(:fetch_token).and_return('ThisIsTheToken') - allow(Proofing::Aamva::AuthenticationClient).to receive(:new).and_return(auth_client) - stub_request(:post, AamvaFixtures.example_config.verification_url). - to_return(body: AamvaFixtures.verification_response, status: 200) - - response = verification_client.send_verification_request( - applicant: applicant, - session_id: '1234-abcd-efgh', - ) + describe '#send_verification_request' do + let(:response_body) { AamvaFixtures.verification_response } + let(:response_http_status) { 200 } + + before do + auth_client = instance_double(Proofing::Aamva::AuthenticationClient) + allow(auth_client).to receive(:fetch_token).and_return('ThisIsTheToken') + allow(Proofing::Aamva::AuthenticationClient).to receive(:new).and_return(auth_client) + + stub_request(:post, AamvaFixtures.example_config.verification_url). + to_return(body: response_body, status: response_http_status) + end + + let(:response) do + verification_client.send_verification_request( + applicant: applicant, + session_id: '1234-abcd-efgh', + ) + end + context 'when verification is successful' do + it 'returns a successful response' do expect(response).to be_a Proofing::Aamva::Response::VerificationResponse expect(response.success?).to eq(true) end end context 'when verification is not successful' do - it 'should return an unsuccessful response with errors' do - auth_client = instance_double(Proofing::Aamva::AuthenticationClient) - allow(auth_client).to receive(:fetch_token).and_return('ThisIsTheToken') - allow(Proofing::Aamva::AuthenticationClient).to receive(:new).and_return(auth_client) + context 'because we have a valid response and a 200 status, but the response says "no"' do + let(:response_body) do + modify_xml_at_xpath( + AamvaFixtures.verification_response, + '//PersonBirthDateMatchIndicator', + 'false', + ) + end + + it 'returns an unsuccessful response with errors' do + expect(response).to be_a Proofing::Aamva::Response::VerificationResponse + expect(response.success?).to eq(false) + end + end - stub_request(:post, AamvaFixtures.example_config.verification_url). - to_return(status: 200, body: modify_xml_at_xpath( + context 'because we have a valid response and a non-200 status, and the response says "no"' do + let(:response_body) do + modify_xml_at_xpath( AamvaFixtures.verification_response, '//PersonBirthDateMatchIndicator', 'false', - )) + ) + end + let(:response_http_status) { 500 } + + it 'throws an exception about the status code' do + expect { response }.to raise_error( + Proofing::Aamva::VerificationError, + /Unexpected status code in response: 500/, + ) + end + end - response = verification_client.send_verification_request( - applicant: applicant, - session_id: '1234-abcd-efgh', - ) + context 'because we have an MVA timeout and 500 status' do + let(:response_body) { AamvaFixtures.soap_fault_response } + let(:response_http_status) { 500 } - expect(response).to be_a Proofing::Aamva::Response::VerificationResponse - expect(response.success?).to eq(false) + it 'throws an exception about the MVA timeout' do + expect { response }.to raise_error( + Proofing::Aamva::VerificationError, + /#{Proofing::Aamva::Response::VerificationResponse::MVA_TIMEOUT_EXCEPTION}/o, + ) + end + + it 'throws an exception about the status code' do + expect { response }.to raise_error( + Proofing::Aamva::VerificationError, + /Unexpected status code in response: 500/, + ) + end + end + + context 'because we have an MVA timeout and 200 status' do + let(:response_body) { AamvaFixtures.soap_fault_response } + let(:response_http_status) { 200 } + + it 'parses the raw response body' do + begin + response + rescue Proofing::Aamva::VerificationError + end + end + + it 'throws an exception about the MVA timeout' do + expect { response }.to raise_error( + Proofing::Aamva::VerificationError, + /#{Proofing::Aamva::Response::VerificationResponse::MVA_TIMEOUT_EXCEPTION}/o, + ) + end + end + + context 'because we have an invalid response and a 200 status' do + let(:response_body) { 'error: computer has no brain.
' } + + it 'tries to parse the raw response body' do + begin + response + rescue Proofing::Aamva::VerificationError + end + end + + it 'throws a SOAP exception' do + expect { response }.to raise_error( + Proofing::Aamva::VerificationError, + /No close tag for \/br/, + ) + end + end + + context 'because we have an invalid response and a non-200 status' do + let(:response_body) { '

I\'m a teapot' } + let(:response_http_status) { 418 } + + it 'tries to parse the raw response body' do + begin + response + rescue Proofing::Aamva::VerificationError + end + end + + it 'throws an error which complains about the invalid response' do + expect { response }.to raise_error( + Proofing::Aamva::VerificationError, + /No close tag for \/h1/, + ) + end + + it 'throws an error which complains about the HTTP error code' do + expect { response }.to raise_error( + Proofing::Aamva::VerificationError, + /Unexpected status code in response: 418/, + ) + end end end end diff --git a/spec/services/proofing/lexis_nexis/instant_verify/proofing_spec.rb b/spec/services/proofing/lexis_nexis/instant_verify/proofing_spec.rb index fdeb8b9ba9f..ffd2146795e 100644 --- a/spec/services/proofing/lexis_nexis/instant_verify/proofing_spec.rb +++ b/spec/services/proofing/lexis_nexis/instant_verify/proofing_spec.rb @@ -44,7 +44,7 @@ result = subject.proof(applicant) expect(result.success?).to eq(true) - expect(result.errors).to eq({}) + expect(result.errors).to include(InstantVerify: include(a_kind_of(Hash))) expect(result.vendor_workflow).to( eq(LexisNexisFixtures.example_config.phone_finder_workflow), ) diff --git a/spec/services/proofing/lexis_nexis/phone_finder/proofing_spec.rb b/spec/services/proofing/lexis_nexis/phone_finder/proofing_spec.rb index 6ba049bc9a6..074142ab8d7 100644 --- a/spec/services/proofing/lexis_nexis/phone_finder/proofing_spec.rb +++ b/spec/services/proofing/lexis_nexis/phone_finder/proofing_spec.rb @@ -48,7 +48,9 @@ result = subject.proof(applicant) expect(result.success?).to eq(true) - expect(result.errors).to eq({}) + expect(result.errors).to include( + PhoneFinder: include(a_kind_of(Hash)), + ) expect(result.vendor_workflow).to( eq(LexisNexisFixtures.example_config.phone_finder_workflow), ) @@ -69,7 +71,6 @@ expect(result.success?).to eq(false) expect(result.errors).to include( - base: include(a_kind_of(String)), PhoneFinder: include(a_kind_of(Hash)), ) expect(result.transaction_id).to eq('31000000000000') diff --git a/spec/services/proofing/lexis_nexis/response_spec.rb b/spec/services/proofing/lexis_nexis/response_spec.rb index f61d77c61c9..cc434136747 100644 --- a/spec/services/proofing/lexis_nexis/response_spec.rb +++ b/spec/services/proofing/lexis_nexis/response_spec.rb @@ -38,7 +38,11 @@ end context 'with a passed verification' do - it { expect(subject.verification_errors).to eq({}) } + it 'returns a hash of error' do + errors = subject.verification_errors + + expect(errors).to have_key(:InstantVerify) + end end end diff --git a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb index 5ca6594595d..6386f38098f 100644 --- a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb +++ b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb @@ -11,15 +11,24 @@ merge(same_address_as_id: current_address_matches_id). transform_keys(&:to_s) end - let(:subject) { described_class } + subject(:subject) { described_class } let(:subject_analytics) { FakeAnalytics.new } + let(:transliterator) { UspsInPersonProofing::Transliterator.new } let(:service_provider) { nil } + let(:usps_ipp_transliteration_enabled) { true } before(:each) do stub_request_token stub_request_enroll allow(IdentityConfig.store).to receive(:usps_mock_fallback).and_return(usps_mock_fallback) + allow(subject).to receive(:transliterator).and_return(transliterator) + allow(transliterator).to receive(:transliterate). + with(anything) do |val| + transliterated_without_change(val) + end allow(subject).to receive(:analytics).and_return(subject_analytics) + allow(IdentityConfig.store).to receive(:usps_ipp_transliteration_enabled). + and_return(usps_ipp_transliteration_enabled) end describe '#schedule_in_person_enrollment' do @@ -58,38 +67,79 @@ expect(enrollment.current_address_matches_id).to eq(current_address_matches_id) end - it 'creates usps enrollment' do - proofer = UspsInPersonProofing::Mock::Proofer.new - mock = double - - expect(UspsInPersonProofing::Proofer).to receive(:new).and_return(mock) - expect(mock).to receive(:request_enroll) do |applicant| - expect(applicant.first_name).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:first_name]) - expect(applicant.last_name).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:last_name]) - expect(applicant.address).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:address1]) - expect(applicant.city).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:city]) - expect(applicant.state).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:state]) - expect(applicant.zip_code).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:zipcode]) - expect(applicant.email).to eq('no-reply@login.gov') - expect(applicant.unique_id).to eq(enrollment.unique_id) - - proofer.request_enroll(applicant) + context 'transliteration disabled' do + let(:usps_ipp_transliteration_enabled) { false } + + it 'creates usps enrollment without using transliteration' do + mock_proofer = double(UspsInPersonProofing::Mock::Proofer) + expect(subject).to receive(:usps_proofer).and_return(mock_proofer) + + expect(transliterator).not_to receive(:transliterate) + expect(mock_proofer).to receive(:request_enroll) do |applicant| + expect(applicant.first_name).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:first_name]) + expect(applicant.last_name).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:last_name]) + expect(applicant.address).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:address1]) + expect(applicant.city).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:city]) + expect(applicant.state).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:state]) + expect(applicant.zip_code).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:zipcode]) + expect(applicant.email).to eq('no-reply@login.gov') + expect(applicant.unique_id).to eq(enrollment.unique_id) + + UspsInPersonProofing::Mock::Proofer.new.request_enroll(applicant) + end + + subject.schedule_in_person_enrollment(user, pii) end + end - subject.schedule_in_person_enrollment(user, pii) + context 'transliteration enabled' do + let(:usps_ipp_transliteration_enabled) { true } + + it 'creates usps enrollment while using transliteration' do + mock_proofer = double(UspsInPersonProofing::Mock::Proofer) + expect(subject).to receive(:usps_proofer).and_return(mock_proofer) + + first_name = Idp::Constants::MOCK_IDV_APPLICANT[:first_name] + last_name = Idp::Constants::MOCK_IDV_APPLICANT[:last_name] + address = Idp::Constants::MOCK_IDV_APPLICANT[:address1] + city = Idp::Constants::MOCK_IDV_APPLICANT[:city] + + expect(transliterator).to receive(:transliterate). + with(first_name).and_return(transliterated_without_change(first_name)) + expect(transliterator).to receive(:transliterate). + with(last_name).and_return(transliterated(last_name)) + expect(transliterator).to receive(:transliterate). + with(address).and_return(transliterated_with_failure(address)) + expect(transliterator).to receive(:transliterate). + with(city).and_return(transliterated(city)) + + expect(mock_proofer).to receive(:request_enroll) do |applicant| + expect(applicant.first_name).to eq(first_name) + expect(applicant.last_name).to eq("transliterated_#{last_name}") + expect(applicant.address).to eq(address) + expect(applicant.city).to eq("transliterated_#{city}") + expect(applicant.state).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:state]) + expect(applicant.zip_code).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:zipcode]) + expect(applicant.email).to eq('no-reply@login.gov') + expect(applicant.unique_id).to eq(enrollment.unique_id) + + UspsInPersonProofing::Mock::Proofer.new.request_enroll(applicant) + end + + subject.schedule_in_person_enrollment(user, pii) + end end context 'when the enrollment does not have a unique ID' do it 'uses the deprecated InPersonEnrollment#usps_unique_id value to create the enrollment' do enrollment.update(unique_id: nil) - proofer = UspsInPersonProofing::Mock::Proofer.new - mock = double + mock_proofer = double(UspsInPersonProofing::Mock::Proofer) + expect(subject).to receive(:usps_proofer).and_return(mock_proofer) - expect(UspsInPersonProofing::Proofer).to receive(:new).and_return(mock) - expect(mock).to receive(:request_enroll) do |applicant| + expect(mock_proofer).to receive(:request_enroll) do |applicant| expect(applicant.unique_id).to eq(enrollment.usps_unique_id) - proofer.request_enroll(applicant) + UspsInPersonProofing::Mock::Proofer.new.request_enroll(applicant) end subject.schedule_in_person_enrollment(user, pii) @@ -146,4 +196,31 @@ end end end + + def transliterated_without_change(value) + UspsInPersonProofing::Transliterator::TransliterationResult.new( + changed?: false, + original: value, + transliterated: value, + unsupported_chars: [], + ) + end + + def transliterated(value) + UspsInPersonProofing::Transliterator::TransliterationResult.new( + changed?: true, + original: value, + transliterated: "transliterated_#{value}", + unsupported_chars: [], + ) + end + + def transliterated_with_failure(value) + UspsInPersonProofing::Transliterator::TransliterationResult.new( + changed?: true, + original: value, + transliterated: "transliterated_failed_#{value}", + unsupported_chars: [':'], + ) + end end diff --git a/spec/services/usps_in_person_proofing/transliterator_spec.rb b/spec/services/usps_in_person_proofing/transliterator_spec.rb new file mode 100644 index 00000000000..bbc0ac03dff --- /dev/null +++ b/spec/services/usps_in_person_proofing/transliterator_spec.rb @@ -0,0 +1,93 @@ +require 'rails_helper' + +RSpec.describe UspsInPersonProofing::Transliterator do + describe '#transliterate' do + subject(:transliterator) { UspsInPersonProofing::Transliterator.new } + context 'baseline functionality' do + context 'with an input that requires transliteration' do + let(:input_value) { "\t\n BobИy \t TЉble?s\r\n" } + let(:result) { transliterator.transliterate(input_value) } + let(:transliterated_result) { 'Bob?y T?ble?s' } + + it 'returns the original value that was requested to be transliterated' do + expect(result.original).to eq(input_value) + end + it 'includes a "changed?" key indicating that transliteration did change the value' do + expect(result.changed?).to be(true) + end + it 'strips whitespace from the ends' do + expect(result.transliterated).not_to match(/^\s+/) + expect(result.transliterated).not_to match(/\s+^/) + end + it 'replaces consecutive whitespaces with regular spaces' do + expect(result.transliterated).not_to match(/\s\s/) + expect(result.transliterated).not_to match(/[^\S ]+/) + end + it 'returns a list of the characters that transliteration does not support' do + expect(result.unsupported_chars).to include('И', 'Љ') + end + it 'transliterates using English locale when default does not match' do + expect(I18n).to receive(:transliterate). + with(duck_type(:to_s), locale: :en). + and_call_original + result + end + it 'does not count question marks as unsupported characters by default' do + expect(result.unsupported_chars).not_to include('?') + expect(result.transliterated).to include('?') + end + it 'returns the transliterated value' do + expect(result.transliterated).to eq(transliterated_result) + end + end + context 'with an input that does not require transliteration' do + let(:input_value) { 'Abc Is My Fav Number' } + let(:result) { transliterator.transliterate(input_value) } + + it 'returns the original value that was requested to be transliterated' do + expect(result.original).to eq(input_value) + end + it 'includes a "changed?" key indicating that transliteration did not change the value' do + expect(result.changed?).to be(false) + end + + it 'transliterated value is identical to the original value' do + expect(result.transliterated).to eq(input_value) + end + end + end + + context 'for additional values not supported for transliteration by default' do + { + # Convert okina to apostrophe + "ʻ": "'", + # Convert quotation marks + "’": "'", + "‘": "'", + "‛": "'", + "“": '"', + "‟": '"', + "”": '"', + # Convert hyphens + "‐": '-', + "‑": '-', + "‒": '-', + "–": '-', + "—": '-', + "﹘": '-', + # Convert number signs + "﹟": '#', + "#": '#', + }.each do |key, value| + it "converts \"\\u#{key.to_s.ord.to_s(16).rjust( + 4, + '0', + )}\" to \"\\u#{value.ord.to_s(16).rjust( + 4, '0' + )}\"" do + expect(transliterator.transliterate(key).transliterated).to eq(value) + end + end + end + end +end diff --git a/spec/support/controller_helper.rb b/spec/support/controller_helper.rb index d607be3fd3c..61db17ce642 100644 --- a/spec/support/controller_helper.rb +++ b/spec/support/controller_helper.rb @@ -67,6 +67,7 @@ def stub_verify_steps_one_and_two(user) ssn: '666-12-1234', }.with_indifferent_access idv_session.profile_confirmation = true + idv_session.resolution_successful = 'phone' allow(subject).to receive(:confirm_idv_applicant_created).and_return(true) allow(subject).to receive(:idv_session).and_return(idv_session) allow(subject).to receive(:user_session).and_return(user_session) diff --git a/spec/support/features/doc_auth_helper.rb b/spec/support/features/doc_auth_helper.rb index cb3ec0ddc96..0a52a1708f9 100644 --- a/spec/support/features/doc_auth_helper.rb +++ b/spec/support/features/doc_auth_helper.rb @@ -5,6 +5,7 @@ module DocAuthHelper GOOD_SSN = Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN[:ssn] GOOD_SSN_MASKED = '9**-**-***4' + SAMPLE_TMX_SUMMARY_REASON_CODE = { tmx_summary_reason_code: ['Identity_Negative_History'] } SSN_THAT_FAILS_RESOLUTION = '123-45-6666' SSN_THAT_RAISES_EXCEPTION = '000-00-0000' @@ -57,10 +58,6 @@ def idv_doc_auth_upload_step idv_doc_auth_step_path(step: :upload) end - def idv_doc_auth_ssn_step - idv_doc_auth_step_path(step: :ssn) - end - def idv_doc_auth_document_capture_step idv_doc_auth_step_path(step: :document_capture) end @@ -303,4 +300,22 @@ def fill_out_address_form_fail def fill_out_doc_auth_phone_form_ok(phone = '415-555-0199') fill_in :doc_auth_phone, with: phone end + + def complete_all_idv_steps_with(threatmetrix:) + allow(IdentityConfig.store).to receive(:otp_delivery_blocklist_maxretry).and_return(300) + user = create(:user, :signed_up) + visit_idp_from_ial1_oidc_sp( + client_id: service_provider.issuer, + irs_attempts_api_session_id: 'test-session-id', + ) + visit root_path + sign_in_and_2fa_user(user) + complete_doc_auth_steps_before_ssn_step + select threatmetrix, from: :mock_profiling_result + complete_ssn_step + click_idv_continue + complete_phone_step(user) + complete_review_step(user) + acknowledge_and_confirm_personal_key + end end diff --git a/spec/support/features/idv_helper.rb b/spec/support/features/idv_helper.rb index 13cf08e2199..2deaaf63487 100644 --- a/spec/support/features/idv_helper.rb +++ b/spec/support/features/idv_helper.rb @@ -152,30 +152,6 @@ def visit_idp_from_oidc_sp_with_ial2( ) end - def visit_idp_from_oidc_va_with_ial2( - client_id: sp_oidc_issuer, - state: SecureRandom.hex, - nonce: SecureRandom.hex, - verified_within: nil, - inherited_proofing_auth: Idv::InheritedProofing::Va::Mocks::Service::VALID_AUTH_CODE - ) - @state = state - @client_id = sp_oidc_issuer - @nonce = nonce - visit openid_connect_authorize_path( - client_id: client_id, - response_type: 'code', - acr_values: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, - scope: 'openid email profile:name phone social_security_number', - redirect_uri: sp_oidc_redirect_uri, - state: state, - prompt: 'select_account', - nonce: nonce, - verified_within: verified_within, - inherited_proofing_auth: inherited_proofing_auth, - ) - end - def visit_idp_from_oidc_sp_with_loa3 visit openid_connect_authorize_path( client_id: sp_oidc_issuer, diff --git a/spec/support/features/inherited_proofing_helper.rb b/spec/support/features/inherited_proofing_helper.rb deleted file mode 100644 index 90e397d0ce6..00000000000 --- a/spec/support/features/inherited_proofing_helper.rb +++ /dev/null @@ -1,62 +0,0 @@ -require_relative 'idv_step_helper' -require_relative 'doc_auth_helper' - -module InheritedProofingHelper - include IdvStepHelper - include DocAuthHelper - - # Steps - def idv_ip_get_started_step - idv_inherited_proofing_step_path(step: :get_started) - end - - def idv_inherited_proofing_agreement_step - idv_inherited_proofing_step_path(step: :agreement) - end - - def idv_ip_verify_info_step - idv_inherited_proofing_step_path(step: :verify_info) - end - - # Traverse Steps - - # create account - def complete_inherited_proofing_steps_before_get_started_step(expect_accessible: false) - visit idv_ip_get_started_step unless current_path == idv_ip_get_started_step - expect(page).to be_axe_clean.according_to :section508, :"best-practice" if expect_accessible - end - - # get started - def complete_get_started_step - click_on t('inherited_proofing.buttons.continue') - end - - def complete_inherited_proofing_steps_before_agreement_step(expect_accessible: false) - complete_inherited_proofing_steps_before_get_started_step(expect_accessible: expect_accessible) - complete_get_started_step - expect(page).to be_axe_clean.according_to :section508, :"best-practice" if expect_accessible - end - - # get started > agreement > verify_wait > please verify - def complete_agreement_step - find('label', text: t('inherited_proofing.instructions.consent', app_name: APP_NAME)).click - click_on t('inherited_proofing.buttons.continue') - end - - def complete_inherited_proofing_steps_before_verify_step(expect_accessible: false) - complete_inherited_proofing_steps_before_agreement_step(expect_accessible: expect_accessible) - complete_agreement_step - expect(page).to be_axe_clean.according_to :section508, :"best-practice" if expect_accessible - end - - def complete_inherited_proofing_verify_step - click_on t('inherited_proofing.buttons.continue') - end - - # get_started > agreement > verify_wait > please verify > complete - def complete_all_inherited_proofing_steps_to_handoff(expect_accessible: false) - complete_inherited_proofing_steps_before_verify_step(expect_accessible: expect_accessible) - complete_inherited_proofing_verify_step - expect(page).to be_axe_clean.according_to :section508, :"best-practice" if expect_accessible - end -end diff --git a/spec/support/features/inherited_proofing_with_service_provider_helper.rb b/spec/support/features/inherited_proofing_with_service_provider_helper.rb deleted file mode 100644 index f899b293d2c..00000000000 --- a/spec/support/features/inherited_proofing_with_service_provider_helper.rb +++ /dev/null @@ -1,55 +0,0 @@ -require_relative 'idv_step_helper' -require_relative 'doc_auth_helper' - -module InheritedProofingWithServiceProviderHelper - include IdvStepHelper - include DocAuthHelper - - # Simulates a user (in this case, a VA inherited proofing-authorized user) - # coming over to login.gov from a service provider, and hitting the - # OpenidConnect::AuthorizationController#index action. - def send_user_from_service_provider_to_login_gov_openid_connect(user, inherited_proofing_auth) - expect(user).to_not be_nil - # NOTE: VA user. - visit_idp_from_oidc_va_with_ial2 inherited_proofing_auth: inherited_proofing_auth - end - - def complete_steps_up_to_inherited_proofing_get_started_step(user, expect_accessible: false) - unless current_path == idv_inherited_proofing_step_path(step: :get_started) - complete_idv_steps_before_phone_step(user) - click_link t('links.cancel') - click_button t('idv.cancel.actions.start_over') - expect(page).to have_current_path(idv_inherited_proofing_step_path(step: :get_started)) - end - expect(page).to be_axe_clean.according_to :section508, :"best-practice" if expect_accessible - end - - def complete_steps_up_to_inherited_proofing_how_verifying_step(user, expect_accessible: false) - complete_steps_up_to_inherited_proofing_get_started_step user, - expect_accessible: expect_accessible - unless current_path == idv_inherited_proofing_step_path(step: :agreement) - click_on t('inherited_proofing.buttons.continue') - end - end - - def complete_steps_up_to_inherited_proofing_we_are_retrieving_step(user, - expect_accessible: false) - complete_steps_up_to_inherited_proofing_how_verifying_step( - user, - expect_accessible: expect_accessible, - ) - unless current_path == idv_inherited_proofing_step_path(step: :verify_wait) - check t('inherited_proofing.instructions.consent', app_name: APP_NAME), - allow_label_click: true - click_on t('inherited_proofing.buttons.continue') - end - end - - def complete_steps_up_to_inherited_proofing_verify_your_info_step(user, - expect_accessible: false) - complete_steps_up_to_inherited_proofing_we_are_retrieving_step( - user, - expect_accessible: expect_accessible, - ) - end -end diff --git a/spec/support/idv_examples/fail_to_verify.rb b/spec/support/idv_examples/fail_to_verify.rb deleted file mode 100644 index 2ce9e8650d9..00000000000 --- a/spec/support/idv_examples/fail_to_verify.rb +++ /dev/null @@ -1,45 +0,0 @@ -shared_examples 'fail to verify idv info' do |step| - let(:locale) { LinkLocaleResolver.locale } - let(:step_locale_key) do - return :sessions if step == :profile - step - end - - before do - start_idv_from_sp - complete_idv_steps_before_step(step) - fill_out_idv_form_fail if step == :profile - fill_out_phone_form_fail if step == :phone - click_continue - click_continue - end - - it 'renders a warning failure screen and lets the user try again' do - expect(page).to have_current_path(session_failure_path) if step == :profile - expect(page).to have_current_path(phone_failure_path) if step == :phone - expect(page).to have_content t("idv.failure.#{step_locale_key}.heading") - expect(page).to have_content t("idv.failure.#{step_locale_key}.warning") - - click_on t('idv.failure.button.warning') - - if step == :profile - fill_out_idv_form_ok - click_idv_continue - end - fill_out_phone_form_ok if step == :phone - click_idv_continue - - expect(page).to have_current_path(idv_phone_path) if step == :profile - expect(page).to have_current_path(idv_otp_delivery_method_path) if step == :phone - expect(page).to have_content(t('idv.titles.session.phone')) if step == :profile - expect(page).to have_content(t('idv.titles.otp_delivery_method')) if step == :phone - end - - def session_failure_path - idv_session_errors_warning_path(local: locale) - end - - def phone_failure_path - idv_phone_errors_warning_path(locale: locale) - end -end diff --git a/spec/support/shared_contexts/inherited_proofing/encrypted_user_attributes.json b/spec/support/shared_contexts/inherited_proofing/encrypted_user_attributes.json deleted file mode 100644 index af29a512873..00000000000 --- a/spec/support/shared_contexts/inherited_proofing/encrypted_user_attributes.json +++ /dev/null @@ -1 +0,0 @@ -{"data":"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.IL_uTLpwR3ZoDQKuRY_clxK1AmrEnf3rCREIj8XGQ-iA7NxCiYfZ2CxuXFOTIzFbKXjcNYT1F56bCUuPwSmHNt88AGumB3RcskR6POfBu8EcjK2CI6myycGuQwm_1Dp9Vi55TQpSFRy5Bld7IR0gbk4ju0qTVSeH59-AyBGr0w07vojdHcPe-SDWEC1pG0_4iyVg0x2wOFAh6kIjMJ04sJYB4e7uW8hEI7lSwDLpiW-8KsjGhwCVIkUGPw7XKtLiWo1U_nXSragpG-E6XRx0Hn3YckSwEAMTATeZZPJr0TAAMO_jtukL0e7_ApwsCI-sEdI035_4befLlDnuz1QFJg.oLmsRlZKFlL_3Th4.YumiTPq6y8jyCpVwuSpqsd8iWQ_AqEN81v8pV9lB2dPb6po03aj05K361IWmWfB3gXir--L3nPpUdlFFkxF1X12QVkpfmH03kj01Zoaq9hZcQvY7d4QoOkMNkdONNFZ3_sp-4-11m5ki2TpD1AidkLe7AIaSvBvhYOq0TC-0veLwRvp5234-XyDq9o5hLogzUa3G1BxcZO_TxpS5IhV4CzJ2a-o_ymSgUULDjrAty23XMiqXxTMFbVCpMDrvgGTX2TYOYx0PngjySlir6Zf4WjKhvFBOd34hvx2MUYTEGPw.UcPA0owzraT7ckc1cRDzeg"} diff --git a/spec/support/shared_contexts/inherited_proofing/va_api_context.rb b/spec/support/shared_contexts/inherited_proofing/va_api_context.rb deleted file mode 100644 index bec50c6a17c..00000000000 --- a/spec/support/shared_contexts/inherited_proofing/va_api_context.rb +++ /dev/null @@ -1,15 +0,0 @@ -RSpec.shared_context 'va_api_context' do - # Sample mocked API call: - # stub_request(:get, request_uri). - # with(headers: request_headers). - # to_return(status: 200, body: '{}', headers: {}) - - let(:auth_code) { 'mocked-auth-code-for-testing' } - let(:private_key) { private_key_from_store_or(file_name: 'empty.key') } - let(:payload) { { inherited_proofing_auth: auth_code, exp: 1.day.from_now.to_i } } - let(:jwt_token) { JWT.encode(payload, private_key, 'RS256') } - let(:request_uri) do - "#{Idv::InheritedProofing::Va::Service::BASE_URI}/inherited_proofing/user_attributes" - end - let(:request_headers) { { Authorization: "Bearer #{jwt_token}" } } -end diff --git a/spec/support/shared_contexts/inherited_proofing/va_user_context.rb b/spec/support/shared_contexts/inherited_proofing/va_user_context.rb deleted file mode 100644 index 81ac0b82545..00000000000 --- a/spec/support/shared_contexts/inherited_proofing/va_user_context.rb +++ /dev/null @@ -1,18 +0,0 @@ -RSpec.shared_context 'va_user_context' do - # As given to us from VA - let(:user_attributes) do - { first_name: 'Fakey', - last_name: 'Fakerson', - address: { street: '123 Fake St', - street2: 'Apt 235', - city: 'Faketown', - state: 'WA', - country: nil, - zip: '98037' }, - phone: '2063119187', - birth_date: '2022-1-31', - ssn: '123456789' } - end - # Encrypted with AppArtifacts.store.oidc_private_key for testing - let(:encrypted_user_attributes) { File.read("#{__dir__}/encrypted_user_attributes.json") } -end diff --git a/spec/views/idv/inherited_proofing/agreement.html.erb_spec.rb b/spec/views/idv/inherited_proofing/agreement.html.erb_spec.rb deleted file mode 100644 index d8f6ac9c379..00000000000 --- a/spec/views/idv/inherited_proofing/agreement.html.erb_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'rails_helper' - -describe 'idv/inherited_proofing/agreement.html.erb' do - include Devise::Test::ControllerHelpers - - let(:flow_session) { {} } - let(:sp_name) { 'test' } - - before do - allow(view).to receive(:decorated_session).and_return(@decorated_session) - allow(view).to receive(:flow_session).and_return(flow_session) - allow(view).to receive(:url_for).and_return('https://www.example.com/') - allow(view).to receive(:user_signing_up?).and_return(true) - end - - it 'renders the Continue button' do - render template: 'idv/inherited_proofing/agreement' - - expect(rendered).to have_button(t('inherited_proofing.buttons.continue')) - end - - it 'renders the Cancel link' do - render template: 'idv/inherited_proofing/agreement' - - expect(rendered).to have_link(t('links.cancel_account_creation')) - end - - context 'with or without service provider' do - it 'renders content' do - render template: 'idv/inherited_proofing/agreement' - - expect(rendered).to have_content(t('inherited_proofing.info.lets_go')) - expect(rendered).to have_content( - t('inherited_proofing.headings.verify_identity'), - ) - expect(rendered).to have_content(t('inherited_proofing.info.verify_identity')) - expect(rendered).to have_content( - t('inherited_proofing.headings.secure_account'), - ) - expect(rendered).to have_content( - t('inherited_proofing.info.secure_account', sp_name: sp_name), - ) - end - end -end diff --git a/spec/views/idv/inherited_proofing/get_started.html.erb_spec.rb b/spec/views/idv/inherited_proofing/get_started.html.erb_spec.rb deleted file mode 100644 index 76d27eef061..00000000000 --- a/spec/views/idv/inherited_proofing/get_started.html.erb_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -require 'rails_helper' - -describe 'idv/inherited_proofing/get_started.html.erb' do - include Devise::Test::ControllerHelpers - - let(:flow_session) { {} } - let(:sp_name) { 'test' } - - before do - @decorated_session = instance_double(ServiceProviderSessionDecorator) - allow(@decorated_session).to receive(:sp_name).and_return(sp_name) - allow(view).to receive(:decorated_session).and_return(@decorated_session) - allow(view).to receive(:flow_session).and_return(flow_session) - allow(view).to receive(:url_for).and_return('https://www.example.com/') - allow(view).to receive(:user_fully_authenticated?).and_return(true) - allow(view).to receive(:user_signing_up?).and_return(true) - - @presenter = instance_double(Idv::InheritedProofing::InheritedProofingPresenter) - allow(@presenter).to receive(:learn_more_phone_or_mail_url).and_return('https://www.va.gov/resources/managing-your-vagov-profile/') - allow(@presenter).to receive(:get_help_url).and_return('https://www.va.gov/resources/managing-your-vagov-profile/') - allow(view).to receive(:presenter).and_return(@presenter) - end - - it 'renders the Continue button' do - render template: 'idv/inherited_proofing/get_started' - - expect(rendered).to have_button(t('inherited_proofing.buttons.continue')) - end - - it 'renders the Cancel link' do - render template: 'idv/inherited_proofing/get_started' - - expect(rendered).to have_link(t('links.cancel_account_creation')) - end - - context 'with or without service provider' do - it 'renders troubleshooting options' do - render template: 'idv/inherited_proofing/get_started' - - expect(rendered).to have_link( - t('inherited_proofing.troubleshooting.options.learn_more_phone_or_mail'), - ) - expect(rendered).not_to have_link(nil, href: idv_inherited_proofing_return_to_sp_path) - expect(rendered).to have_link( - t( - 'inherited_proofing.troubleshooting.options.get_help', - sp_name: sp_name, - ), - ) - expect(rendered).to have_link( - t('inherited_proofing.troubleshooting.options.learn_more_phone_or_mail'), - ) - end - end -end diff --git a/spec/views/idv/inherited_proofing/retrieval.html.erb_spec.rb b/spec/views/idv/inherited_proofing/retrieval.html.erb_spec.rb deleted file mode 100644 index 7db2ed40eb1..00000000000 --- a/spec/views/idv/inherited_proofing/retrieval.html.erb_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'rails_helper' - -describe 'idv/inherited_proofing/verify_wait.html.erb' do - include Devise::Test::ControllerHelpers - - it 'renders' do - render template: 'idv/inherited_proofing/verify_wait' - - # Appropriate header - expect(rendered).to have_text(t('inherited_proofing.headings.retrieval')) - - # Spinner - expect(rendered).to have_css("img[src*='shield-spinner']") - - # Appropriate text - expect(rendered).to have_text(t('inherited_proofing.info.retrieval_time')) - expect(rendered).to have_text(t('inherited_proofing.info.retrieval_thanks')) - end -end diff --git a/spec/views/idv/otp_delivery_method/new.html.erb_spec.rb b/spec/views/idv/otp_delivery_method/new.html.erb_spec.rb deleted file mode 100644 index 46d4585739d..00000000000 --- a/spec/views/idv/otp_delivery_method/new.html.erb_spec.rb +++ /dev/null @@ -1,81 +0,0 @@ -require 'rails_helper' - -describe 'idv/otp_delivery_method/new.html.erb' do - let(:gpo_letter_available) { false } - let(:step_indicator_steps) { Idv::Flows::DocAuthFlow::STEP_INDICATOR_STEPS } - let(:supports_sms) { true } - let(:supports_voice) { true } - - before do - phone_number_capabilities = instance_double( - PhoneNumberCapabilities, - supports_sms?: supports_sms, - supports_voice?: supports_voice, - ) - - allow(view).to receive(:phone_number_capabilities).and_return(phone_number_capabilities) - allow(view).to receive(:user_signing_up?).and_return(false) - allow(view).to receive(:user_fully_authenticated?).and_return(true) - allow(view).to receive(:gpo_letter_available).and_return(gpo_letter_available) - allow(view).to receive(:step_indicator_steps).and_return(step_indicator_steps) - end - - subject(:rendered) { render template: 'idv/otp_delivery_method/new' } - - context 'gpo letter available' do - let(:gpo_letter_available) { true } - - it 'renders troubleshooting options' do - expect(rendered).to have_link(t('idv.troubleshooting.options.change_phone_number')) - expect(rendered).to have_link(t('idv.troubleshooting.options.verify_by_mail')) - end - end - - context 'gpo letter not available' do - let(:gpo_letter_available) { false } - - it 'renders troubleshooting options' do - expect(rendered).to have_link(t('idv.troubleshooting.options.change_phone_number')) - expect(rendered).not_to have_link(t('idv.troubleshooting.options.verify_by_mail')) - end - end - - context 'phone vendor outage' do - before do - allow_any_instance_of(VendorStatus).to receive(:vendor_outage?).and_return(false) - allow_any_instance_of(VendorStatus).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 - - it 'disables problematic vendor option' do - expect(rendered).to have_field('otp_delivery_preference', with: :voice, disabled: false) - expect(rendered).to have_field('otp_delivery_preference', with: :sms, disabled: true) - end - end - - it 'renders sms and voice options' do - expect(rendered).to have_field('otp_delivery_preference', with: :voice) - expect(rendered).to have_field('otp_delivery_preference', with: :sms) - end - - context 'without sms support' do - let(:supports_sms) { false } - - it 'renders voice option' do - expect(rendered).to have_field('otp_delivery_preference', with: :voice) - expect(rendered).not_to have_field('otp_delivery_preference', with: :sms) - end - end - - context 'without voice support' do - let(:supports_voice) { false } - - it 'renders sms option' do - expect(rendered).not_to have_field('otp_delivery_preference', with: :voice) - expect(rendered).to have_field('otp_delivery_preference', with: :sms) - end - end -end diff --git a/yarn.lock b/yarn.lock index fc21062e5bc..0d867c73df7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2232,15 +2232,15 @@ browser-stdout@1.3.1: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== -browserslist@^4.14.5, browserslist@^4.19.1, browserslist@^4.21.3, browserslist@^4.21.4: - version "4.21.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" - integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== +browserslist@^4.14.5, browserslist@^4.19.1, browserslist@^4.21.3, browserslist@^4.21.5: + version "4.21.5" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" + integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== dependencies: - caniuse-lite "^1.0.30001400" - electron-to-chromium "^1.4.251" - node-releases "^2.0.6" - update-browserslist-db "^1.0.9" + caniuse-lite "^1.0.30001449" + electron-to-chromium "^1.4.284" + node-releases "^2.0.8" + update-browserslist-db "^1.0.10" buffer-builder@^0.2.0: version "0.2.0" @@ -2302,10 +2302,10 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -caniuse-lite@^1.0.30001400: - version "1.0.30001441" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001441.tgz#987437b266260b640a23cd18fbddb509d7f69f3e" - integrity sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg== +caniuse-lite@^1.0.30001449: + version "1.0.30001458" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001458.tgz#871e35866b4654a7d25eccca86864f411825540c" + integrity sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w== chai-as-promised@^7.1.1: version "7.1.1" @@ -2907,10 +2907,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.4.251: - version "1.4.284" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" - integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== +electron-to-chromium@^1.4.284: + version "1.4.314" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.314.tgz#33e4ad7a2ca2ddbe2e113874cc0c0e2a00cb46bf" + integrity sha512-+3RmNVx9hZLlc0gW//4yep0K5SYKmIvB5DXg1Yg6varsuAHlHwTeqeygfS8DWwLCsNOWrgj+p9qgM5WYjw1lXQ== element-closest@^2.0.1: version "2.0.2" @@ -4897,10 +4897,10 @@ node-modules-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= -node-releases@^2.0.6: - version "2.0.8" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.8.tgz#0f349cdc8fcfa39a92ac0be9bc48b7706292b9ae" - integrity sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A== +node-releases@^2.0.8: + version "2.0.10" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f" + integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== normalize-package-data@^2.5.0: version "2.5.0" @@ -6567,7 +6567,7 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= -update-browserslist-db@^1.0.9: +update-browserslist-db@^1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==