diff --git a/.rubocop.yml b/.rubocop.yml index 7ac07b19819..b11c5735c6f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -28,7 +28,7 @@ AllCops: - 'vendor/**/*' - 'public/**/*' TargetRubyVersion: 3.2.0 - TargetRailsVersion: 7.0 + TargetRailsVersion: 7.1 UseCache: true DisabledByDefault: true SuggestExtensions: false @@ -786,6 +786,9 @@ Performance/StartWith: Performance/StringIdentifierArgument: Enabled: true +Performance/StringInclude: + Enabled: true + Performance/StringReplacement: Enabled: true @@ -1082,6 +1085,9 @@ Style/DefWithParentheses: Style/Dir: Enabled: true +Style/DirEmpty: + Enabled: true + Style/EachForSimpleLoop: Enabled: true @@ -1114,6 +1120,9 @@ Style/EndBlock: Style/EvalWithLocation: Enabled: true +Style/ExactRegexpMatch: + Enabled: true + Style/For: Enabled: true EnforcedStyle: each @@ -1274,6 +1283,21 @@ Style/RedundantFreeze: Style/RedundantInterpolation: Enabled: true +Style/RedundantHeredocDelimiterQuotes: + Enabled: true + +Style/RedundantLineContinuation: + Enabled: true + +Style/RedundantArrayConstructor: + Enabled: true + +Style/RedundantFilterChain: + Enabled: true + +Style/RedundantRegexpConstructor: + Enabled: true + Style/RedundantParentheses: Enabled: true diff --git a/Gemfile b/Gemfile index ec26b54fad6..6fa687cbcb2 100644 --- a/Gemfile +++ b/Gemfile @@ -3,9 +3,9 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" } ruby "~> #{File.read(File.join(__dir__, '.ruby-version')).strip}" -gem 'rails', '~> 7.0.0' +gem 'rails', '~> 7.1.0' -gem 'activerecord-postgis-adapter' +gem 'activerecord-postgis-adapter', '~> 9.0' gem 'ahoy_matey', '~> 3.0' gem 'aws-sdk-kms', '~> 1.4' gem 'aws-sdk-cloudwatchlogs', require: false @@ -110,7 +110,7 @@ group :development, :test do gem 'rspec', '~> 3.12.0' gem 'rspec-rails', '~> 6.0' gem 'rubocop', '~> 1.55.1', require: false - gem 'rubocop-performance', '~> 1.18.0', require: false + gem 'rubocop-performance', '~> 1.19.0', require: false gem 'rubocop-rails', '>= 2.5.2', require: false gem 'rubocop-rspec', require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 8e3e2ab218a..3f970defec7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -60,73 +60,81 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.0.7.2) - actionpack (= 7.0.7.2) - activesupport (= 7.0.7.2) + actioncable (7.1.1) + actionpack (= 7.1.1) + activesupport (= 7.1.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.7.2) - actionpack (= 7.0.7.2) - activejob (= 7.0.7.2) - activerecord (= 7.0.7.2) - activestorage (= 7.0.7.2) - activesupport (= 7.0.7.2) + zeitwerk (~> 2.6) + actionmailbox (7.1.1) + actionpack (= 7.1.1) + activejob (= 7.1.1) + activerecord (= 7.1.1) + activestorage (= 7.1.1) + activesupport (= 7.1.1) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.7.2) - actionpack (= 7.0.7.2) - actionview (= 7.0.7.2) - activejob (= 7.0.7.2) - activesupport (= 7.0.7.2) + actionmailer (7.1.1) + actionpack (= 7.1.1) + actionview (= 7.1.1) + activejob (= 7.1.1) + activesupport (= 7.1.1) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp - rails-dom-testing (~> 2.0) - actionpack (7.0.7.2) - actionview (= 7.0.7.2) - activesupport (= 7.0.7.2) - rack (~> 2.0, >= 2.2.4) + rails-dom-testing (~> 2.2) + actionpack (7.1.1) + actionview (= 7.1.1) + activesupport (= 7.1.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.7.2) - actionpack (= 7.0.7.2) - activerecord (= 7.0.7.2) - activestorage (= 7.0.7.2) - activesupport (= 7.0.7.2) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + actiontext (7.1.1) + actionpack (= 7.1.1) + activerecord (= 7.1.1) + activestorage (= 7.1.1) + activesupport (= 7.1.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.7.2) - activesupport (= 7.0.7.2) + actionview (7.1.1) + activesupport (= 7.1.1) builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.7.2) - activesupport (= 7.0.7.2) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (7.1.1) + activesupport (= 7.1.1) globalid (>= 0.3.6) - activemodel (7.0.7.2) - activesupport (= 7.0.7.2) - activerecord (7.0.7.2) - activemodel (= 7.0.7.2) - activesupport (= 7.0.7.2) - activerecord-postgis-adapter (8.0.2) - activerecord (~> 7.0.0) + activemodel (7.1.1) + activesupport (= 7.1.1) + activerecord (7.1.1) + activemodel (= 7.1.1) + activesupport (= 7.1.1) + timeout (>= 0.4.0) + activerecord-postgis-adapter (9.0.1) + activerecord (~> 7.1.0) rgeo-activerecord (~> 7.0.0) - activestorage (7.0.7.2) - actionpack (= 7.0.7.2) - activejob (= 7.0.7.2) - activerecord (= 7.0.7.2) - activesupport (= 7.0.7.2) + activestorage (7.1.1) + actionpack (= 7.1.1) + activejob (= 7.1.1) + activerecord (= 7.1.1) + activesupport (= 7.1.1) marcel (~> 1.0) - mini_mime (>= 1.1.0) - activesupport (7.0.7.2) + activesupport (7.1.1) + base64 + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) minitest (>= 5.1) + mutex_m tzinfo (~> 2.0) addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) @@ -185,7 +193,8 @@ GEM thread_safe (~> 0.3, >= 0.3.1) barby (0.6.8) base32-crockford (0.1.0) - bcrypt (3.1.16) + base64 (0.1.1) + bcrypt (3.1.19) benchmark-ips (2.12.0) better_errors (2.10.1) erubi (>= 1.0.0) @@ -198,13 +207,14 @@ GEM erubi (~> 1.4) parser (>= 2.4) smart_properties - bindata (2.4.14) - bootsnap (1.16.0) + bigdecimal (3.1.4) + bindata (2.4.15) + bootsnap (1.17.0) msgpack (~> 1.2) brakeman (6.0.1) browser (5.3.1) builder (3.2.4) - bullet (7.0.7) + bullet (7.1.0) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) bundler-audit (0.9.1) @@ -231,7 +241,7 @@ GEM coercible (1.0.0) descendants_tracker (~> 0.0.1) concurrent-ruby (1.2.2) - connection_pool (2.4.0) + connection_pool (2.4.1) cose (1.3.0) cbor (~> 0.5.9) openssl-signature_algorithm (~> 1.0) @@ -259,7 +269,7 @@ GEM descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) device_detector (1.0.7) - devise (4.8.1) + devise (4.9.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) @@ -270,6 +280,8 @@ GEM dotiw (5.3.2) activesupport i18n + drb (2.1.1) + ruby2_keywords dumb_delegator (1.0.0) email_spec (2.2.2) htmlentities (~> 4.3.3) @@ -319,7 +331,7 @@ GEM fugit (>= 1.1) railties (>= 6.0.0) thor (>= 0.14.1) - google-protobuf (3.24.0) + google-protobuf (3.24.4) hashdiff (1.0.1) hashie (4.1.0) heapy (0.2.0) @@ -343,8 +355,9 @@ GEM terminal-table (>= 1.5.1) ice_nine (0.11.2) io-console (0.6.0) - irb (1.7.4) - reline (>= 0.3.6) + irb (1.8.3) + rdoc + reline (>= 0.3.8) jmespath (1.6.2) jsbundling-rails (1.1.2) railties (>= 6.0.0) @@ -394,11 +407,12 @@ GEM method_source (1.0.0) mini_histogram (0.3.1) mini_mime (1.1.5) - mini_portile2 (2.8.4) + mini_portile2 (2.8.5) minitest (5.20.0) msgpack (1.7.2) multiset (0.5.3) - net-imap (0.3.7) + mutex_m (0.1.2) + net-imap (0.4.2) date net-protocol net-pop (0.1.2) @@ -407,10 +421,11 @@ GEM timeout net-sftp (3.0.0) net-ssh (>= 5.0.0, < 7.0.0) - net-smtp (0.3.3) + net-smtp (0.4.0) net-protocol net-ssh (6.1.0) - newrelic_rpm (9.5.0) + newrelic_rpm (9.6.0) + base64 nio4r (2.5.9) nokogiri (1.14.5) mini_portile2 (~> 2.8.0) @@ -420,10 +435,10 @@ GEM openssl (> 2.0, < 3.1) orm_adapter (0.5.0) parallel (1.23.0) - parser (3.2.2.3) + parser (3.2.2.4) ast (~> 2.4.1) racc - pg (1.5.3) + pg (1.5.4) pg_query (4.2.3) google-protobuf (>= 3.22.3) phonelib (0.8.4) @@ -442,7 +457,7 @@ GEM activesupport (>= 7.0.0) rack railties (>= 7.0.0) - pry (0.14.1) + pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) pry-byebug (3.10.1) @@ -458,7 +473,7 @@ GEM puma (5.6.7) nio4r (~> 2.0) raabro (1.4.0) - racc (1.7.1) + racc (1.7.2) rack (2.2.8) rack-attack (6.5.0) rack (>= 1.0, < 3) @@ -469,26 +484,31 @@ GEM rack (>= 1.2.0) rack-proxy (0.7.4) rack + rack-session (1.0.1) + rack (< 3) rack-test (2.1.0) rack (>= 1.3) rack-timeout (0.6.0) rack_session_access (0.2.0) builder (>= 2.0.0) rack (>= 1.0.0) - rails (7.0.7.2) - actioncable (= 7.0.7.2) - actionmailbox (= 7.0.7.2) - actionmailer (= 7.0.7.2) - actionpack (= 7.0.7.2) - actiontext (= 7.0.7.2) - actionview (= 7.0.7.2) - activejob (= 7.0.7.2) - activemodel (= 7.0.7.2) - activerecord (= 7.0.7.2) - activestorage (= 7.0.7.2) - activesupport (= 7.0.7.2) + rackup (1.0.0) + rack (< 3) + webrick + rails (7.1.1) + actioncable (= 7.1.1) + actionmailbox (= 7.1.1) + actionmailer (= 7.1.1) + actionpack (= 7.1.1) + actiontext (= 7.1.1) + actionview (= 7.1.1) + activejob (= 7.1.1) + activemodel (= 7.1.1) + activerecord (= 7.1.1) + activestorage (= 7.1.1) + activesupport (= 7.1.1) bundler (>= 1.15.0) - railties (= 7.0.7.2) + railties (= 7.1.1) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -503,26 +523,29 @@ GEM rails-i18n (7.0.6) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) - railties (7.0.7.2) - actionpack (= 7.0.7.2) - activesupport (= 7.0.7.2) - method_source + railties (7.1.1) + actionpack (= 7.1.1) + activesupport (= 7.1.1) + irb + rackup (>= 1.0.0) rake (>= 12.2) - thor (~> 1.0) - zeitwerk (~> 2.5) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.0.6) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) + rdoc (6.5.0) + psych (>= 4.0.0) redacted_struct (1.1.0) redcarpet (3.6.0) redis (5.0.6) redis-client (>= 0.9.0) redis-client (0.14.1) connection_pool - regexp_parser (2.8.1) - reline (0.3.7) + regexp_parser (2.8.2) + reline (0.3.9) io-console (~> 0.5) request_store (1.5.1) rack (>= 1.4) @@ -577,13 +600,13 @@ GEM rubocop-ast (>= 1.28.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.29.0) + rubocop-ast (1.30.0) parser (>= 3.2.1.0) rubocop-capybara (2.19.0) rubocop (~> 1.41) rubocop-factory_bot (2.24.0) rubocop (~> 1.33) - rubocop-performance (1.18.0) + rubocop-performance (1.19.1) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) rubocop-rails (2.20.2) @@ -615,7 +638,7 @@ GEM websocket (~> 1.0) shoulda-matchers (4.5.1) activesupport (>= 4.2.0) - simple_form (5.1.0) + simple_form (5.3.0) actionpack (>= 5.2) activemodel (>= 5.2) simple_xlsx_reader (5.0.0) @@ -634,13 +657,13 @@ GEM unf (~> 0.1.4) smart_properties (1.17.0) stringex (2.8.5) - strong_migrations (1.6.0) + strong_migrations (1.6.4) activerecord (>= 5.2) subprocess (1.5.5) tableparser (1.0.1) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) - thor (1.2.2) + thor (1.3.0) thread_safe (0.3.6) timeout (0.4.0) tpm-key_attestation (0.11.0) @@ -652,7 +675,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.8) - unicode-display_width (2.4.2) + unicode-display_width (2.5.0) uniform_notifier (1.16.0) valid_email (0.1.4) activemodel @@ -707,7 +730,7 @@ PLATFORMS ruby DEPENDENCIES - activerecord-postgis-adapter + activerecord-postgis-adapter (~> 9.0) ahoy_matey (~> 3.0) aws-sdk-cloudwatchlogs aws-sdk-kms (~> 1.4) @@ -783,7 +806,7 @@ DEPENDENCIES rack-test (>= 1.1.0) rack-timeout rack_session_access (>= 0.2.0) - rails (~> 7.0.0) + rails (~> 7.1.0) rails-controller-testing (>= 1.0.4) redacted_struct redis (>= 3.2.0) @@ -796,7 +819,7 @@ DEPENDENCIES rspec-retry rspec_junit_formatter rubocop (~> 1.55.1) - rubocop-performance (~> 1.18.0) + rubocop-performance (~> 1.19.0) rubocop-rails (>= 2.5.2) rubocop-rspec ruby-progressbar @@ -830,4 +853,4 @@ RUBY VERSION ruby 3.2.2p53 BUNDLED WITH - 2.4.4 + 2.4.20 diff --git a/Makefile b/Makefile index e521072f34d..98baee6f308 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ ARTIFACT_DESTINATION_FILE ?= ./tmp/idp.tar.gz clobber_db \ clobber_assets \ clobber_logs \ + watch_events \ docker_setup \ download_acuant_sdk \ fast_setup \ @@ -302,5 +303,8 @@ clobber_logs: ## Purges logs and tmp/ rm -rf tmp/letter_opener rm -rf tmp/mails +watch_events: ## Prints events logging as they happen + @tail -F -n0 log/events.log | jq "select(.name | test(\".*$$EVENT_NAME.*\"; \"i\")) | ." + tidy: clobber_assets clobber_logs ## Remove assets, logs, and unused gems, but leave DB alone bundle clean diff --git a/app/assets/images/alert/icon-lock-alert-important.svg b/app/assets/images/alert/icon-lock-alert-important.svg deleted file mode 100644 index 0a1190b3cd9..00000000000 --- a/app/assets/images/alert/icon-lock-alert-important.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/images/carat-right.svg b/app/assets/images/carat-right.svg deleted file mode 100644 index dc2a091c774..00000000000 --- a/app/assets/images/carat-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/images/idv/user-in-person.svg b/app/assets/images/idv/user-in-person.svg deleted file mode 100644 index a48daa7ebf7..00000000000 --- a/app/assets/images/idv/user-in-person.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/images/mfa-options/security-key-icon.svg b/app/assets/images/mfa-options/security-key-icon.svg deleted file mode 100644 index 57ba60b1150..00000000000 --- a/app/assets/images/mfa-options/security-key-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/images/minus.svg b/app/assets/images/minus.svg deleted file mode 100644 index cf773710826..00000000000 --- a/app/assets/images/minus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/images/personal-key/email.svg b/app/assets/images/personal-key/email.svg deleted file mode 100644 index a4ebfd7d445..00000000000 --- a/app/assets/images/personal-key/email.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/images/personal-key/warning.svg b/app/assets/images/personal-key/warning.svg deleted file mode 100644 index 4ec85cd21d7..00000000000 --- a/app/assets/images/personal-key/warning.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/images/plus.svg b/app/assets/images/plus.svg deleted file mode 100644 index 7d4cdc49e3c..00000000000 --- a/app/assets/images/plus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/images/scissors.svg b/app/assets/images/scissors.svg deleted file mode 100644 index b97f9e527e1..00000000000 --- a/app/assets/images/scissors.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/images/sign-in.svg b/app/assets/images/sign-in.svg deleted file mode 100644 index a7211e5d3f4..00000000000 --- a/app/assets/images/sign-in.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/images/user.svg b/app/assets/images/user.svg deleted file mode 100644 index 6074e80aeda..00000000000 --- a/app/assets/images/user.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/images/verified.svg b/app/assets/images/verified.svg deleted file mode 100644 index ee412c8f683..00000000000 --- a/app/assets/images/verified.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/images/webauthn-verified.svg b/app/assets/images/webauthn-verified.svg deleted file mode 100644 index 0c186a7827f..00000000000 --- a/app/assets/images/webauthn-verified.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/controllers/frontend_log_controller.rb b/app/controllers/frontend_log_controller.rb index 16e75a8d853..430ac2d05ba 100644 --- a/app/controllers/frontend_log_controller.rb +++ b/app/controllers/frontend_log_controller.rb @@ -12,8 +12,20 @@ class FrontendLogController < ApplicationController # rubocop:disable Layout/LineLength EVENT_MAP = { 'Frontend Error' => FrontendErrorLogger.method(:track_error), + 'IdV: Acuant SDK loaded' => :idv_acuant_sdk_loaded, + 'IdV: back image added' => :idv_back_image_added, + 'IdV: back image clicked' => :idv_back_image_clicked, + 'IdV: barcode warning continue clicked' => :idv_barcode_warning_continue_clicked, + 'IdV: barcode warning retake photos clicked' => :idv_barcode_warning_retake_photos_clicked, + 'IdV: Capture troubleshooting dismissed' => :idv_capture_troubleshooting_dismissed, 'IdV: consent checkbox toggled' => :idv_consent_checkbox_toggled, 'IdV: download personal key' => :idv_personal_key_downloaded, + 'IdV: exit optional questions' => :idv_exit_optional_questions, + 'IdV: front image added' => :idv_front_image_added, + 'IdV: front image clicked' => :idv_front_image_clicked, + 'IdV: Image capture failed' => :idv_image_capture_failed, + 'IdV: Link sent capture doc polling complete' => :idv_link_sent_capture_doc_polling_complete, + 'IdV: Link sent capture doc polling started' => :idv_link_sent_capture_doc_polling_started, 'IdV: location submitted' => :idv_in_person_location_submitted, 'IdV: location visited' => :idv_in_person_location_visited, 'IdV: Mobile device and camera check' => :idv_mobile_device_and_camera_check, @@ -26,6 +38,8 @@ class FrontendLogController < ApplicationController 'IdV: user clicked sp link on ready to verify page' => :idv_in_person_ready_to_verify_sp_link_clicked, 'IdV: user clicked what to bring link on ready to verify page' => :idv_in_person_ready_to_verify_what_to_bring_link_clicked, 'IdV: verify in person troubleshooting option clicked' => :idv_verify_in_person_troubleshooting_option_clicked, + 'IdV: warning action triggered' => :idv_warning_action_triggered, + 'IdV: warning shown' => :idv_warning_shown, 'Multi-Factor Authentication: download backup code' => :multi_factor_auth_backup_code_download, 'User prompted before navigation' => :user_prompted_before_navigation, 'User prompted before navigation and still on page' => :user_prompted_before_navigation_and_still_on_page, @@ -33,9 +47,14 @@ class FrontendLogController < ApplicationController # rubocop:enable Layout/LineLength def create - frontend_logger.track_event(log_params[:event], log_params[:payload].to_h) + result = frontend_logger.track_event(log_params[:event], log_params[:payload].to_h) - render json: { success: true }, status: :ok + if result + render json: { success: true }, status: :ok + else + render json: { success: false, error_message: 'invalid event' }, + status: :bad_request + end end private diff --git a/app/controllers/sign_up/completions_controller.rb b/app/controllers/sign_up/completions_controller.rb index 8b510e33235..5580063d6e3 100644 --- a/app/controllers/sign_up/completions_controller.rb +++ b/app/controllers/sign_up/completions_controller.rb @@ -99,8 +99,7 @@ def track_completion_event(last_page) end def pii - pii_string = Pii::Cacher.new(current_user, user_session).fetch_string - JSON.parse(pii_string || '{}', symbolize_names: true) + Pii::Cacher.new(current_user, user_session).fetch || Pii::Attributes.new end def send_in_person_completion_survey diff --git a/app/forms/idv/api_image_upload_form.rb b/app/forms/idv/api_image_upload_form.rb index 3e3470a4c70..bb201d2aec8 100644 --- a/app/forms/idv/api_image_upload_form.rb +++ b/app/forms/idv/api_image_upload_form.rb @@ -139,7 +139,7 @@ def extra_attributes attempts: attempts, remaining_attempts: remaining_attempts, user_id: user_uuid, - pii_like_keypaths: [[:pii]], + pii_like_keypaths: DocPiiForm.pii_like_keypaths, flow_path: params[:flow_path], } diff --git a/app/forms/idv/doc_pii_form.rb b/app/forms/idv/doc_pii_form.rb index 2c340884b65..3616d2a8b94 100644 --- a/app/forms/idv/doc_pii_form.rb +++ b/app/forms/idv/doc_pii_form.rb @@ -2,7 +2,20 @@ module Idv class DocPiiForm include ActiveModel::Model - validate :validate_pii + validate :name_valid? + validate :dob_valid? + validates_presence_of :address1, { message: proc { + I18n.t('doc_auth.errors.alerts.address_check') + } } + validates_length_of :state, { is: 2, + message: proc { + I18n.t('doc_auth.errors.general.no_liveness') + } } + validate :zipcode_valid? + validates :jurisdiction, inclusion: { in: Idp::Constants::STATE_AND_TERRITORY_CODES, + message: proc { + I18n.t('doc_auth.errors.general.no_liveness') + } } attr_reader :first_name, :last_name, :dob, :address1, :state, :zipcode, :attention_with_barcode, :jurisdiction @@ -25,7 +38,7 @@ def submit success: valid?, errors: errors, extra: { - pii_like_keypaths: [[:pii]], # see errors.add(:pii) + pii_like_keypaths: self.class.pii_like_keypaths, attention_with_barcode: attention_with_barcode?, }, ) @@ -33,60 +46,46 @@ def submit response end - private - - attr_reader :pii_from_doc - - def validate_pii - if error_count > 1 - errors.add(:pii, generic_error, type: :generic_error) - elsif !name_valid? - errors.add(:pii, name_error, type: :name_error) - elsif dob.blank? - errors.add(:pii, dob_error, type: :dob_error) - elsif !dob_meets_min_age? - errors.add(:pii, dob_min_age_error, type: :dob_min_age_error) - elsif address1.blank? - errors.add(:pii, address_error, type: :address_error) - elsif !state_valid? - errors.add(:pii, generic_error, type: :generic_error) - elsif !zipcode_valid? - errors.add(:pii, generic_error, type: :generic_error) - elsif !jurisdiction_valid? - errors.add(:pii, generic_error, type: :generic_error) + def self.pii_like_keypaths + keypaths = [[:pii]] + attrs = %i[name dob dob_min_age address1 state zipcode jurisdiction] + keypaths << attrs + attrs.each do |k| + keypaths << [:errors, k] + keypaths << [:error_details, k] end + keypaths end - def jurisdiction_valid? - Idp::Constants::STATE_AND_TERRITORY_CODES.include? jurisdiction - end + private + + attr_reader :pii_from_doc def name_valid? - first_name.present? && last_name.present? + return if first_name.present? && last_name.present? + + errors.add(:name, name_error, type: :name) end def dob_valid? - dob.present? && dob_meets_min_age? - end + if dob.blank? + errors.add(:dob, dob_error, type: :dob) + return + end - def dob_meets_min_age? dob_date = DateParser.parse_legacy(dob) today = Time.zone.today age = today.year - dob_date.year - ((today.month > dob_date.month || (today.month == dob_date.month && today.day >= dob_date.day)) ? 0 : 1) - age >= IdentityConfig.store.idv_min_age_years - end - - def state_valid? - state.present? && state.length == 2 - end - - def error_count - [name_valid?, dob_valid?, state_valid?].count(&:blank?) + if age < IdentityConfig.store.idv_min_age_years + errors.add(:dob_min_age, dob_min_age_error, type: :dob) + end end def zipcode_valid? - zipcode.is_a?(String) && zipcode.present? + return if zipcode.is_a?(String) && zipcode.present? + + errors.add(:zipcode, generic_error, type: :zipcode) end def generic_error @@ -104,9 +103,5 @@ def dob_error def dob_min_age_error I18n.t('doc_auth.errors.pii.birth_date_min_age') end - - def address_error - I18n.t('doc_auth.errors.alerts.address_check') - end end end diff --git a/app/forms/openid_connect_authorize_form.rb b/app/forms/openid_connect_authorize_form.rb index a3e65e9ab3d..6b02a028c50 100644 --- a/app/forms/openid_connect_authorize_form.rb +++ b/app/forms/openid_connect_authorize_form.rb @@ -100,7 +100,7 @@ def success_redirect_uri end def ial_values - acr_values.filter { |acr| %r{/ial/}.match?(acr) || %r{/loa/}.match?(acr) } + acr_values.filter { |acr| acr.include?('ial') || acr.include?('loa') } end def ial_context @@ -112,7 +112,7 @@ def ial end def aal_values - acr_values.filter { |acr| %r{/aal/}.match? acr } + acr_values.filter { |acr| acr.include?('aal') } end def aal diff --git a/app/forms/otp_verification_form.rb b/app/forms/otp_verification_form.rb index 88ad6f34a80..8a5a5efd490 100644 --- a/app/forms/otp_verification_form.rb +++ b/app/forms/otp_verification_form.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + class OtpVerificationForm include ActiveModel::Model - validates :code, presence: true - validate :validate_code_length + CODE_REGEX = /\A[0-9]+\z/ + validates :code, presence: true, length: { is: TwoFactorAuthenticatable::DIRECT_OTP_LENGTH } validate :validate_code_matches_format validate :validate_user_otp_presence validate :validate_user_otp_expiration @@ -31,31 +33,26 @@ def submit attr_reader :code, :user, :phone_configuration - def validate_code_length - return if code.blank? || code.size == TwoFactorAuthenticatable::DIRECT_OTP_LENGTH - errors.add(:code, :incorrect_length, type: :incorrect_length) - end - def validate_code_matches_format - return if code.blank? || code.match?(/^[0-9]+/i) - errors.add(:code, :pattern_mismatch, type: :pattern_mismatch) + return if code.blank? || code.match?(CODE_REGEX) + errors.add(:code, 'pattern_mismatch', type: :pattern_mismatch) end def validate_user_otp_presence return if user.direct_otp.present? - errors.add(:code, :user_otp_missing, type: :user_otp_missing) + errors.add(:code, 'user_otp_missing', type: :user_otp_missing) end def validate_user_otp_expiration return if !otp_expired? - errors.add(:code, :user_otp_expired, type: :user_otp_expired) + errors.add(:code, 'user_otp_expired', type: :user_otp_expired) end def validate_code_equals_user_otp return if code.blank? || user.direct_otp.blank? || ActiveSupport::SecurityUtils.secure_compare(user.direct_otp, code) - errors.add(:code, :incorrect, type: :incorrect) + errors.add(:code, 'incorrect', type: :incorrect) end def otp_expired? diff --git a/app/forms/webauthn_verification_form.rb b/app/forms/webauthn_verification_form.rb index 70a6a49b9e5..fa51beca58a 100644 --- a/app/forms/webauthn_verification_form.rb +++ b/app/forms/webauthn_verification_form.rb @@ -64,7 +64,7 @@ def self.domain_name def validate_assertion_response return if webauthn_error.present? || webauthn_configuration.blank? || valid_assertion_response? - errors.add(:authenticator_data, :invalid_authenticator_data, type: :invalid_authenticator_data) + errors.add(:authenticator_data, 'invalid_authenticator_data', type: :invalid_authenticator_data) end def validate_webauthn_error diff --git a/app/javascript/packages/components/icon-list/icon-list-content.spec.tsx b/app/javascript/packages/components/icon-list/icon-list-content.spec.tsx deleted file mode 100644 index 094c90d493b..00000000000 --- a/app/javascript/packages/components/icon-list/icon-list-content.spec.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { render } from '@testing-library/react'; -import IconListContent from './icon-list-content'; - -describe('IconListContent', () => { - it('renders the component with expected class and children', () => { - const { getByText } = render( - -
Example
-
, - ); - - const child = getByText('Example'); - const item = child.parentElement!; - - expect(item.classList.contains('usa-icon-list__content')).to.be.true(); - expect(item.textContent).to.equal('Example'); - }); -}); diff --git a/app/javascript/packages/components/icon-list/icon-list-content.tsx b/app/javascript/packages/components/icon-list/icon-list-content.tsx deleted file mode 100644 index 9cd309753c2..00000000000 --- a/app/javascript/packages/components/icon-list/icon-list-content.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { ReactNode } from 'react'; - -interface IconListContentProps { - children?: ReactNode; -} - -function IconListContent({ children }: IconListContentProps) { - const classes = 'usa-icon-list__content'; - - return
{children}
; -} - -export default IconListContent; diff --git a/app/javascript/packages/components/icon-list/icon-list-icon.spec.tsx b/app/javascript/packages/components/icon-list/icon-list-icon.spec.tsx deleted file mode 100644 index f7e15ca507d..00000000000 --- a/app/javascript/packages/components/icon-list/icon-list-icon.spec.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { render } from '@testing-library/react'; -import IconListIcon from './icon-list-icon'; - -describe('IconListIcon', () => { - it('renders the component with expected class and children', () => { - const { getByText } = render( - -
Example
-
, - ); - - const child = getByText('Example'); - const item = child.parentElement!; - - expect(item.classList.contains('usa-icon-list__icon')).to.be.true(); - expect(item.classList.contains('example-class')).to.be.true(); - expect(item.textContent).to.equal('Example'); - }); -}); diff --git a/app/javascript/packages/components/icon-list/icon-list-icon.tsx b/app/javascript/packages/components/icon-list/icon-list-icon.tsx deleted file mode 100644 index e3a562ccf15..00000000000 --- a/app/javascript/packages/components/icon-list/icon-list-icon.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import type { ReactNode } from 'react'; - -interface IconListIconProps { - children?: ReactNode; - - className?: string; -} - -function IconListIcon({ children, className }: IconListIconProps) { - const classes = ['usa-icon-list__icon', 'text-primary-dark', className].filter(Boolean).join(' '); - - return
{children}
; -} - -export default IconListIcon; diff --git a/app/javascript/packages/components/icon-list/icon-list-item.spec.tsx b/app/javascript/packages/components/icon-list/icon-list-item.spec.tsx deleted file mode 100644 index 1db47eaba61..00000000000 --- a/app/javascript/packages/components/icon-list/icon-list-item.spec.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { render } from '@testing-library/react'; -import IconListItem from './icon-list-item'; - -describe('IconListItem', () => { - it('renders the component with expected class and children', () => { - const { container } = render( - -
Example
-
, - ); - - const wrapper = container.firstElementChild!; - expect(wrapper.classList.contains('usa-icon-list__item')).to.be.true(); - - const iconListIcon = wrapper.firstElementChild!; - expect(iconListIcon.classList.contains('usa-icon-list__icon')).to.be.true(); - const icon = iconListIcon.firstElementChild!; - expect(icon.classList.contains('usa-icon')).to.be.true(); - const iconListContent = iconListIcon.nextElementSibling!; - expect(iconListContent.classList.contains('usa-icon-list__content')).to.be.true(); - const iconListTitle = iconListContent.firstElementChild!; - expect(iconListTitle.classList.contains('usa-icon-list__title')).to.be.true(); - expect(iconListTitle.textContent).to.equal('Example title'); - const child = iconListTitle.nextElementSibling!; - expect(child.textContent).to.equal('Example'); - }); -}); diff --git a/app/javascript/packages/components/icon-list/icon-list-item.tsx b/app/javascript/packages/components/icon-list/icon-list-item.tsx deleted file mode 100644 index 0de2b011703..00000000000 --- a/app/javascript/packages/components/icon-list/icon-list-item.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Icon, IconListContent, IconListIcon, IconListTitle } from '@18f/identity-components'; -import type { ReactNode } from 'react'; -import type { DesignSystemIcon } from '../icon'; - -interface IconListItemProps { - children?: ReactNode; - - icon: DesignSystemIcon; - - title: string; -} - -function IconListItem({ children, icon, title }: IconListItemProps) { - const classes = 'usa-icon-list__item'; - - return ( -
  • - - - - - {title} - {children} - -
  • - ); -} - -export default IconListItem; diff --git a/app/javascript/packages/components/icon-list/icon-list-title.spec.tsx b/app/javascript/packages/components/icon-list/icon-list-title.spec.tsx deleted file mode 100644 index 1f29fb8bb73..00000000000 --- a/app/javascript/packages/components/icon-list/icon-list-title.spec.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { render } from '@testing-library/react'; -import IconListTitle from './icon-list-title'; - -describe('IconListTitle', () => { - it('renders the component with expected class and children', () => { - const { getByText } = render( - -
    Example
    -
    , - ); - - const child = getByText('Example'); - const item = child.parentElement!; - - expect(item.classList.contains('usa-icon-list__title')).to.be.true(); - expect(item.textContent).to.equal('Example'); - }); -}); diff --git a/app/javascript/packages/components/icon-list/icon-list-title.tsx b/app/javascript/packages/components/icon-list/icon-list-title.tsx deleted file mode 100644 index d8dc6a417d5..00000000000 --- a/app/javascript/packages/components/icon-list/icon-list-title.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import type { ReactNode } from 'react'; - -interface IconListTitleProps { - children?: ReactNode; - - className?: string; -} - -function IconListTitle({ children, className }: IconListTitleProps) { - const classes = ['usa-icon-list__title', 'font-sans-md', 'padding-top-0', className] - .filter(Boolean) - .join(' '); - - return

    {children}

    ; -} - -export default IconListTitle; diff --git a/app/javascript/packages/components/icon-list/icon-list.spec.tsx b/app/javascript/packages/components/icon-list/icon-list.spec.tsx deleted file mode 100644 index 9ff6b6eea45..00000000000 --- a/app/javascript/packages/components/icon-list/icon-list.spec.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { render } from '@testing-library/react'; -import IconList from './icon-list'; - -describe('IconList', () => { - it('renders the component with expected class and children', () => { - const { getByText } = render( - -
    Example
    -
    , - ); - - const child = getByText('Example'); - const item = child.parentElement!; - - expect(item.classList.contains('usa-icon-list')).to.be.true(); - expect(item.textContent).to.equal('Example'); - }); -}); diff --git a/app/javascript/packages/components/icon-list/icon-list.tsx b/app/javascript/packages/components/icon-list/icon-list.tsx deleted file mode 100644 index b17ce1a1677..00000000000 --- a/app/javascript/packages/components/icon-list/icon-list.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { ReactNode } from 'react'; - -interface IconListProps { - children?: ReactNode; -} - -function IconList({ children }: IconListProps) { - const classes = 'usa-icon-list'; - - return ; -} - -export default IconList; diff --git a/app/javascript/packages/components/index.ts b/app/javascript/packages/components/index.ts index 5888c91f13f..d0d1fe2238b 100644 --- a/app/javascript/packages/components/index.ts +++ b/app/javascript/packages/components/index.ts @@ -3,11 +3,6 @@ export { default as BlockLink } from './block-link'; export { default as Button } from './button'; export { default as FullScreen } from './full-screen'; export { default as Icon } from './icon'; -export { default as IconList } from './icon-list/icon-list'; -export { default as IconListContent } from './icon-list/icon-list-content'; -export { default as IconListIcon } from './icon-list/icon-list-icon'; -export { default as IconListItem } from './icon-list/icon-list-item'; -export { default as IconListTitle } from './icon-list/icon-list-title'; export { default as Link } from './link'; export { default as PageFooter } from './page-footer'; export { default as PageHeading } from './page-heading'; diff --git a/app/javascript/packages/phone-input/index.spec.ts b/app/javascript/packages/phone-input/index.spec.ts index a49ee0bc557..30102cb61f6 100644 --- a/app/javascript/packages/phone-input/index.spec.ts +++ b/app/javascript/packages/phone-input/index.spec.ts @@ -64,7 +64,6 @@ describe('PhoneInput', () => { phoneInput.setAttribute('aria-invalid', 'false'); phoneInput.setAttribute('aria-describedby', 'validated-field-error-298658fb'); phoneInput.setAttribute('required', 'required'); - phoneInput.setAttribute('aria-required', 'true'); element.innerHTML = `