diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b8ed7a69236..371a9828d81 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -187,7 +187,7 @@ specs: POSTGRES_HOST_AUTH_METHOD: trust RAILS_ENV: test services: - - name: postgis/postgis:13-3.3 + - name: postgres:13.9 alias: db-postgres command: ['--fsync=false', '--synchronous_commit=false', '--full_page_writes=false'] - name: redis:7.0 diff --git a/Brewfile b/Brewfile index 0ebe762e20f..2018d5ff118 100644 --- a/Brewfile +++ b/Brewfile @@ -1,5 +1,4 @@ brew 'postgresql@14' -brew 'postgis' brew 'redis' brew 'node@20' brew 'yarn' diff --git a/Gemfile b/Gemfile index 291ab73a93b..5b39b9cd35a 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,6 @@ ruby "~> #{File.read(File.join(__dir__, '.ruby-version')).strip}" gem 'rails', '~> 7.1.3' -gem 'activerecord-postgis-adapter', '~> 9.0' gem 'ahoy_matey', '~> 3.0' gem 'aws-sdk-kms', '~> 1.4' gem 'aws-sdk-cloudwatchlogs', require: false @@ -24,7 +23,7 @@ gem 'caxlsx', require: false gem 'concurrent-ruby' gem 'connection_pool' gem 'csv' -gem 'cssbundling-rails' +gem 'cssbundling-rails', '1.0.0' gem 'devise', '~> 4.8' gem 'dotiw', '>= 4.0.1' gem 'faraday', '~> 2' diff --git a/Gemfile.lock b/Gemfile.lock index 89c453cc548..0401ebac159 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -136,9 +136,6 @@ GEM activemodel (= 7.1.3.2) activesupport (= 7.1.3.2) timeout (>= 0.4.0) - activerecord-postgis-adapter (9.0.1) - activerecord (~> 7.1.0) - rgeo-activerecord (~> 7.0.0) activestorage (7.1.3.2) actionpack (= 7.1.3.2) activejob (= 7.1.3.2) @@ -226,7 +223,7 @@ GEM erubi (~> 1.4) parser (>= 2.4) smart_properties - bigdecimal (3.1.6) + bigdecimal (3.1.7) bindata (2.4.15) bootsnap (1.17.0) msgpack (~> 1.2) @@ -300,8 +297,7 @@ GEM dotiw (5.3.2) activesupport i18n - drb (2.2.0) - ruby2_keywords + drb (2.2.1) dumb_delegator (1.0.0) email_spec (2.2.2) htmlentities (~> 4.3.3) @@ -360,7 +356,7 @@ GEM htmlbeautifier (1.4.2) htmlentities (4.3.4) http_accept_language (2.1.1) - i18n (1.14.1) + i18n (1.14.4) concurrent-ruby (~> 1.0) i18n-tasks (1.0.12) activesupport (>= 4.0.2) @@ -374,10 +370,10 @@ GEM rainbow (>= 2.2.2, < 4.0) terminal-table (>= 1.5.1) ice_nine (0.11.2) - io-console (0.7.1) - irb (1.11.0) + io-console (0.7.2) + irb (1.12.0) rdoc - reline (>= 0.3.8) + reline (>= 0.4.2) jmespath (1.6.2) jsbundling-rails (1.1.2) railties (>= 6.0.0) @@ -428,7 +424,7 @@ GEM mini_histogram (0.3.1) mini_mime (1.1.5) mini_portile2 (2.8.5) - minitest (5.22.2) + minitest (5.22.3) msgpack (1.7.2) multiset (0.5.3) mutex_m (0.2.0) @@ -448,7 +444,7 @@ GEM net-ssh (6.1.0) newrelic_rpm (9.7.0) nio4r (2.7.0) - nokogiri (1.16.3) + nokogiri (1.16.4) mini_portile2 (~> 2.8.2) racc (~> 1.4) openssl (3.0.2) @@ -498,7 +494,7 @@ GEM nio4r (~> 2.0) raabro (1.4.0) racc (1.7.3) - rack (3.0.9.1) + rack (3.0.10) rack-cors (2.0.2) rack (>= 2.0.0) rack-headers_filter (0.0.1) @@ -554,20 +550,20 @@ GEM thor (~> 1.0, >= 1.2.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.1.0) + rake (13.2.1) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rdoc (6.6.2) + rdoc (6.6.3.1) psych (>= 4.0.0) redacted_struct (2.0.0) redcarpet (3.6.0) - redis (5.0.6) - redis-client (>= 0.9.0) - redis-client (0.14.1) + redis (5.1.0) + redis-client (>= 0.17.0) + redis-client (0.22.0) connection_pool regexp_parser (2.9.0) - reline (0.4.1) + reline (0.5.1) io-console (~> 0.5) request_store (1.5.1) rack (>= 1.4) @@ -576,10 +572,6 @@ GEM railties (>= 5.0) retries (0.0.5) rexml (3.2.6) - rgeo (3.0.0) - rgeo-activerecord (7.0.1) - activerecord (>= 5.0) - rgeo (>= 1.0.0) rotp (6.3.0) rouge (4.2.0) rqrcode (2.1.0) @@ -686,7 +678,7 @@ GEM tableparser (1.0.1) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) - thor (1.3.0) + thor (1.3.1) thread_safe (0.3.6) timeout (0.4.1) tpm-key_attestation (0.11.0) @@ -740,7 +732,7 @@ GEM xpath (3.2.0) nokogiri (~> 1.8) yard (0.9.36) - zeitwerk (2.6.12) + zeitwerk (2.6.13) zlib (3.0.0) zonebie (0.6.1) zxcvbn (0.1.9) @@ -749,7 +741,6 @@ PLATFORMS ruby DEPENDENCIES - activerecord-postgis-adapter (~> 9.0) ahoy_matey (~> 3.0) aws-sdk-cloudwatchlogs aws-sdk-kms (~> 1.4) @@ -773,7 +764,7 @@ DEPENDENCIES caxlsx concurrent-ruby connection_pool - cssbundling-rails + cssbundling-rails (= 1.0.0) csv derailed_benchmarks devise (~> 4.8) diff --git a/Makefile b/Makefile index 343b43d5e46..49789530fa4 100644 --- a/Makefile +++ b/Makefile @@ -19,10 +19,7 @@ ARTIFACT_DESTINATION_FILE ?= ./tmp/idp.tar.gz clobber_assets \ clobber_logs \ watch_events \ - docker_setup \ download_acuant_sdk \ - fast_setup \ - fast_test \ help \ lint \ lint_analytics_events \ @@ -59,12 +56,6 @@ all: check setup $(CONFIG): config/application.yml.default ## Runs setup scripts (updates packages, dependencies, databases, etc) bin/setup -fast_setup: ## Abbreviated setup script that skips linking some files - bin/fast_setup - -docker_setup: ## Setup script for Docker development - bin/docker_setup - check: lint test ## Runs lint tests and spec tests lint: ## Runs all lint tests @@ -126,7 +117,7 @@ lint_asset_bundle_size: ## Lints JavaScript and CSS compiled bundle size @# and you have no options to split that from the common bundles. If you need to increase this @# budget and accept the fact that this will force end-users to endure longer load times, you @# should set the new budget to within a few thousand bytes of the production-compiled size. - find app/assets/builds/application.css -size -220000c | grep . + find app/assets/builds/application.css -size -185000c | grep . find public/packs/js/application-*.digested.js -size -5000c | grep . lint_migrations: @@ -190,10 +181,6 @@ test_serial: export RAILS_ENV := test test_serial: $(CONFIG) ## Runs RSpec and yarn tests serially bundle exec rake spec && yarn test -fast_test: export RAILS_ENV := test -fast_test: ## Abbreviated test run, runs RSpec tests without accessibility specs - bundle exec rspec --exclude-pattern "**/features/accessibility/*_spec.rb" - tmp/$(HOST)-$(PORT).key tmp/$(HOST)-$(PORT).crt: ## Self-signed cert for local HTTPS development mkdir -p tmp openssl req \ diff --git a/README.md b/README.md index 65efb7b3290..4222b3b8cb3 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ Refer to the [_Local Development_ documentation](./docs/local-development.md) to ## Documentation - [Back-end Architecture](docs/backend.md) -- [Docker](docs/Docker.md) - [Front-end Architecture](docs/frontend.md) - [Local Development](docs/local-development.md) - [Mobile local development](docs/mobile.md) diff --git a/app/assets/stylesheets/_uswds.scss b/app/assets/stylesheets/_uswds.scss index 8e321e13293..94e4c5ca8be 100644 --- a/app/assets/stylesheets/_uswds.scss +++ b/app/assets/stylesheets/_uswds.scss @@ -6,8 +6,6 @@ @forward 'usa-alert'; @forward 'usa-banner'; @forward 'usa-button'; -@forward 'usa-form'; -@forward 'usa-header'; @forward 'usa-layout-grid'; @forward 'usa-link'; @forward 'usa-list'; diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index 8134b3f36c8..f77d6eccc9f 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -4,4 +4,3 @@ @forward 'design-system-waiting-room'; @forward 'components'; @forward 'utilities'; -@forward 'print'; diff --git a/app/assets/stylesheets/navigation.css.scss b/app/assets/stylesheets/navigation.css.scss index 4e3d62cf3f7..f69bdea2a85 100644 --- a/app/assets/stylesheets/navigation.css.scss +++ b/app/assets/stylesheets/navigation.css.scss @@ -1,5 +1,6 @@ @use 'uswds-core' as *; +@forward 'usa-header/src/styles/usa-header'; @forward 'usa-nav/src/styles'; @forward 'usa-sidenav/src/styles'; diff --git a/app/assets/stylesheets/print.css.scss b/app/assets/stylesheets/print.css.scss new file mode 100644 index 00000000000..0289871a271 --- /dev/null +++ b/app/assets/stylesheets/print.css.scss @@ -0,0 +1,7 @@ +nav, +footer, +.usa-button, +.usa-radio__input--bordered, +.usa-checkbox__input--bordered { + display: none; +} diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss deleted file mode 100644 index 8292aa80dea..00000000000 --- a/app/assets/stylesheets/print.scss +++ /dev/null @@ -1,9 +0,0 @@ -@media only print { - nav, - footer, - .usa-button, - .usa-radio__input--bordered, - .usa-checkbox__input--bordered { - display: none; - } -} diff --git a/app/components/phone_input_component.html.erb b/app/components/phone_input_component.html.erb index a6d5ce7ecf9..44790df86dc 100644 --- a/app/components/phone_input_component.html.erb +++ b/app/components/phone_input_component.html.erb @@ -4,7 +4,6 @@ data: { delivery_methods:, translated_country_code_names:, - captcha_exempt_countries:, }, ) do %> <%= content_tag( diff --git a/app/components/phone_input_component.rb b/app/components/phone_input_component.rb index 2de195bfefb..8b251b4139a 100644 --- a/app/components/phone_input_component.rb +++ b/app/components/phone_input_component.rb @@ -6,7 +6,6 @@ class PhoneInputComponent < BaseComponent :required, :allowed_countries, :delivery_methods, - :captcha_exempt_countries, :tag_options alias_method :f, :form @@ -17,7 +16,6 @@ def initialize( allowed_countries: nil, delivery_methods: [:sms, :voice], required: false, - captcha_exempt_countries: nil, **tag_options ) @allowed_countries = allowed_countries @@ -25,7 +23,6 @@ def initialize( @form = form @required = required @delivery_methods = delivery_methods - @captcha_exempt_countries = captcha_exempt_countries @tag_options = tag_options end diff --git a/app/controllers/concerns/two_factor_authenticatable_methods.rb b/app/controllers/concerns/two_factor_authenticatable_methods.rb index a7e37560ef0..6f0b9ecda47 100644 --- a/app/controllers/concerns/two_factor_authenticatable_methods.rb +++ b/app/controllers/concerns/two_factor_authenticatable_methods.rb @@ -10,6 +10,26 @@ def auth_methods_session @auth_methods_session ||= AuthMethodsSession.new(user_session:) end + def handle_valid_verification_for_authentication_context(auth_method:) + mark_user_session_authenticated(auth_method:, authentication_type: :valid_2fa) + disavowal_event, disavowal_token = create_user_event_with_disavowal(:sign_in_after_2fa) + + if IdentityConfig.store.feature_new_device_alert_aggregation_enabled && + user_session[:new_device] != false + if current_user.sign_in_new_device_at.blank? + current_user.update(sign_in_new_device_at: disavowal_event.created_at) + end + + UserAlerts::AlertUserAboutNewDevice.send_alert( + user: current_user, + disavowal_event:, + disavowal_token:, + ) + end + + reset_second_factor_attempts_count + end + private def authenticate_user @@ -163,13 +183,6 @@ def handle_valid_verification_for_confirmation_context(auth_method:) reset_second_factor_attempts_count end - def handle_valid_verification_for_authentication_context(auth_method:) - mark_user_session_authenticated(auth_method:, authentication_type: :valid_2fa) - create_user_event(:sign_in_after_2fa) - - reset_second_factor_attempts_count - end - def reset_second_factor_attempts_count UpdateUser.new(user: current_user, attributes: { second_factor_attempts_count: 0 }).call end diff --git a/app/controllers/idv/address_controller.rb b/app/controllers/idv/address_controller.rb index 3dd3d8f42f3..0df0196b98f 100644 --- a/app/controllers/idv/address_controller.rb +++ b/app/controllers/idv/address_controller.rb @@ -11,15 +11,15 @@ class AddressController < ApplicationController def new analytics.idv_address_visit - @address_form = Idv::AddressForm.new(idv_session.pii_from_doc) + @address_form = build_address_form @presenter = AddressPresenter.new end def update clear_future_steps! - @address_form = Idv::AddressForm.new(idv_session.pii_from_doc) + @address_form = build_address_form form_result = @address_form.submit(profile_params) - analytics.idv_address_submitted(**form_result.to_h) + track_submit_event(form_result) capture_address_edited(form_result) if form_result.success? success @@ -41,11 +41,24 @@ def self.step_info private - def idv_form - Idv::AddressForm.new(idv_session.pii_from_doc) + def build_address_form + Idv::AddressForm.new( + idv_session.updated_user_address || address_from_document, + ) + end + + def address_from_document + Pii::Address.new( + address1: idv_session.pii_from_doc[:address1], + address2: idv_session.pii_from_doc[:address2], + city: idv_session.pii_from_doc[:city], + state: idv_session.pii_from_doc[:state], + zipcode: idv_session.pii_from_doc[:zipcode], + ) end def success + idv_session.address_edited = address_edited? idv_session.pii_from_doc = idv_session.pii_from_doc.merge( address1: @address_form.address1, address2: @address_form.address2, @@ -53,7 +66,7 @@ def success state: @address_form.state, zipcode: @address_form.zipcode, ) - idv_session.updated_user_address = address_from_form + idv_session.updated_user_address = @address_form.updated_user_address redirect_to idv_verify_info_url end @@ -62,14 +75,13 @@ def failure render :new end - def address_from_form - Pii::Address.new( - address1: @address_form.address1, - address2: @address_form.address2, - city: @address_form.city, - state: @address_form.state, - zipcode: @address_form.zipcode, - ) + def track_submit_event(form_result) + address_edited = form_result.success? && address_edited? + analytics.idv_address_submitted(**form_result.to_h.merge(address_edited:)) + end + + def address_edited? + address_from_document != @address_form.updated_user_address end def profile_params diff --git a/app/controllers/idv/verify_info_controller.rb b/app/controllers/idv/verify_info_controller.rb index 744d895e392..4b1d438946d 100644 --- a/app/controllers/idv/verify_info_controller.rb +++ b/app/controllers/idv/verify_info_controller.rb @@ -79,7 +79,7 @@ def analytics_arguments end def pii - idv_session.pii_from_doc + idv_session.pii_from_doc.merge(idv_session.updated_user_address.to_h) end end end diff --git a/app/controllers/users/piv_cac_login_controller.rb b/app/controllers/users/piv_cac_login_controller.rb index 13fb27a9ad0..a4db6b9f510 100644 --- a/app/controllers/users/piv_cac_login_controller.rb +++ b/app/controllers/users/piv_cac_login_controller.rb @@ -74,6 +74,7 @@ def process_valid_submission presented: true, ) + user_session[:new_device] = current_user.new_device?(cookie_uuid: cookies[:device]) handle_valid_verification_for_authentication_context( auth_method: TwoFactorAuthenticatable::AuthMethod::PIV_CAC, ) diff --git a/app/controllers/users/reset_passwords_controller.rb b/app/controllers/users/reset_passwords_controller.rb index dfff6ab03ec..47b11696dc5 100644 --- a/app/controllers/users/reset_passwords_controller.rb +++ b/app/controllers/users/reset_passwords_controller.rb @@ -90,7 +90,12 @@ def request_id end def handle_valid_email - create_account_if_email_not_found + RequestPasswordReset.new( + email: email, + request_id: request_id, + analytics: analytics, + irs_attempts_api_tracker: irs_attempts_api_tracker, + ).perform session[:email] = email resend_confirmation = email_params[:resend] @@ -103,24 +108,6 @@ def store_token_in_session session[:reset_password_token] = params[:reset_password_token] end - def create_account_if_email_not_found - user, result = RequestPasswordReset.new( - email: email, - request_id: request_id, - analytics: analytics, - irs_attempts_api_tracker: irs_attempts_api_tracker, - ).perform - - return unless result - - analytics.user_registration_email(**result.to_h) - irs_attempts_api_tracker.user_registration_email_submitted( - email: email, - success: result.success?, - ) - create_user_event(:account_created, user) - end - def handle_invalid_or_expired_token(result) flash[:error] = t("devise.passwords.#{result.errors[:user].first}") session.delete(:reset_password_token) diff --git a/app/controllers/users/two_factor_authentication_controller.rb b/app/controllers/users/two_factor_authentication_controller.rb index bcdc8188813..640c62f071b 100644 --- a/app/controllers/users/two_factor_authentication_controller.rb +++ b/app/controllers/users/two_factor_authentication_controller.rb @@ -275,7 +275,6 @@ def short_term_otp_rate_limiter end def exceeded_short_term_otp_rate_limit? - return false unless IdentityConfig.store.short_term_phone_otp_rate_limiter_enabled short_term_otp_rate_limiter.increment! short_term_otp_rate_limiter.limited? end diff --git a/app/forms/idv/address_form.rb b/app/forms/idv/address_form.rb index 4fc800782c9..f0fc685c993 100644 --- a/app/forms/idv/address_form.rb +++ b/app/forms/idv/address_form.rb @@ -13,46 +13,41 @@ def self.model_name ActiveModel::Name.new(self, nil, 'IdvForm') end - def initialize(pii) - set_ivars_with_pii(pii) - @address_edited = false + def initialize(initial_address) + consume_attributes_from_address(initial_address.to_h) end def submit(params) - consume_params(params) + consume_attributes_from_address(params.to_h) FormResponse.new( success: valid?, errors: errors, extra: { - address_edited: @address_edited, pii_like_keypaths: [[:errors, :zipcode], [:error_details, :zipcode]], }, ) end - private - - def set_ivars_with_pii(pii) - pii = pii.symbolize_keys - @address1 = pii[:address1] - @address2 = pii[:address2] - @city = pii[:city] - @state = pii[:state] - @zipcode = pii[:zipcode] + def updated_user_address + Pii::Address.new( + address1: address1, + address2: address2, + city: city, + state: state, + zipcode: zipcode, + ) end - def consume_params(params) - ATTRIBUTES.each do |attribute_name| - if send(attribute_name).to_s != params[attribute_name].to_s - @address_edited = true - end - send(:"#{attribute_name}=", params[attribute_name].to_s) - end - end + private - def raise_invalid_address_parameter_error(key) - raise ArgumentError, "#{key} is an invalid address attribute" + def consume_attributes_from_address(address_hash) + address_hash = address_hash.symbolize_keys + @address1 = address_hash[:address1].to_s.strip + @address2 = address_hash[:address2].to_s.strip + @city = address_hash[:city].to_s.strip + @state = address_hash[:state].to_s.strip + @zipcode = address_hash[:zipcode].to_s.strip end end end diff --git a/app/forms/idv/phone_confirmation_otp_verification_form.rb b/app/forms/idv/phone_confirmation_otp_verification_form.rb index 892ee2bc538..026ae7b2344 100644 --- a/app/forms/idv/phone_confirmation_otp_verification_form.rb +++ b/app/forms/idv/phone_confirmation_otp_verification_form.rb @@ -48,6 +48,7 @@ def extra_analytics_attributes { code_expired: user_phone_confirmation_session.expired?, code_matches: user_phone_confirmation_session.matches_code?(code), + otp_delivery_preference: user_phone_confirmation_session.delivery_method, second_factor_attempts_count: user.second_factor_attempts_count, second_factor_locked_at: user.second_factor_locked_at, } diff --git a/app/forms/register_user_email_form.rb b/app/forms/register_user_email_form.rb index f712535b26c..e01c85f04b5 100644 --- a/app/forms/register_user_email_form.rb +++ b/app/forms/register_user_email_form.rb @@ -10,15 +10,13 @@ class RegisterUserEmailForm attr_reader :email_address, :terms_accepted attr_accessor :email_language - attr_accessor :password_reset_requested def self.model_name ActiveModel::Name.new(self, nil, 'User') end - def initialize(analytics:, attempts_tracker:, password_reset_requested: false) + def initialize(analytics:, attempts_tracker:) @rate_limited = false - @password_reset_requested = password_reset_requested @analytics = analytics @attempts_tracker = attempts_tracker end @@ -53,7 +51,7 @@ def validate_terms_accepted errors.add(:terms_accepted, t('errors.registration.terms'), type: :terms) end - def submit(params, instructions = nil) + def submit(params) @terms_accepted = !!ActiveModel::Type::Boolean.new.cast(params[:terms_accepted]) build_user_and_email_address_with_email( email: params[:email], @@ -62,7 +60,7 @@ def submit(params, instructions = nil) self.request_id = params[:request_id] self.success = valid? - process_successful_submission(request_id, instructions) if success + process_successful_submission(request_id) if success FormResponse.new(success: success, errors: errors, extra: extra_analytics_attributes) end @@ -72,10 +70,6 @@ def email_taken? @email_taken = lookup_email_taken end - def password_reset_requested? - @password_reset_requested - end - private attr_writer :email, :email_address @@ -98,7 +92,7 @@ def lookup_email_taken true end - def process_successful_submission(request_id, instructions) + def process_successful_submission(request_id) # To prevent discovery of existing emails, we check to see if the email is # already taken and if so, we act as if the user registration was successful. if email_address_record&.user&.suspended? @@ -111,7 +105,7 @@ def process_successful_submission(request_id, instructions) elsif email_taken? send_sign_up_confirmed_email else - send_sign_up_email(request_id, instructions) + send_sign_up_email(request_id) end end @@ -140,7 +134,7 @@ def rate_limit!(rate_limit_type) @rate_limited = rate_limiter.limited? end - def send_sign_up_email(request_id, instructions) + def send_sign_up_email(request_id) rate_limit!(:reg_unconfirmed_email) if rate_limited? @@ -154,11 +148,7 @@ def send_sign_up_email(request_id, instructions) user.accepted_terms_at = Time.zone.now user.save! - SendSignUpEmailConfirmation.new(user).call( - request_id: email_request_id(request_id), - instructions: instructions, - password_reset_requested: password_reset_requested?, - ) + SendSignUpEmailConfirmation.new(user).call(request_id: email_request_id(request_id)) end end diff --git a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts index 251469cda23..01e4231ed77 100644 --- a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts +++ b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts @@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event'; import { screen, waitFor, fireEvent } from '@testing-library/dom'; import { useSandbox, useDefineProperty } from '@18f/identity-test-helpers'; import '@18f/identity-spinner-button/spinner-button-element'; -import { CAPTCHA_EVENT_NAME } from './captcha-submit-button-element'; +import './captcha-submit-button-element'; describe('CaptchaSubmitButtonElement', () => { const sandbox = useSandbox(); @@ -112,29 +112,6 @@ describe('CaptchaSubmitButtonElement', () => { }); }); - context('with cancellation of challenge event', () => { - beforeEach(() => { - const form = document.querySelector('form')!; - form.addEventListener(CAPTCHA_EVENT_NAME, (event) => event.preventDefault()); - }); - - it('submits the form without challenge', async () => { - const button = screen.getByRole('button', { name: 'Submit' }); - const form = document.querySelector('form')!; - - let didSubmit = false; - form.addEventListener('submit', (event) => { - event.preventDefault(); - didSubmit = true; - }); - - await userEvent.click(button); - await waitFor(() => expect(didSubmit).to.be.true()); - - expect(grecaptcha.ready).not.to.have.been.called(); - }); - }); - context('with recaptcha enterprise', () => { beforeEach(() => { const element = document.querySelector('lg-captcha-submit-button')!; diff --git a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.ts b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.ts index 04e25df6290..c1dce46dab3 100644 --- a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.ts +++ b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.ts @@ -1,5 +1,3 @@ -export const CAPTCHA_EVENT_NAME = 'lg:captcha-submit-button:challenge'; - class CaptchaSubmitButtonElement extends HTMLElement { form: HTMLFormElement | null; @@ -55,13 +53,7 @@ class CaptchaSubmitButtonElement extends HTMLElement { } shouldInvokeChallenge(): boolean { - if (!this.recaptchaSiteKey) { - return false; - } - - const event = new CustomEvent(CAPTCHA_EVENT_NAME, { bubbles: true, cancelable: true }); - this.dispatchEvent(event); - return !event.defaultPrevented; + return !!this.recaptchaSiteKey; } handleFormSubmit = (event: SubmitEvent) => { diff --git a/app/javascript/packages/document-capture/components/unknown-error.tsx b/app/javascript/packages/document-capture/components/unknown-error.tsx index a20164083c6..b3063cc612e 100644 --- a/app/javascript/packages/document-capture/components/unknown-error.tsx +++ b/app/javascript/packages/document-capture/components/unknown-error.tsx @@ -83,9 +83,22 @@ function UnknownError({ ); } if ((isFailedSelfieLivenessOrQuality || isFailedSelfie) && err) { + let selfieHelpCenterLinkText = t('doc_auth.errors.general.selfie_failure_help_link_text'); + const helpCenterURL = new URL(helpCenterLink); + if (isFailedSelfieLivenessOrQuality) { + helpCenterURL.hash = 'how-to-add-a-photo-of-your-face-to-help-verify-your-id'; + selfieHelpCenterLinkText = t('doc_auth.errors.alerts.selfie_not_live_help_link_text'); + } return ( <> -

{err.message}

+

+ {err.message}{' '} + {altIsFailedSelfieDontIncludeAttempts && ( + + {selfieHelpCenterLinkText} + + )} +

{!altIsFailedSelfieDontIncludeAttempts && ( +

diff --git a/app/javascript/packages/phone-input/index.spec.ts b/app/javascript/packages/phone-input/index.spec.ts index 10d4c193a96..711c9629a16 100644 --- a/app/javascript/packages/phone-input/index.spec.ts +++ b/app/javascript/packages/phone-input/index.spec.ts @@ -1,10 +1,8 @@ import { getByLabelText, getByRole, getAllByRole } from '@testing-library/dom'; import userEvent from '@testing-library/user-event'; import { computeAccessibleName } from 'dom-accessibility-api'; -import type { SinonStub } from 'sinon'; import * as analytics from '@18f/identity-analytics'; import { useSandbox } from '@18f/identity-test-helpers'; -import { CAPTCHA_EVENT_NAME } from '@18f/identity-captcha-submit-button/captcha-submit-button-element'; const MULTIPLE_OPTIONS_HTML = `