From db2da61d1f2ceae7541b91b6fd57ed89a83005e9 Mon Sep 17 00:00:00 2001 From: Jessica Dembe Date: Thu, 12 Oct 2023 13:26:00 -0400 Subject: [PATCH] Deploy RC 322 to Prod (#9369) * LG-11082 Add Conditional Text To FullAddressSearch Component (#9331) * Add conditional text to view * add new tests * Add period to display text * package version increase from 3.1.0 to 3.1.1 * fix linter errors * changelog: Upcoming feature, USPS Full Address Search, Added conditional logic to display/hide text on the Find a participating Post Office view that will display in Help Center only * Integrate personal key feature specs into end_to_end_idv feature specs (#9336) Since feature specs run slowly, it's better to check assertions as part of a single longer spec rather than restart identity verification over and over. This removes several long-running feature specs from the test suite. [skip changelog] * Update specs to initialize session as HashWithIndifferentAccess (#9347) changelog: Internal, Automated Testing, Improve accuracy of session stubbing in tests * Change `` tags to `` for better accessibility and code consistency (#9349) * Change `` tags to `` for better accessibility and code consistency changelog: User-facing Improvements, Accessibility, Use strong html tag instead of b for emphasis * Enable RSpec/LeakyConstantDeclaration rubocop (#9348) * Enable RSpec/LeakyConstantDeclaration rubocop changelog: Internal, Source code, Enable RSpec rubocop * Use let instead of defining new class --------- Co-authored-by: Mitchell Henke * Sync TypeScript-ESLint versions (#9352) changelog: Internal, Dependencies, Update dependencies to their latest versions * LG-10037: display warning banner on gpo welcome back page if number of gpo letter requests exceeded (#9303) * display warning banner on gpo welcome back page if gpo letter requests are spammed changelog: User-Facing Improvements, Identity Verification, display warning banner if user has sent max letter requests within a time window * handle if user has no gpo confirmatio codes * Update app/views/idv/by_mail/enter_code/index.html.erb Co-authored-by: Andrew Duthie * tests for alert banner for spammed gpo requesets * happy linting * fix extra space in alert_spam_warning_html i18n * happy linting * lintfix i18n * lint line too long * js tag removal from alert gpo spam banner spec * integrate warning alert banner for spammed gpo letter requests into existing tests * refactor test for gpo spam warning banner * happy linting * create before action to remove test order dependency * happy linting * define gpo_verification_enabled in review app * define gpo_verification_enabled in review app --------- Co-authored-by: Andrew Duthie * Upgrade to Rails 7.1 (#9333) * fix otp missing translations * rails 7.1 changelog: Internal, Dependencies, Upgrade to Rails 7.1 * fix untranslated webauthn verification * LG-10837: Add New Piv Cac Logging for login visited (#9294) * changelog: Internal Fixes, Authentication LG-10837: Piv Cac Logging fixes * changelog: Internal, Authentication, Add Login visited for pivcac/change logging names to be uniform * uniform spec test * fix naming convention for piv cac * update rspec * add previous name * Add lint check for reasonable asset bundle sizes (#9353) * Add lint check for reasonable asset bundle sizes changelog: Internal, Automated Testing, Add test for reasonable asset bundle size * TEMPORARY: Revert "Fix JavaScript dead code elimination (#9217)" This reverts commit 0fcc3a73e329c36335e4b85f4bb98566e5f69995. * Revert "TEMPORARY: Revert "Fix JavaScript dead code elimination (#9217)"" This reverts commit af166f2c96a602ca5a109597225431e29d7f1aab. * Update changelog script to reflect non-security Dependabot usage (#9354) changelog: Internal, Changelog, Update changelog script to reflect non-security Dependabot usage * Revert "Upgrade to Rails 7.1 (#9333)" (#9356) This reverts commit f9a0cd054a87d86101465a14f8e59bd3a8b77b25. * LG-10812 | Report on all-time user count (#9350) changelog: Internal, Reporting, Monthly report includes all-time user count * Reorganize combined invoice report for easier manual runs (#9358) changelog: Internal, Reporting, Reorganize combined-invoice-supplement-report * Exclude 'IRS Attempt API: Event metadata' events from log results (#9360) changelog: Internal, Data Requests, Exclude 'IRS Attempt API: Event metadata' events from log results * Remove Guardfile, guard dependencies (#9364) changelog: Internal, Dependencies, Remove unused testing dependencies * LG-11066 Do not redirect users at the phone step unless they are phone and address rate limited (#9345) Users are being rate limited and encounting the phone error screen even if they can still verify by mail. This commit changes the rate limit logic to allow users to proceed to the phone step if they can still verify their phone or complete verification by mail. A side-effect of this change is a bug is fixed where the following situation would exist: 1. A user proofed by mail after exhausting phone attempts 2. The user goes to GPO entry and chooses to cancel and start over 3. The user is redirected to the welcome step to start over 4. The welcome step before action observes the user is phone rate limited and sends the user to the phone errors controller 5. The phone errors controller has a before action to confirm the user has completed the phone errors step; the user has not since in this session so they are redirected to the welcome step 6. Steps 4 and 5 complete until there are too many redirects [skip changelog] Co-authored-by: Sonia Connolly --------- Co-authored-by: gina-yamada <125507397+gina-yamada@users.noreply.github.com> Co-authored-by: Sonia Connolly Co-authored-by: Andrew Duthie Co-authored-by: Zach Margolis Co-authored-by: Mitchell Henke Co-authored-by: Amir Reavis-Bey Co-authored-by: Malick Diarra Co-authored-by: Matt Wagner Co-authored-by: Jonathan Hooper --- .gitlab-ci.yml | 1 + .rubocop.yml | 4 + Gemfile | 3 +- Gemfile.lock | 32 +-- Guardfile | 63 ------ Makefile | 5 + .../concerns/rate_limit_concern.rb | 43 ++-- .../idv/by_mail/enter_code_controller.rb | 9 +- app/controllers/idv/phone_controller.rb | 4 +- .../idv/phone_errors_controller.rb | 4 +- ...piv_cac_authentication_setup_controller.rb | 2 +- .../users/piv_cac_login_controller.rb | 2 +- .../piv_cac_setup_from_sign_in_controller.rb | 2 +- .../components/full-address-search.spec.tsx | 45 ++++ .../components/full-address-search.tsx | 4 +- .../packages/address-search/package.json | 2 +- .../combined_invoice_supplement_report.rb | 12 +- .../reports/monthly_key_metrics_report.rb | 12 ++ app/services/analytics_events.rb | 21 +- .../reporting/total_user_count_report.rb | 24 +++ .../idv/by_mail/enter_code/index.html.erb | 16 +- config/application.yml.default.docker | 1 + config/locales/help_text/en.yml | 16 +- config/locales/help_text/es.yml | 19 +- config/locales/help_text/fr.yml | 20 +- config/locales/idv/en.yml | 7 +- config/locales/idv/es.yml | 8 +- config/locales/idv/fr.yml | 9 +- config/locales/in_person_proofing/en.yml | 2 +- config/locales/in_person_proofing/es.yml | 2 +- .../locales/two_factor_authentication/en.yml | 4 +- .../locales/two_factor_authentication/es.yml | 6 +- .../locales/two_factor_authentication/fr.yml | 6 +- config/locales/user_mailer/en.yml | 4 +- config/locales/user_mailer/es.yml | 8 +- config/locales/user_mailer/fr.yml | 8 +- config/service_providers.localdev.yml | 6 +- docs/local-development.md | 19 -- .../local/fetch_cloudwatch_logs.rb | 2 +- package.json | 4 +- scripts/changelog_check.rb | 2 +- spec/components/base_component_spec.rb | 14 ++ spec/components/block_link_component_spec.rb | 2 + .../idv/ab_test_analytics_concern_spec.rb | 11 +- .../concerns/idv_step_concern_spec.rb | 38 ++-- .../concerns/rate_limit_concern_spec.rb | 107 +++++++--- .../idv/personal_key_controller_spec.rb | 8 +- .../idv/phone_errors_controller_spec.rb | 55 +++-- .../controllers/idv/review_controller_spec.rb | 3 - spec/controllers/idv_controller_spec.rb | 43 +++- .../piv_cac_verification_controller_spec.rb | 4 +- ...ac_authentication_setup_controller_spec.rb | 2 +- .../users/piv_cac_login_controller_spec.rb | 11 +- .../service_provider_session_spec.rb | 2 +- spec/factories/profiles.rb | 6 + spec/factories/service_providers.rb | 8 +- spec/features/idv/end_to_end_idv_spec.rb | 22 ++ spec/features/idv/phone_errors_spec.rb | 4 +- .../idv/proof_address_rate_limit_spec.rb | 92 ++++++++ .../idv/steps/confirmation_step_spec.rb | 71 ------- .../steps/gpo_otp_verification_step_spec.rb | 132 ++++++++---- .../remember_device/sp_expiration_spec.rb | 30 +-- spec/fixtures/git_log_changelog.yml | 15 ++ .../monthly_key_metrics_report_spec.rb | 35 +-- spec/lib/identity_job_log_subscriber_spec.rb | 6 +- .../lib/telephony/pinpoint/sms_sender_spec.rb | 2 +- .../selection_presenter_spec.rb | 16 +- .../set_up_selection_presenter_spec.rb | 12 +- .../sign_in_selection_presenter_spec.rb | 10 +- spec/routing/gpo_verification_routing_spec.rb | 36 ++-- spec/scripts/changelog_check_spec.rb | 2 +- spec/services/doc_auth/acuant/request_spec.rb | 12 +- spec/services/frontend_logger_spec.rb | 12 +- .../reporting/total_user_count_report_spec.rb | 73 +++++++ .../services/service_provider_updater_spec.rb | 6 +- .../enrollment_helper_spec.rb | 1 - spec/support/controller_helper.rb | 19 +- yarn.lock | 200 +++++++----------- 78 files changed, 966 insertions(+), 619 deletions(-) delete mode 100644 Guardfile create mode 100644 app/services/reporting/total_user_count_report.rb create mode 100644 spec/features/idv/proof_address_rate_limit_spec.rb delete mode 100644 spec/features/idv/steps/confirmation_step_spec.rb create mode 100644 spec/services/reporting/total_user_count_report_spec.rb diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 62f6d2243a5..20f62c1e9ce 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -243,6 +243,7 @@ js_build: - *bundle_install - *yarn_production_install - bundle exec rake assets:precompile + - make lint_asset_bundle_size js_tests: stage: test diff --git a/.rubocop.yml b/.rubocop.yml index de889dd8439..7ac07b19819 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -5,6 +5,7 @@ # https://github.com/bbatsov/rubocop/blob/master/config/disabled.yml require: - rubocop-rails + - rubocop-rspec - rubocop-performance - ./lib/linters/analytics_event_name_linter.rb - ./lib/linters/localized_validation_message_linter.rb @@ -997,6 +998,9 @@ Rails/WhereNot: Rails/WhereNotWithMultipleConditions: Enabled: true +RSpec/LeakyConstantDeclaration: + Enabled: true + Security/Eval: Enabled: true diff --git a/Gemfile b/Gemfile index f4eea5be00c..8c1cc92db0a 100644 --- a/Gemfile +++ b/Gemfile @@ -87,7 +87,6 @@ gem 'zxcvbn', '0.1.9' group :development do gem 'better_errors', '>= 2.5.1' gem 'derailed_benchmarks' - gem 'guard-rspec', require: false gem 'irb' gem 'letter_opener', '~> 1.8' gem 'rack-mini-profiler', '>= 1.1.3', require: false @@ -107,10 +106,12 @@ group :development, :test do gem 'pry-doc' gem 'pry-rails' gem 'psych' + 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-rails', '>= 2.5.2', require: false + gem 'rubocop-rspec', require: false end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 27419228666..501904fdc06 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -299,7 +299,6 @@ GEM ffi-compiler (1.0.1) ffi (>= 1.0.0) rake - formatador (0.2.5) foundation_emails (2.2.1.0) fugit (1.8.1) et-orbi (~> 1, >= 1.2.7) @@ -318,20 +317,6 @@ GEM thor (>= 0.14.1) webrick (>= 1.3) google-protobuf (3.24.0) - guard (2.16.2) - formatador (>= 0.2.4) - listen (>= 2.7, < 4.0) - lumberjack (>= 1.0.12, < 2.0) - nenv (~> 0.1) - notiffany (~> 0.0) - pry (>= 0.9.12) - shellany (~> 0.0) - thor (>= 0.18.1) - guard-compat (1.2.1) - guard-rspec (4.7.3) - guard (~> 2.1) - guard-compat (~> 1.1) - rspec (>= 2.99.0, < 4.0) hashdiff (1.0.1) hashie (4.1.0) heapy (0.2.0) @@ -394,7 +379,6 @@ GEM yard (~> 0.9.25) zeitwerk (~> 2.5) lru_redux (1.1.0) - lumberjack (1.2.9) mail (2.8.1) mini_mime (>= 0.1.1) net-imap @@ -411,7 +395,6 @@ GEM minitest (5.19.0) msgpack (1.7.2) multiset (0.5.3) - nenv (0.3.0) net-imap (0.3.7) date net-protocol @@ -429,9 +412,6 @@ GEM nokogiri (1.14.5) mini_portile2 (~> 2.8.0) racc (~> 1.4) - notiffany (0.1.3) - nenv (~> 0.1) - shellany (~> 0.0) openssl (3.0.2) openssl-signature_algorithm (1.2.1) openssl (> 2.0, < 3.1) @@ -596,6 +576,10 @@ GEM unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.29.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 (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) @@ -603,6 +587,10 @@ GEM activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) + rubocop-rspec (2.24.1) + rubocop (~> 1.33) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) ruby-progressbar (1.13.0) ruby-saml (1.13.0) nokogiri (>= 1.10.5) @@ -622,7 +610,6 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - shellany (0.0.1) shoulda-matchers (4.5.1) activesupport (>= 4.2.0) simple_form (5.1.0) @@ -748,7 +735,6 @@ DEPENDENCIES faraday-retry foundation_emails good_job (~> 3.0) - guard-rspec hashie (~> 4.1) http_accept_language i18n-tasks (~> 1.0) @@ -799,12 +785,14 @@ DEPENDENCIES retries rotp (~> 6.1) rqrcode + rspec (~> 3.12.0) rspec-rails (~> 6.0) rspec-retry rspec_junit_formatter rubocop (~> 1.55.1) rubocop-performance (~> 1.18.0) rubocop-rails (>= 2.5.2) + rubocop-rspec ruby-progressbar ruby-saml safe_target_blank (>= 1.0.2) diff --git a/Guardfile b/Guardfile deleted file mode 100644 index 8586edb9f26..00000000000 --- a/Guardfile +++ /dev/null @@ -1,63 +0,0 @@ -# A sample Guardfile -# More info at https://github.com/guard/guard#readme - -## Uncomment and set this to only include directories you want to watch -# directories %w(app lib config test spec features) \ -# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")} - -## Note: if you are using the `directories` clause above and you are not -## watching the project directory ('.'), then you will want to move -## the Guardfile to a watched dir and symlink it back, e.g. -# -# $ mkdir config -# $ mv Guardfile config/ -# $ ln -s config/Guardfile . -# -# and, you'll have to watch "config/Guardfile" instead of "Guardfile" - -# Note: The cmd option is now required due to the increasing number of ways -# rspec may be run, below are examples of the most common uses. -# * bundler: 'bundle exec rspec' -# * bundler binstubs: 'bin/rspec' -# * spring: 'bin/rspec' (This will use spring if running and you have -# installed the spring binstubs per the docs) -# * zeus: 'zeus rspec' (requires the server to be started separately) -# * 'just' rspec: 'rspec' -guard :rspec, cmd: ENV['GUARD_RSPEC_CMD'] || 'bundle exec rspec' do - require 'guard/rspec/dsl' - dsl = Guard::RSpec::Dsl.new(self) - - # Feel free to open issues for suggestions and improvements - - # RSpec files - rspec = dsl.rspec - watch(rspec.spec_helper) { rspec.spec_dir } - watch(rspec.spec_support) { rspec.spec_dir } - watch(rspec.spec_files) - - # Ruby files - ruby = dsl.ruby - dsl.watch_spec_files_for(ruby.lib_files) - - # Rails files - rails = dsl.rails(view_extensions: %w[erb]) - dsl.watch_spec_files_for(rails.app_files) - dsl.watch_spec_files_for(rails.views) - - watch(rails.controllers) do |m| - [ - rspec.spec.call("routing/#{m[1]}_routing"), - rspec.spec.call("controllers/#{m[1]}_controller"), - rspec.spec.call("acceptance/#{m[1]}"), - ] - end - - # Rails config changes - watch(rails.spec_helper) { rspec.spec_dir } - watch(rails.routes) { "#{rspec.spec_dir}/routing" } - watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" } - - # Capybara features specs - watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") } - watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") } -end diff --git a/Makefile b/Makefile index bdc5239fdee..ec1071a64f8 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,7 @@ ARTIFACT_DESTINATION_FILE ?= ./tmp/idp.tar.gz lint_tracker_events \ lint_yaml \ lint_yarn_workspaces \ + lint_asset_bundle_size \ lintfix \ normalize_yaml \ optimize_assets \ @@ -113,6 +114,10 @@ lint_yaml: normalize_yaml ## Lints YAML files lint_yarn_workspaces: ## Lints Yarn workspace packages scripts/validate-workspaces.js +lint_asset_bundle_size: ## Lints JavaScript and CSS compiled bundle size + find app/assets/builds/application.css -size -350000c | grep . + find public/packs/js/application-*.digested.js -size -8000c | grep . + lint_migrations: scripts/migration_check diff --git a/app/controllers/concerns/rate_limit_concern.rb b/app/controllers/concerns/rate_limit_concern.rb index 75d48d0a179..bcffa3f369f 100644 --- a/app/controllers/concerns/rate_limit_concern.rb +++ b/app/controllers/concerns/rate_limit_concern.rb @@ -1,27 +1,36 @@ module RateLimitConcern extend ActiveSupport::Concern - ALL_IDV_RATE_LIMITTERS = [:idv_resolution, :idv_doc_auth, :proof_address, :proof_ssn].freeze + ALL_IDV_RATE_LIMITERS = [:idv_resolution, :idv_doc_auth, :proof_ssn].freeze - def confirm_not_rate_limited(rate_limiters = ALL_IDV_RATE_LIMITTERS) - rate_limited = false - rate_limiters.each do |rate_limit_type| - if rate_limit_redirect!(rate_limit_type) - rate_limited = true - break - end + def confirm_not_rate_limited(rate_limiters = ALL_IDV_RATE_LIMITERS) + exceeded_rate_limits = check_for_exceeded_rate_limits(rate_limiters) + if exceeded_rate_limits.any? + rate_limit_redirect!(exceeded_rate_limits.first) + return true end - rate_limited + confirm_not_rate_limited_for_phone_and_letter_address_verification end def confirm_not_rate_limited_after_doc_auth - rate_limitters = [:idv_resolution, :proof_ssn, :proof_address] - confirm_not_rate_limited(rate_limitters) + rate_limiters = [:idv_resolution, :proof_ssn] + confirm_not_rate_limited(rate_limiters) end - def confirm_not_rate_limited_after_idv_resolution - rate_limitters = [:proof_address] - confirm_not_rate_limited(rate_limitters) + def confirm_not_rate_limited_for_phone_address_verification + if idv_attempter_rate_limited?(:proof_address) + rate_limit_redirect!(:proof_address) + return true + end + end + + private + + def confirm_not_rate_limited_for_phone_and_letter_address_verification + if idv_attempter_rate_limited?(:proof_address) && Idv::GpoMail.new(current_user).mail_spammed? + rate_limit_redirect!(:proof_address) + return true + end end def rate_limit_redirect!(rate_limit_type) @@ -60,6 +69,12 @@ def rate_limited_redirect(rate_limit_type) end end + def check_for_exceeded_rate_limits(rate_limit_types) + rate_limit_types.select do |rate_limit_type| + idv_attempter_rate_limited?(rate_limit_type) + end + end + def idv_attempter_rate_limited?(rate_limit_type) if rate_limit_type == :proof_ssn return unless pii_ssn diff --git a/app/controllers/idv/by_mail/enter_code_controller.rb b/app/controllers/idv/by_mail/enter_code_controller.rb index daa730f6211..a15a1645d27 100644 --- a/app/controllers/idv/by_mail/enter_code_controller.rb +++ b/app/controllers/idv/by_mail/enter_code_controller.rb @@ -24,12 +24,14 @@ def index end gpo_mail = Idv::GpoMail.new(current_user) + @gpo_mail_spammed = gpo_mail.mail_spammed? + @last_date_letter_was_sent = last_date_letter_was_sent @gpo_verify_form = GpoVerifyForm.new(user: current_user, pii: pii) @code = session[:last_gpo_confirmation_code] if FeatureManagement.reveal_gpo_code? @should_prompt_user_to_request_another_letter = FeatureManagement.gpo_verification_enabled? && - !gpo_mail.mail_spammed? && + !@gpo_mail_spammed && !gpo_mail.profile_too_old? if pii_locked? @@ -152,6 +154,11 @@ def threatmetrix_enabled? def pii_locked? !Pii::Cacher.new(current_user, user_session).exists_in_session? end + + def last_date_letter_was_sent + current_user.gpo_verification_pending_profile&.gpo_confirmation_codes&. + pluck(:updated_at)&.max + end end end end diff --git a/app/controllers/idv/phone_controller.rb b/app/controllers/idv/phone_controller.rb index d30421e17d3..7d948416541 100644 --- a/app/controllers/idv/phone_controller.rb +++ b/app/controllers/idv/phone_controller.rb @@ -7,7 +7,7 @@ class PhoneController < ApplicationController attr_reader :idv_form - before_action :confirm_not_rate_limited_after_idv_resolution, except: [:new] + before_action :confirm_not_rate_limited_for_phone_address_verification, except: [:new] before_action :confirm_verify_info_step_complete before_action :confirm_step_needed before_action :set_idv_form @@ -24,7 +24,7 @@ def new render 'shared/wait' and return if async_state.in_progress? - return if confirm_not_rate_limited_after_idv_resolution + return if confirm_not_rate_limited_for_phone_address_verification if async_state.none? Funnel::DocAuth::RegisterStep.new(current_user.id, current_sp&.issuer). diff --git a/app/controllers/idv/phone_errors_controller.rb b/app/controllers/idv/phone_errors_controller.rb index 1db71299607..41cb400335f 100644 --- a/app/controllers/idv/phone_errors_controller.rb +++ b/app/controllers/idv/phone_errors_controller.rb @@ -6,7 +6,7 @@ class PhoneErrorsController < ApplicationController before_action :confirm_two_factor_authenticated before_action :confirm_idv_phone_step_needed - before_action :confirm_idv_phone_step_submitted + before_action :confirm_idv_phone_step_submitted, except: [:failure] before_action :set_gpo_letter_available before_action :ignore_form_step_wait_requests @@ -32,6 +32,8 @@ def jobfail end def failure + return redirect_to(idv_phone_url) unless rate_limiter.limited? + @expires_at = rate_limiter.expires_at track_event(type: :failure) end diff --git a/app/controllers/users/piv_cac_authentication_setup_controller.rb b/app/controllers/users/piv_cac_authentication_setup_controller.rb index ddeb777d2d6..73cef8934b9 100644 --- a/app/controllers/users/piv_cac_authentication_setup_controller.rb +++ b/app/controllers/users/piv_cac_authentication_setup_controller.rb @@ -56,7 +56,7 @@ def submit_new_piv_cac private def track_piv_cac_setup_visit - analytics.piv_cac_setup_visit(**analytics_properties) + analytics.piv_cac_setup_visited(**analytics_properties) end def remove_piv_cac diff --git a/app/controllers/users/piv_cac_login_controller.rb b/app/controllers/users/piv_cac_login_controller.rb index ffcab1985de..e213ae94f54 100644 --- a/app/controllers/users/piv_cac_login_controller.rb +++ b/app/controllers/users/piv_cac_login_controller.rb @@ -37,7 +37,7 @@ def error private def render_prompt - analytics.piv_cac_setup_visit(in_account_creation_flow: false) + analytics.piv_cac_login_visited @presenter = PivCacAuthenticationLoginPresenter.new(piv_cac_login_form, url_options) render :new end diff --git a/app/controllers/users/piv_cac_setup_from_sign_in_controller.rb b/app/controllers/users/piv_cac_setup_from_sign_in_controller.rb index b32cc210e5a..eca3141fbcc 100644 --- a/app/controllers/users/piv_cac_setup_from_sign_in_controller.rb +++ b/app/controllers/users/piv_cac_setup_from_sign_in_controller.rb @@ -32,7 +32,7 @@ def decline private def render_prompt - analytics.piv_cac_setup_visit(in_account_creation_flow: false) + analytics.piv_cac_setup_visited(in_account_creation_flow: false) render :prompt end diff --git a/app/javascript/packages/address-search/components/full-address-search.spec.tsx b/app/javascript/packages/address-search/components/full-address-search.spec.tsx index f8a663bcb71..91c3cbcd2d4 100644 --- a/app/javascript/packages/address-search/components/full-address-search.spec.tsx +++ b/app/javascript/packages/address-search/components/full-address-search.spec.tsx @@ -66,6 +66,51 @@ describe('FullAddressSearch', () => { }); }); + context('Address Search Label Text', () => { + it('does not render when handleLocationSelect is not null', async () => { + const handleLocationsFound = sandbox.stub(); + const onSelect = sinon.stub(); + const { queryByText } = render( + new Map() }}> + undefined} + handleLocationSelect={onSelect} + disabled={false} + /> + , + ); + + const searchLabel = await queryByText( + 'in_person_proofing.headings.po_search.address_search_label', + ); + expect(searchLabel).to.be.empty; + }); + + it('renders when handleLocationSelect is null', async () => { + const handleLocationsFound = sandbox.stub(); + const { queryByText } = render( + new Map() }}> + undefined} + handleLocationSelect={null} + disabled={false} + /> + , + ); + + const searchLabel = await queryByText( + 'in_person_proofing.body.location.po_search.address_search_label', + ); + expect(searchLabel).to.exist(); + }); + }); + context('validates form', () => { it('displays an error for all required fields when input is empty', async () => { const handleLocationsFound = sandbox.stub(); diff --git a/app/javascript/packages/address-search/components/full-address-search.tsx b/app/javascript/packages/address-search/components/full-address-search.tsx index f21f05fcd80..ab2dc67b402 100644 --- a/app/javascript/packages/address-search/components/full-address-search.tsx +++ b/app/javascript/packages/address-search/components/full-address-search.tsx @@ -30,11 +30,13 @@ function FullAddressSearch({ {t('idv.failure.exceptions.post_office_search_error')} )} - {handleLocationSelect && ( + {handleLocationSelect ? ( <> {t('in_person_proofing.headings.po_search.location')}

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

+ ) : ( +

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

)} ] iaas + # @return [String] CSV report + def build_csv(iaas) by_iaa_results = iaas.flat_map do |iaa| Db::MonthlySpAuthCount::UniqueMonthlyAuthCountsByIaa.call( key: iaa.key, @@ -27,12 +33,10 @@ def perform(_date) end end - csv = combine_by_iaa_month( + combine_by_iaa_month( by_iaa_results: by_iaa_results, by_issuer_results: by_issuer_results, ) - - save_report(REPORT_NAME, csv, extension: 'csv') end def combine_by_iaa_month(by_iaa_results:, by_issuer_results:) diff --git a/app/jobs/reports/monthly_key_metrics_report.rb b/app/jobs/reports/monthly_key_metrics_report.rb index 31150211f33..a01d1a6ad98 100644 --- a/app/jobs/reports/monthly_key_metrics_report.rb +++ b/app/jobs/reports/monthly_key_metrics_report.rb @@ -12,10 +12,12 @@ def perform(date = Time.zone.today) account_reuse_table = account_reuse_report.account_reuse_report total_profiles_table = account_reuse_report.total_identities_report account_deletion_rate_table = account_deletion_rate_report.account_deletion_report + total_user_count_table = total_user_count_report.total_user_count_report upload_to_s3(account_reuse_table, report_name: 'account_reuse') upload_to_s3(total_profiles_table, report_name: 'total_profiles') upload_to_s3(account_deletion_rate_table, report_name: 'account_deletion_rate') + upload_to_s3(total_user_count_table, report_name: 'total_user_count') email_tables = [ [ @@ -38,6 +40,12 @@ def perform(date = Time.zone.today) }, *account_deletion_rate_table, ], + [ + { + title: 'Total user count (all-time)', + }, + *total_user_count_table, + ], ] email_message = "Report: #{REPORT_NAME} #{date}" @@ -71,6 +79,10 @@ def account_deletion_rate_report @account_deletion_rate_report ||= Reporting::AccountDeletionRateReport.new(report_date) end + def total_user_count_report + @total_user_count_report ||= Reporting::TotalUserCountReport.new(report_date) + end + def upload_to_s3(report_body, report_name: nil) _latest, path = generate_s3_paths(REPORT_NAME, 'csv', subname: report_name, now: report_date) diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 7be300f33e2..8293acf24ea 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -2652,13 +2652,14 @@ def multi_factor_auth_added_phone(enabled_mfa_methods_count:, **extra) ) end + # @identity.idp.previous_event_name Multi-Factor Authentication: Added PIV_CAC # Tracks when the user has added the MFA method piv_cac to their account # @param [Integer] enabled_mfa_methods_count number of registered mfa methods for the user # @param [Boolean] in_account_creation_flow whether user is going through creation flow def multi_factor_auth_added_piv_cac(enabled_mfa_methods_count:, in_account_creation_flow:, **extra) track_event( - 'Multi-Factor Authentication: Added PIV_CAC', # rubocop:disable IdentityIdp/AnalyticsEventNameLinter + :multi_factor_auth_added_piv_cac, { method_name: :piv_cac, enabled_mfa_methods_count:, @@ -2767,6 +2768,7 @@ def multi_factor_auth_enter_personal_key_visit(context:, **extra) ) end + # @identity.idp.previous_event_name 'Multi-Factor Authentication: enter PIV CAC visited' # @param ["authentication","reauthentication","confirmation"] context user session context # @param ["piv_cac"] multi_factor_auth_method # @param [Integer, nil] piv_cac_configuration_id PIV/CAC configuration database ID @@ -2778,7 +2780,7 @@ def multi_factor_auth_enter_piv_cac( **extra ) track_event( - 'Multi-Factor Authentication: enter PIV CAC visited', # rubocop:disable IdentityIdp/AnalyticsEventNameLinter + :multi_factor_auth_enter_piv_cac, context: context, multi_factor_auth_method: multi_factor_auth_method, piv_cac_configuration_id: piv_cac_configuration_id, @@ -3352,29 +3354,36 @@ def phone_deletion(success:, phone_configuration_id:, **extra) end # @identity.idp.previous_event_name User Registration: piv cac disabled + # @identity.idp.previous_event_name PIV CAC disabled # Tracks when user's piv cac is disabled def piv_cac_disabled - track_event('PIV CAC disabled') # rubocop:disable IdentityIdp/AnalyticsEventNameLinter + track_event(:piv_cac_disabled) end + # @identity.idp.previous_event_name PIV/CAC login # @param [Boolean] success # @param [Hash] errors # tracks piv cac login event def piv_cac_login(success:, errors:, **extra) track_event( - 'PIV/CAC Login', # rubocop:disable IdentityIdp/AnalyticsEventNameLinter + :piv_cac_login, success: success, errors: errors, **extra, ) end + def piv_cac_login_visited + track_event(:piv_cac_login_visited) + end + # @identity.idp.previous_event_name User Registration: piv cac setup visited + # @identity.idp.previous_event_name PIV CAC setup visited # Tracks when user's piv cac setup # @param [Boolean] in_account_creation_flow - def piv_cac_setup_visit(in_account_creation_flow:, **extra) + def piv_cac_setup_visited(in_account_creation_flow:, **extra) track_event( - 'PIV CAC setup visited', # rubocop:disable IdentityIdp/AnalyticsEventNameLinter + :piv_cac_setup_visited, in_account_creation_flow:, **extra, ) diff --git a/app/services/reporting/total_user_count_report.rb b/app/services/reporting/total_user_count_report.rb new file mode 100644 index 00000000000..7ed0f321448 --- /dev/null +++ b/app/services/reporting/total_user_count_report.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Reporting + class TotalUserCountReport + attr_reader :report_date + + def initialize(report_date = Time.zone.today) + @report_date = report_date + end + + def total_user_count_report + [ + ['All-time user count'], + [total_user_count], + ] + end + + private + + def total_user_count + User.where('created_at <= ?', report_date).count + end + end +end diff --git a/app/views/idv/by_mail/enter_code/index.html.erb b/app/views/idv/by_mail/enter_code/index.html.erb index e7b5c8c78d1..1eab6fe93e4 100644 --- a/app/views/idv/by_mail/enter_code/index.html.erb +++ b/app/views/idv/by_mail/enter_code/index.html.erb @@ -8,9 +8,21 @@ <% end %> <% if @user_did_not_receive_letter %> -<% title t('idv.gpo.did_not_receive_letter.title') %> + <% title t('idv.gpo.did_not_receive_letter.title') %> <% else %> -<% title t('idv.gpo.title') %> + <% title t('idv.gpo.title') %> +<% end %> + +<% if @gpo_mail_spammed %> + <%= render AlertComponent.new(type: :warning, class: 'margin-bottom-4') do %> + <%= t( + 'idv.gpo.alert_spam_warning_html', + date_letter_was_sent: I18n.l( + @last_date_letter_was_sent, + format: :event_date, + ), + ) %> + <% end %> <% end %> <%= render AlertComponent.new(type: :warning, class: 'margin-bottom-3') do %> diff --git a/config/application.yml.default.docker b/config/application.yml.default.docker index 5cb51cd09bd..b75220aed25 100644 --- a/config/application.yml.default.docker +++ b/config/application.yml.default.docker @@ -32,3 +32,4 @@ production: doc_auth_vendor: 'mock' usps_auth_token_refresh_job_enabled: false lexisnexis_threatmetrix_mock_enabled: true + enable_usps_verification: true diff --git a/config/locales/help_text/en.yml b/config/locales/help_text/en.yml index 0a90f49ced7..1493867348e 100644 --- a/config/locales/help_text/en.yml +++ b/config/locales/help_text/en.yml @@ -8,14 +8,16 @@ en: email: Email address full_name: Full name ial1_consent_reminder_html: You must consent each year to share your information - with %{sp}. We’ll share your information with %{sp} to + with %{sp}. We’ll share your information with + %{sp} to connect your account. + ial1_intro_html: We’ll share your information with %{sp} to connect your account. - ial1_intro_html: We’ll share your information with %{sp} to connect your account. - ial2_consent_reminder_html: '%{sp} needs to know who you are to connect - to your account. You must consent each year to share your verified - 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_consent_reminder_html: '%{sp} needs to know who you are to + connect to your account. You must consent each year to share your + verified 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 diff --git a/config/locales/help_text/es.yml b/config/locales/help_text/es.yml index cd0179d3b50..70eb1fe7f2a 100644 --- a/config/locales/help_text/es.yml +++ b/config/locales/help_text/es.yml @@ -8,15 +8,16 @@ es: email: Dirección de correo electrónico full_name: Nombre completo ial1_consent_reminder_html: Usted debe dar cada año su consentimiento para - compartir su información con %{sp}. Compartiremos su información - con %{sp} para vincular su cuenta. - ial1_intro_html: Le haremos llegar su información a %{sp} para conectar su cuenta. - ial2_consent_reminder_html: 'Para conectar su cuenta, %{sp} necesita - saber quién usted. Debe dar su consentimiento cada año para compartir su - información verificada con %{sp}. Compartiremos esta - información:' - ial2_intro_html: '%{sp} necesita saber quién es para conectar su cuenta. - Compartiremos esta información con el organismo asociado: ' + compartir su información con %{sp}. Compartiremos su + información con %{sp} para vincular su cuenta. + ial1_intro_html: Le haremos llegar su información a %{sp} para + conectar su cuenta. + ial2_consent_reminder_html: 'Para conectar su cuenta, %{sp} + necesita saber quién usted. Debe dar su consentimiento cada año para + compartir su información verificada con %{sp}. + Compartiremos esta 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 diff --git a/config/locales/help_text/fr.yml b/config/locales/help_text/fr.yml index 4b7e484f9c8..11aac282bdd 100644 --- a/config/locales/help_text/fr.yml +++ b/config/locales/help_text/fr.yml @@ -8,16 +8,16 @@ fr: email: Adresse e-mail full_name: Nom complet ial1_consent_reminder_html: Vous devez consentir chaque année au partage de vos - informations avec %{sp}. Nous partagerons vos informations avec - %{sp} pour connecter votre compte. - ial1_intro_html: Nous partagerons vos informations avec %{sp} pour - connecter votre compte. - ial2_consent_reminder_html: '%{sp} doit savoir qui vous êtes pour se - connecter à votre compte. Vous devez consentir chaque année à partager - vos informations vérifiées avec %{sp}. Nous partagerons ces - informations :' - ial2_intro_html: '%{sp} a besoin de savoir qui vous êtes pour connecter - votre compte. Nous partagerons ces informations avec l’agence + informations avec %{sp}. Nous partagerons vos + informations avec %{sp} pour connecter votre compte. + ial1_intro_html: Nous partagerons vos informations avec %{sp} + pour connecter votre compte. + ial2_consent_reminder_html: '%{sp} doit savoir qui vous êtes + pour se connecter à votre compte. Vous devez consentir chaque année à + partager vos informations vérifiées avec %{sp}. Nous + partagerons ces informations :' + 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 diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml index 0619c1c515d..692c007b363 100644 --- a/config/locales/idv/en.yml +++ b/config/locales/idv/en.yml @@ -172,6 +172,8 @@ en: JavaScript to continue this process.' gpo: alert_info: 'We sent a letter with your verification code to:' + alert_spam_warning_html: 'We are unable to send more letters. The most recent + letter was sent on %{date_letter_was_sent}.' change_to_verification_code_html: 'The one-time code from your letter is now referred to as verification code.' clear_and_start_over: Clear your information and start over @@ -253,8 +255,9 @@ en: - Your primary number (the one you use the most often) return_to_profile: '‹ Return to your %{app_name} profile' review: - by_mail_password_reminder_html: Remember your password. The verification - code in your letter won’t work if you reset your password later. + by_mail_password_reminder_html: Remember your password. The + verification code in your letter won’t work if you reset your password + later. message: '%{app_name} will encrypt your information with your password. This means that your information is secure and only you will be able to access or change it.' diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml index c62f543c9b3..97cf2caf318 100644 --- a/config/locales/idv/es.yml +++ b/config/locales/idv/es.yml @@ -180,6 +180,8 @@ es: habilitar JavaScript para continuar con este proceso.' gpo: alert_info: 'Enviamos una carta con su código de verificación a:' + alert_spam_warning_html: 'No podemos enviar más cartas. La última carta se envió + el %{date_letter_was_sent}.' change_to_verification_code_html: 'El código único de su carta ahora se conoce como código de verificación.' clear_and_start_over: Borrar su información y empezar de nuevo @@ -265,9 +267,9 @@ es: - Su número principal (el que utiliza con más frecuencia) return_to_profile: '‹ Volver a tu perfil de %{app_name}' review: - by_mail_password_reminder_html: Recuerde su contraseña. El código de - verificación de su carta no funcionará si restablece su contraseña más - tarde. + by_mail_password_reminder_html: Recuerde su contraseña. El + código de verificación de su carta no funcionará si restablece su + contraseña más tarde. message: '%{app_name} encriptará tu información con tu contraseña. Esto significa que tu información estará segura y solo tú podrás consultarla o modificarla.' diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml index bc46fc1f5ac..78831ad8862 100644 --- a/config/locales/idv/fr.yml +++ b/config/locales/idv/fr.yml @@ -186,6 +186,9 @@ fr: devez activer JavaScript pour poursuivre ce processus.' gpo: alert_info: 'Nous avons envoyé une lettre avec votre code de vérification à:' + alert_spam_warning_html: 'Nous ne sommes pas en mesure d’envoyer d’autres + lettres. La dernière lettre a été envoyée + %{date_letter_was_sent}.' change_to_verification_code_html: 'Le code à usage unique figurant dans votre lettre est désormais appelé code de vérification.' @@ -279,9 +282,9 @@ fr: - Votre numéro principal (celui que vous utilisez le plus souvent) return_to_profile: '‹ Revenir à votre profil %{app_name}' review: - by_mail_password_reminder_html: Mémorisez votre mot de passe. Le code de - vérification contenu dans votre lettre ne fonctionnera pas si vous - réinitialisez votre mot de passe par la suite. + by_mail_password_reminder_html: Mémorisez votre mot de passe. + Le code de vérification contenu dans votre lettre ne fonctionnera pas + si vous réinitialisez votre mot de passe par la suite. message: '%{app_name} crypte vos informations avec votre mot de passe. Cela signifie que vos informations sont sécurisées et que vous seul pourrez y accéder ou les modifier.' diff --git a/config/locales/in_person_proofing/en.yml b/config/locales/in_person_proofing/en.yml index 22962daf2ed..d6b371e89fa 100644 --- a/config/locales/in_person_proofing/en.yml +++ b/config/locales/in_person_proofing/en.yml @@ -39,7 +39,7 @@ en: po_search: address_label: Address address_search_hint: 'Example: 1234 N Example St., Allentown, PA 12345' - address_search_label: Enter an address to find a Post Office near you + address_search_label: Enter an address to find a Post Office near you. city_label: City is_searching_message: Searching for Post Office locations… none_found: Sorry, there are no participating Post Offices within 50 miles of diff --git a/config/locales/in_person_proofing/es.yml b/config/locales/in_person_proofing/es.yml index 812b0e144ce..e36c6e66b81 100644 --- a/config/locales/in_person_proofing/es.yml +++ b/config/locales/in_person_proofing/es.yml @@ -42,7 +42,7 @@ es: address_label: Dirección address_search_hint: 'Ejemplo: 1234 N Example St., Allentown, PA 12345' address_search_label: Introduzca una dirección para encontrar una Oficina de - Correos cercana a usted + Correos cercana a usted. city_label: Ciudad is_searching_message: Buscando oficinas de correos… none_found: Lo sentimos, no hay Oficinas de Correos participantes en un radio de diff --git a/config/locales/two_factor_authentication/en.yml b/config/locales/two_factor_authentication/en.yml index 4a54b871641..7c1f8b47f92 100644 --- a/config/locales/two_factor_authentication/en.yml +++ b/config/locales/two_factor_authentication/en.yml @@ -99,8 +99,8 @@ en: otp_delivery_preference: 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. + 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) diff --git a/config/locales/two_factor_authentication/es.yml b/config/locales/two_factor_authentication/es.yml index ad5a263dcc3..726c504fdaf 100644 --- a/config/locales/two_factor_authentication/es.yml +++ b/config/locales/two_factor_authentication/es.yml @@ -104,9 +104,9 @@ es: seguridad para ese número de teléfono. otp_delivery_preference: 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}. + 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}. no_supported_options: No podemos verificar los números de teléfono de %{location} phone_call: llamada telefónica sms: Mensaje de texto (SMS, sigla en inglés) diff --git a/config/locales/two_factor_authentication/fr.yml b/config/locales/two_factor_authentication/fr.yml index 6fc09347ad4..96b5ea0b64f 100644 --- a/config/locales/two_factor_authentication/fr.yml +++ b/config/locales/two_factor_authentication/fr.yml @@ -109,9 +109,9 @@ fr: de sécurité à ce numéro de téléphone. otp_delivery_preference: 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}. + 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}. no_supported_options: Nous ne sommes pas en mesure de vérifier les numéros de téléphone de %{location} phone_call: appel téléphonique diff --git a/config/locales/user_mailer/en.yml b/config/locales/user_mailer/en.yml index 87d0a14c160..712469f47c1 100644 --- a/config/locales/user_mailer/en.yml +++ b/config/locales/user_mailer/en.yml @@ -267,8 +267,8 @@ en: concerned someone other than you may be trying to access your information. learn_more_link_text: Learn more about your options - reminder_html: As a reminder, %{app_name} will never ask for your login - credentials by phone or email. You can take additional steps to + reminder_html: As a reminder, %{app_name} will never ask for your login + credentials by phone or email. You can take additional steps to secure your account by enabling two-factor authentication. step_1: Visit the %{app_name} website and select sign in step_2: Select “forgot your password?” at the bottom of the page diff --git a/config/locales/user_mailer/es.yml b/config/locales/user_mailer/es.yml index f25a7ca2698..ca46b638e66 100644 --- a/config/locales/user_mailer/es.yml +++ b/config/locales/user_mailer/es.yml @@ -283,10 +283,10 @@ es: preocupados alguien que no sea usted puede estar intentando acceder a su información. learn_more_link_text: Conozca más sobre sus opciones - reminder_html: Le recordamos que %{app_name} nunca le pedirá sus credenciales - de inicio de sesión. por teléfono o correo electrónico. Puede tomar - medidas adicionales para proteger su cuenta habilitando la autenticación - de dos factores. + reminder_html: Le recordamos que %{app_name} nunca le pedirá sus + credenciales de inicio de sesión. por teléfono o correo + electrónico. Puede tomar medidas adicionales para proteger su + cuenta habilitando la autenticación de dos factores. step_1: Visite el sitio web %{app_name} y seleccione iniciar sesión step_2: Seleccione “¿olvidó su contraseña?” al final de la página step_3: Siga las instrucciones para restablecer su contraseña diff --git a/config/locales/user_mailer/fr.yml b/config/locales/user_mailer/fr.yml index 3bd4b728c2a..ea710048efb 100644 --- a/config/locales/user_mailer/fr.yml +++ b/config/locales/user_mailer/fr.yml @@ -293,10 +293,10 @@ fr: %{app_name}. Nous sommes concernés quelqu’un d’autre que vous tente peut-être d’accéder à vos informations. learn_more_link_text: En savoir plus sur vos options - reminder_html: Pour rappel, %{app_name} ne vous demandera jamais vos - identifiants de connexion par téléphone ou par e-mail. Vous pouvez - prendre des mesures supplémentaires pour sécuriser votre compte en - activant l’authentification à deux facteurs. + reminder_html: Pour rappel, %{app_name} ne vous demandera jamais vos + identifiants de connexion par téléphone ou par e-mail. Vous + pouvez prendre des mesures supplémentaires pour sécuriser votre compte + en activant l’authentification à deux facteurs. step_1: Visitez le site Web %{app_name} et sélectionnez Connexion step_2: Sélectionnez “Vous avez oublié votre mot de passe?” au bas de la page step_3: Suivez les instructions pour réinitialiser votre mot de passe diff --git a/config/service_providers.localdev.yml b/config/service_providers.localdev.yml index ab8affd53ff..a00dcea2759 100644 --- a/config/service_providers.localdev.yml +++ b/config/service_providers.localdev.yml @@ -266,14 +266,14 @@ test: ial: 2 help_text: sign_in: - en: First time here from %{sp_name}?

Your old %{sp_name} username + en: First time here from %{sp_name}?

Your old %{sp_name} username and password won’t work. Please create a Login.gov account using the same email address you use for %{sp_name}.

Learn more - es: ¿Ha venido de %{sp_name}?

Si tiene un perfil de %{sp_name} + es: ¿Ha venido de %{sp_name}?

Si tiene un perfil de %{sp_name} existente, favor de usar la dirección de correo electrónico primaria o secundaria que usó para %{sp_name} para crear un nueva cuenta de Login.gov

Obtenga más información. - fr: Êtes-vous venu(e) de %{sp_name}?

Si vous avez déjà un profil + fr: Êtes-vous venu(e) de %{sp_name}?

Si vous avez déjà un profil %{sp_name}, veuillez utiliser l'adresse e-mail principale ou secondaire que vous avez utilisée pour %{sp_name} pour créer votre nouveau compte Login.gov

En diff --git a/docs/local-development.md b/docs/local-development.md index 7ad1eced699..86051607d6b 100644 --- a/docs/local-development.md +++ b/docs/local-development.md @@ -61,7 +61,6 @@ If not using macOS: Login.gov uses the following tools for our testing: - [RSpec](https://relishapp.com/rspec/rspec-core/docs/command-line) - - [Guard](https://github.com/guard/guard-rspec) - [Mocha documentation](https://mochajs.org/) To run our full test suite locally, use the following command: @@ -92,24 +91,6 @@ If not using macOS: $ SHOW_BROWSER=true bundle exec rspec spec/features/ ``` -### Speeding up local development and testing - - To automatically run the test that corresponds to the file you are editing, - run `bundle exec guard` with the env var `GUARD_RSPEC_CMD` set to your preferred - command for running `rspec`. For example, if you use [Zeus](https://github.com/burke/zeus), - you would set the env var to `zeus rspec`: - ```console - GUARD_RSPEC_CMD="zeus rspec" bundle exec guard - ``` - - If you don't specify the `GUARD_RSPEC_CMD` env var, it will default to - `bundle exec rspec`. - - We also recommend setting up a shell alias for running this command, such as: - ```console - alias idpguard='GUARD_RSPEC_CMD="zeus rspec" bundle exec guard' - ``` - ### Viewing email messages In local development, the application does not deliver real email messages. Instead, we use a tool diff --git a/lib/data_requests/local/fetch_cloudwatch_logs.rb b/lib/data_requests/local/fetch_cloudwatch_logs.rb index 4b0910e2de8..733d155cf37 100644 --- a/lib/data_requests/local/fetch_cloudwatch_logs.rb +++ b/lib/data_requests/local/fetch_cloudwatch_logs.rb @@ -78,7 +78,7 @@ def cloudwatch_client def query_string <<~QUERY fields @timestamp, @message - | filter @message like /#{uuid}/ + | filter properties.user_id = '#{uuid}' and name != 'IRS Attempt API: Event metadata' QUERY end diff --git a/package.json b/package.json index 4546dd9abc0..d64adb8a949 100644 --- a/package.json +++ b/package.json @@ -61,8 +61,8 @@ "@types/react-dom": "^17.0.11", "@types/sinon": "^10.0.13", "@types/sinon-chai": "^3.2.8", - "@typescript-eslint/eslint-plugin": "^5.38.1", - "@typescript-eslint/parser": "^6.7.4", + "@typescript-eslint/eslint-plugin": "^6.7.5", + "@typescript-eslint/parser": "^6.7.5", "chai": "^4.3.10", "chai-as-promised": "^7.1.1", "clipboard-polyfill": "^3.0.3", diff --git a/scripts/changelog_check.rb b/scripts/changelog_check.rb index 6f214999a7c..e096ee3bd83 100755 --- a/scripts/changelog_check.rb +++ b/scripts/changelog_check.rb @@ -18,7 +18,7 @@ SECURITY_CHANGELOG = { category: 'Internal', subcategory: 'Dependencies', - change: 'Update dependencies to resolve security advisories', + change: 'Update dependencies to latest versions', }.freeze REVERT_CHANGELOG = { category: 'Bug Fixes', diff --git a/spec/components/base_component_spec.rb b/spec/components/base_component_spec.rb index 6c9a23989e5..f8eb34f2dba 100644 --- a/spec/components/base_component_spec.rb +++ b/spec/components/base_component_spec.rb @@ -1,11 +1,13 @@ require 'rails_helper' RSpec.describe BaseComponent, type: :component do + # rubocop:disable RSpec/LeakyConstantDeclaration class ExampleComponent < BaseComponent def call '' end end + # rubocop:enable RSpec/LeakyConstantDeclaration let(:view_context) { vc_test_controller.view_context } @@ -20,6 +22,7 @@ def call end context 'with sidecar script' do + # rubocop:disable RSpec/LeakyConstantDeclaration class ExampleComponentWithScript < BaseComponent def call '' @@ -32,7 +35,9 @@ def self.sidecar_files(extensions) files.presence || super(extensions) end end + # rubocop:enable RSpec/LeakyConstantDeclaration + # rubocop:disable RSpec/LeakyConstantDeclaration class ExampleComponentWithScriptRenderingOtherComponentWithScript < BaseComponent def call render(ExampleComponentWithScript.new) @@ -46,7 +51,9 @@ def self.sidecar_files(extensions) end end end + # rubocop:enable RSpec/LeakyConstantDeclaration + # rubocop:disable RSpec/LeakyConstantDeclaration class NestedExampleComponentWithScript < ExampleComponentWithScript def self.sidecar_files(extensions) if extensions.include?('js') @@ -56,6 +63,7 @@ def self.sidecar_files(extensions) end end end + # rubocop:enable RSpec/LeakyConstantDeclaration it 'adds script to class variable when rendered' do expect(view_context).to receive(:enqueue_component_scripts).with( @@ -98,6 +106,7 @@ def self.sidecar_files(extensions) end context 'with sidecar stylesheet' do + # rubocop:disable RSpec/LeakyConstantDeclaration class ExampleComponentWithStylesheet < BaseComponent def call '' @@ -109,7 +118,9 @@ def self.sidecar_files(extensions) files.presence || super(extensions) end end + # rubocop:enable RSpec/LeakyConstantDeclaration + # rubocop:disable RSpec/LeakyConstantDeclaration class ExampleComponentWithStylesheetRenderingOtherComponentWithStylesheet < BaseComponent def call render(ExampleComponentWithStylesheet.new) @@ -123,7 +134,9 @@ def self.sidecar_files(extensions) end end end + # rubocop:enable RSpec/LeakyConstantDeclaration + # rubocop:disable RSpec/LeakyConstantDeclaration class NestedExampleComponentWithStylesheet < ExampleComponentWithStylesheet def self.sidecar_files(extensions) if extensions.include?('scss') @@ -133,6 +146,7 @@ def self.sidecar_files(extensions) end end end + # rubocop:enable RSpec/LeakyConstantDeclaration it 'adds script to class variable when rendered' do expect(view_context).to receive(:enqueue_component_stylesheets).with( diff --git a/spec/components/block_link_component_spec.rb b/spec/components/block_link_component_spec.rb index f42393119b7..f3b9ece14a2 100644 --- a/spec/components/block_link_component_spec.rb +++ b/spec/components/block_link_component_spec.rb @@ -30,6 +30,7 @@ end context 'with custom renderer' do + # rubocop:disable RSpec/LeakyConstantDeclaration class ExampleBlockLinkCustomRendererComponent < BaseComponent def initialize(href:, **) @href = href @@ -39,6 +40,7 @@ def call content_tag(:button, "Example #{content.strip}", data: { href: @href }) end end + # rubocop:enable RSpec/LeakyConstantDeclaration it 'renders using the custom renderer' do rendered = render_inline BlockLinkComponent.new( diff --git a/spec/controllers/concerns/idv/ab_test_analytics_concern_spec.rb b/spec/controllers/concerns/idv/ab_test_analytics_concern_spec.rb index 7a8c8b9cd34..f14e4bc47b3 100644 --- a/spec/controllers/concerns/idv/ab_test_analytics_concern_spec.rb +++ b/spec/controllers/concerns/idv/ab_test_analytics_concern_spec.rb @@ -1,19 +1,14 @@ require 'rails_helper' -RSpec.describe 'AbTestAnalyticsConcern' do - module Idv - class StepController < ApplicationController - include AbTestAnalyticsConcern - end - end - +RSpec.describe Idv::AbTestAnalyticsConcern do let(:user) { create(:user) } let(:idv_session) do Idv::Session.new(user_session: subject.user_session, current_user: user, service_provider: nil) end describe '#ab_test_analytics_buckets' do - controller Idv::StepController do + controller(ApplicationController) do + include Idv::AbTestAnalyticsConcern end let(:acuant_sdk_args) { { as_bucket: :as_value } } diff --git a/spec/controllers/concerns/idv_step_concern_spec.rb b/spec/controllers/concerns/idv_step_concern_spec.rb index f9c8d2351de..b2c2ff8810a 100644 --- a/spec/controllers/concerns/idv_step_concern_spec.rb +++ b/spec/controllers/concerns/idv_step_concern_spec.rb @@ -6,26 +6,28 @@ Idv::Session.new(user_session: subject.user_session, current_user: user, service_provider: nil) end - module Idv - class StepController < ApplicationController - include IdvStepConcern + idv_step_controller_class = Class.new(ApplicationController) do + def self.name + 'AnonymousController' + end - def show - render plain: 'Hello' - end + include IdvStepConcern + + def show + render plain: 'Hello' end end describe 'before_actions' do it 'includes handle_fraud' do - expect(Idv::StepController).to have_actions( + expect(idv_step_controller_class).to have_actions( :before, :handle_fraud, ) end it 'includes check_for_mail_only_outage before_action' do - expect(Idv::StepController).to have_actions( + expect(idv_step_controller_class).to have_actions( :before, :check_for_mail_only_outage, ) @@ -33,14 +35,14 @@ def show end describe '#confirm_idv_needed' do - controller Idv::StepController do + controller(idv_step_controller_class) do before_action :confirm_idv_needed end before(:each) do sign_in(user) routes.draw do - get 'show' => 'idv/step#show' + get 'show' => 'anonymous#show' end end @@ -73,14 +75,14 @@ def show end describe '#confirm_address_step_complete' do - controller Idv::StepController do + controller(idv_step_controller_class) do before_action :confirm_address_step_complete end before(:each) do sign_in(user) routes.draw do - get 'show' => 'idv/step#show' + get 'show' => 'anonymous#show' end end @@ -131,14 +133,14 @@ def show end describe '#confirm_verify_info_step_complete' do - controller Idv::StepController do + controller(idv_step_controller_class) do before_action :confirm_verify_info_step_complete end before(:each) do sign_in(user) routes.draw do - get 'show' => 'idv/step#show' + get 'show' => 'anonymous#show' end end @@ -185,7 +187,7 @@ def show end describe '#confirm_no_pending_in_person_enrollment' do - controller Idv::StepController do + controller(idv_step_controller_class) do before_action :confirm_no_pending_in_person_enrollment end @@ -193,7 +195,7 @@ def show sign_in(user) allow(subject).to receive(:current_user).and_return(user) routes.draw do - get 'show' => 'idv/step#show' + get 'show' => 'anonymous#show' end end @@ -223,7 +225,7 @@ def show end describe '#confirm_no_pending_gpo_profile' do - controller Idv::StepController do + controller(idv_step_controller_class) do before_action :confirm_no_pending_gpo_profile end @@ -231,7 +233,7 @@ def show sign_in(user) allow(subject).to receive(:current_user).and_return(user) routes.draw do - get 'show' => 'idv/step#show' + get 'show' => 'anonymous#show' end end diff --git a/spec/controllers/concerns/rate_limit_concern_spec.rb b/spec/controllers/concerns/rate_limit_concern_spec.rb index 6edf7982bdf..69c14e681d8 100644 --- a/spec/controllers/concerns/rate_limit_concern_spec.rb +++ b/spec/controllers/concerns/rate_limit_concern_spec.rb @@ -3,23 +3,25 @@ RSpec.describe 'RateLimitConcern' do let(:user) { create(:user, :fully_registered, email: 'old_email@example.com') } - module Idv - class StepController < ApplicationController - include RateLimitConcern - include IdvSession + idv_step_controller_class = Class.new(ApplicationController) do + def self.name + 'AnonymousController' + end - def show - render plain: 'Hello' - end + include RateLimitConcern + include IdvSession - def update - render plain: 'Bye' - end + def show + render plain: 'Hello' + end + + def update + render plain: 'Bye' end end describe '#confirm_not_rate_limited' do - controller Idv::StepController do + controller(idv_step_controller_class) do before_action :confirm_not_rate_limited end @@ -27,8 +29,8 @@ def update sign_in(user) allow(subject).to receive(:current_user).and_return(user) routes.draw do - get 'show' => 'idv/step#show' - put 'update' => 'idv/step#update' + get 'show' => 'anonymous#show' + put 'update' => 'anonymous#update' end end @@ -66,15 +68,55 @@ def update end context 'with proof_address rate_limiter (PhoneStep)' do - before do - rate_limiter = RateLimiter.new(user: user, rate_limit_type: :proof_address) - rate_limiter.increment_to_limited! + context 'when the user is phone rate limited' do + before do + rate_limiter = RateLimiter.new(user: user, rate_limit_type: :proof_address) + rate_limiter.increment_to_limited! + end + + it 'does not redirect' do + get :show + + expect(response.body).to eq 'Hello' + expect(response.status).to eq 200 + end end - it 'redirects to proof_address rate limited error page' do - get :show + context 'when the user is mail rate limited' do + before do + create( + :profile, + :verification_cancelled, + :letter_sends_rate_limited, + user: user, + ) + end + + it 'does not redirect' do + get :show - expect(response).to redirect_to idv_phone_errors_failure_url + expect(response.body).to eq 'Hello' + expect(response.status).to eq 200 + end + end + + context 'when the user is phone and mail rate limited' do + before do + create( + :profile, + :verification_cancelled, + :letter_sends_rate_limited, + user: user, + ) + rate_limiter = RateLimiter.new(user: user, rate_limit_type: :proof_address) + rate_limiter.increment_to_limited! + end + + it 'redirects to proof_address rate limited error page' do + get :show + + expect(response).to redirect_to idv_phone_errors_failure_url + end end end @@ -100,7 +142,7 @@ def update end describe '#confirm_not_rate_limited_after_doc_auth' do - controller Idv::StepController do + controller(idv_step_controller_class) do before_action :confirm_not_rate_limited_after_doc_auth end @@ -108,8 +150,8 @@ def update sign_in(user) allow(subject).to receive(:current_user).and_return(user) routes.draw do - get 'show' => 'idv/step#show' - put 'update' => 'idv/step#update' + get 'show' => 'anonymous#show' + put 'update' => 'anonymous#update' end end @@ -131,21 +173,21 @@ def update end end - describe '#confirm_not_rate_limited_after_idv_resolution' do - controller Idv::StepController do - before_action :confirm_not_rate_limited_after_idv_resolution + describe '#confirm_not_rate_limited_for_phone_address_verification' do + controller(idv_step_controller_class) do + before_action :confirm_not_rate_limited_for_phone_address_verification end before(:each) do sign_in(user) allow(subject).to receive(:current_user).and_return(user) routes.draw do - get 'show' => 'idv/step#show' - put 'update' => 'idv/step#update' + get 'show' => 'anonymous#show' + put 'update' => 'anonymous#update' end end - it 'redirects if the user is rate limited for a step after idv resolution' do + it 'redirects if the user is rate limited for phone address verification' do RateLimiter.new(user: user, rate_limit_type: :proof_address).increment_to_limited! get :show @@ -162,5 +204,14 @@ def update expect(response.body).to eq 'Hello' expect(response.status).to eq 200 end + + it 'does not redirect if the user is rate limited for mail' do + create(:profile, :verification_cancelled, :letter_sends_rate_limited, user: user) + + get :show + + expect(response.body).to eq 'Hello' + expect(response.status).to eq 200 + end end end diff --git a/spec/controllers/idv/personal_key_controller_spec.rb b/spec/controllers/idv/personal_key_controller_spec.rb index a10b2ce458a..fef9bfa662f 100644 --- a/spec/controllers/idv/personal_key_controller_spec.rb +++ b/spec/controllers/idv/personal_key_controller_spec.rb @@ -205,7 +205,7 @@ def index expect(@analytics).to have_logged_event( 'IdV: personal key submitted', - address_verification_method: nil, + address_verification_method: 'phone', fraud_review_pending: false, fraud_rejection: false, in_person_verification_pending: false, @@ -249,7 +249,7 @@ def index expect(@analytics).to have_logged_event( 'IdV: personal key submitted', - address_verification_method: nil, + address_verification_method: 'phone', fraud_review_pending: false, fraud_rejection: false, deactivation_reason: nil, @@ -277,7 +277,7 @@ def index expect(@analytics).to have_logged_event( 'IdV: personal key submitted', - address_verification_method: nil, + address_verification_method: 'phone', fraud_review_pending: false, fraud_rejection: false, in_person_verification_pending: false, @@ -306,7 +306,7 @@ def index 'IdV: personal key submitted', fraud_review_pending: true, fraud_rejection: false, - address_verification_method: nil, + address_verification_method: 'phone', in_person_verification_pending: false, deactivation_reason: nil, proofing_components: nil, diff --git a/spec/controllers/idv/phone_errors_controller_spec.rb b/spec/controllers/idv/phone_errors_controller_spec.rb index 009c40cbc8c..c0b89832fa0 100644 --- a/spec/controllers/idv/phone_errors_controller_spec.rb +++ b/spec/controllers/idv/phone_errors_controller_spec.rb @@ -87,22 +87,13 @@ end end - context 'the user is not authenticated and not recovering their account' do + context 'the user is not authenticated' do let(:user) { nil } it 'redirects to sign in' do get action expect(response).to redirect_to(new_user_session_url) end - it 'does not log an event' do - expect(@analytics).not_to receive(:track_event).with( - 'IdV: phone error visited', - hash_including( - type: action, - ), - ) - get action - end end end @@ -235,18 +226,16 @@ describe '#failure' do let(:action) { :failure } - let(:template) { 'idv/phone_errors/failure' } - - it_behaves_like 'an idv phone errors controller action' context 'while rate limited' do let(:user) { create(:user) } - it 'assigns expiration time' do + it 'renders an error and assigns expiration time' do RateLimiter.new(rate_limit_type: :proof_address, user: user).increment_to_limited! get action expect(assigns(:expires_at)).to be_kind_of(Time) + expect(response).to render_template('idv/phone_errors/failure') end it 'logs an event' do @@ -265,6 +254,44 @@ ) end end + + context 'fetch() request from form-steps-wait JS' do + before do + request.headers['X-Form-Steps-Wait'] = '1' + end + + it 'returns an empty response' do + get action + expect(response).to have_http_status(204) + end + + it 'does not log an event' do + expect(@analytics).not_to receive(:track_event).with( + 'IdV: phone error visited', + anything, + ) + get action + end + end + end + + context 'while not rate limited' do + let(:user) { create(:user) } + + it 'redirects to the phone step' do + get action + + expect(response).to redirect_to(idv_phone_url) + end + end + + context 'the user is not authenticated' do + let(:user) { nil } + it 'redirects to sign in' do + get action + + expect(response).to redirect_to(new_user_session_url) + end end end end diff --git a/spec/controllers/idv/review_controller_spec.rb b/spec/controllers/idv/review_controller_spec.rb index b63de98cbdd..cc8521fbaf4 100644 --- a/spec/controllers/idv/review_controller_spec.rb +++ b/spec/controllers/idv/review_controller_spec.rb @@ -620,9 +620,6 @@ def show let(:applicant) do Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE end - let(:stub_idv_session) do - stub_user_with_applicant_data(user, applicant) - end before do allow(IdentityConfig.store).to receive(:proofing_device_profiling). diff --git a/spec/controllers/idv_controller_spec.rb b/spec/controllers/idv_controller_spec.rb index f1ef9da9402..64c5758e36f 100644 --- a/spec/controllers/idv_controller_spec.rb +++ b/spec/controllers/idv_controller_spec.rb @@ -27,7 +27,7 @@ get :index end - it 'redirects to sad face page if fraud review is pending' do + it 'redirects to please call page if fraud review is pending' do profile = create(:profile, :fraud_review_pending) stub_sign_in(profile.user) @@ -105,7 +105,46 @@ stub_sign_in(profile.user) end - it 'redirects to rate limited page' do + it 'redirects the user to start proofing' do + get :index + + expect(response).to redirect_to idv_welcome_url + end + end + + context 'if the number of letter sends has been exceeded' do + before do + user = create(:user) + profile = create( + :profile, + :letter_sends_rate_limited, + user: user, + ) + + stub_sign_in(profile.user) + end + + it 'redirects the user to start proofing' do + get :index + + expect(response).to redirect_to idv_welcome_url + end + end + + context 'if the number of letter sends and phone attempts have been exceeded' do + before do + user = create(:user) + profile = create( + :profile, + :letter_sends_rate_limited, + user: user, + ) + RateLimiter.new(rate_limit_type: :proof_address, user: user).increment_to_limited! + + stub_sign_in(profile.user) + end + + it 'redirects to failure page' do get :index expect(response).to redirect_to idv_phone_errors_failure_url diff --git a/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb index d1a14d10f8d..9002c041bbf 100644 --- a/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb @@ -105,7 +105,7 @@ } expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: enter PIV CAC visited', attributes) + with(:multi_factor_auth_enter_piv_cac, attributes) submit_attributes = { success: true, @@ -203,7 +203,7 @@ } expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: enter PIV CAC visited', attributes) + with(:multi_factor_auth_enter_piv_cac, attributes) expect(@irs_attempts_api_tracker).to receive(:mfa_login_rate_limited). with(mfa_device_type: 'piv_cac') diff --git a/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb b/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb index 78deccdc71b..32c56dd516a 100644 --- a/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb +++ b/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb @@ -108,7 +108,7 @@ it 'tracks the analytic event of visited' do stub_analytics expect(@analytics).to receive(:track_event). - with('PIV CAC setup visited', { + with(:piv_cac_setup_visited, { in_account_creation_flow: false, enabled_mfa_methods_count: 1, }) diff --git a/spec/controllers/users/piv_cac_login_controller_spec.rb b/spec/controllers/users/piv_cac_login_controller_spec.rb index ca92bde1d75..9a0571a3405 100644 --- a/spec/controllers/users/piv_cac_login_controller_spec.rb +++ b/spec/controllers/users/piv_cac_login_controller_spec.rb @@ -10,10 +10,9 @@ context 'without a token' do before { get :new } - it 'tracks the piv_cac setup' do + it 'tracks the piv cac login' do expect(@analytics).to have_received(:track_event).with( - 'PIV CAC setup visited', - in_account_creation_flow: false, + :piv_cac_login_visited, ) end @@ -29,7 +28,7 @@ before { get :new, params: { token: token } } it 'tracks the login attempt' do expect(@analytics).to have_received(:track_event).with( - 'PIV/CAC Login', + :piv_cac_login, { errors: {}, key_id: nil, @@ -74,7 +73,7 @@ it 'tracks the login attempt' do expect(@analytics).to have_received(:track_event).with( - 'PIV/CAC Login', + :piv_cac_login, { errors: { type: 'user.not_found', @@ -113,7 +112,7 @@ it 'tracks the login attempt' do expect(@analytics).to have_received(:track_event).with( - 'PIV/CAC Login', + :piv_cac_login, { errors: {}, key_id: nil, diff --git a/spec/decorators/service_provider_session_spec.rb b/spec/decorators/service_provider_session_spec.rb index 3afc96504d0..9c0f173842c 100644 --- a/spec/decorators/service_provider_session_spec.rb +++ b/spec/decorators/service_provider_session_spec.rb @@ -46,7 +46,7 @@ context 'sp has custom alert' do it 'uses the custom template' do expect(subject.sp_alert('sign_in')). - to eq "custom sign in help text for #{sp.friendly_name}" + to eq "custom sign in help text for #{sp.friendly_name}" end end diff --git a/spec/factories/profiles.rb b/spec/factories/profiles.rb index 8d05154518e..2562195c2c6 100644 --- a/spec/factories/profiles.rb +++ b/spec/factories/profiles.rb @@ -70,6 +70,12 @@ pii { Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE } end + trait :letter_sends_rate_limited do + gpo_confirmation_codes do + build_list(:gpo_confirmation_code, IdentityConfig.store.max_mail_events) + end + end + after(:build) do |profile, evaluator| if evaluator.pii pii_attrs = Pii::Attributes.new_from_hash(evaluator.pii) diff --git a/spec/factories/service_providers.rb b/spec/factories/service_providers.rb index bd27d47121e..bc77f9885bc 100644 --- a/spec/factories/service_providers.rb +++ b/spec/factories/service_providers.rb @@ -8,9 +8,11 @@ return_to_sp_url { '/' } agency { association :agency } help_text do - { sign_in: { en: 'custom sign in help text for %{sp_name}' }, - sign_up: { en: 'custom sign up help text for %{sp_name}' }, - forgot_password: { en: 'custom forgot password help text for %{sp_name}' } } + { sign_in: { en: 'custom sign in help text for %{sp_name}' }, + sign_up: { en: 'custom sign up help text for %{sp_name}' }, + forgot_password: { + en: 'custom forgot password help text for %{sp_name}', + } } end trait :without_help_text do diff --git a/spec/features/idv/end_to_end_idv_spec.rb b/spec/features/idv/end_to_end_idv_spec.rb index 4c88b222624..8d0e77909d0 100644 --- a/spec/features/idv/end_to_end_idv_spec.rb +++ b/spec/features/idv/end_to_end_idv_spec.rb @@ -276,10 +276,32 @@ def validate_review_submit(user) def validate_come_back_later_page expect(page).to have_current_path(idv_letter_enqueued_path) expect_in_person_gpo_step_indicator_current_step(t('step_indicator.flows.idv.get_a_letter')) + expect(page).to have_content(t('idv.titles.come_back_later')) + expect(page).not_to have_content(t('step_indicator.flows.idv.verify_phone_or_address')) end def validate_personal_key_page expect(current_path).to eq idv_personal_key_path + + # Clicking acknowledge checkbox is required to continue + click_continue + expect(page).to have_content(t('forms.validation.required_checkbox')) + expect(current_path).to eq(idv_personal_key_path) + + expect(page).to have_content(t('forms.personal_key_partial.acknowledgement.header')) + expect(page).to have_content(t('forms.personal_key_partial.acknowledgement.text')) + expect(page).to have_content(t('forms.personal_key_partial.acknowledgement.help_link_text')) + expect(page).to have_content(t('idv.messages.confirm')) + expect_step_indicator_current_step(t('step_indicator.flows.idv.secure_account')) + expect(page).to have_css( + '.step-indicator__step--complete', + text: t('step_indicator.flows.idv.verify_phone_or_address'), + ) + expect(page).not_to have_content(t('step_indicator.flows.idv.get_a_letter')) + + # Refreshing shows same page (BUT with new personal key, we should warn the user) + visit current_path + expect(page).not_to have_content(t('idv.messages.confirm')) expect(page).to have_content(t('forms.personal_key_partial.acknowledgement.header')) end diff --git a/spec/features/idv/phone_errors_spec.rb b/spec/features/idv/phone_errors_spec.rb index fa19f851ab6..f9f3d5cdf48 100644 --- a/spec/features/idv/phone_errors_spec.rb +++ b/spec/features/idv/phone_errors_spec.rb @@ -13,8 +13,8 @@ verify_phone_submitted(idv_phone_errors_warning_url, idv_phone_errors_warning_path) end - it 'only renders failure after phone has been submitted' do - verify_phone_submitted(idv_phone_errors_failure_url, idv_phone_errors_failure_path) + it 'only renders timeout after phone has been submitted' do + verify_phone_submitted(idv_phone_errors_timeout_url, idv_phone_errors_timeout_path) end it 'only renders jobfail after phone has been submitted' do diff --git a/spec/features/idv/proof_address_rate_limit_spec.rb b/spec/features/idv/proof_address_rate_limit_spec.rb new file mode 100644 index 00000000000..83665cf7a00 --- /dev/null +++ b/spec/features/idv/proof_address_rate_limit_spec.rb @@ -0,0 +1,92 @@ +require 'rails_helper' + +RSpec.feature 'address proofing rate limit' do + include IdvStepHelper + include IdvHelper + + context 'a user is phone rate limited' do + scenario 'the user does not encounter an error until phone entry and can verify by mail', :js do + user = user_with_2fa + RateLimiter.new(user: user, rate_limit_type: :proof_address).increment_to_limited! + + start_idv_from_sp + complete_idv_steps_before_phone_step(user) + + expect(current_path).to eq(idv_phone_errors_failure_path) + + click_on t('idv.failure.phone.rate_limited.gpo.button') + click_on t('idv.buttons.mail.send') + + expect(page).to have_content(t('idv.titles.session.review', app_name: APP_NAME)) + expect(current_path).to eq(idv_review_path) + fill_in 'Password', with: user.password + click_idv_continue + expect(page).to have_current_path(idv_letter_enqueued_path) + end + end + + context 'a user is mail limited' do + scenario 'the user can verify by phone but does not have the mail option', :js do + profile = create( + :profile, + :verify_by_mail_pending, + :with_pii, + :verification_cancelled, + :letter_sends_rate_limited, + ) + user = profile.user + + start_idv_from_sp + complete_idv_steps_before_phone_step(user) + + # There should be no option to verify by mail on the phone input screen + expect(page).to_not have_content(t('idv.troubleshooting.options.verify_by_mail')) + + fill_out_phone_form_fail + click_idv_send_security_code + + # There should be no option to verify by mail on the warning page + expect(current_path).to eq(idv_phone_errors_warning_path) + expect(page).to_not have_content(t('idv.failure.phone.warning.gpo.button')) + + # Visiting the letter request URL should redirect to phone + visit idv_request_letter_path + expect(current_path).to eq(idv_phone_path) + + fill_out_phone_form_ok + click_idv_send_security_code + fill_in_code_with_last_phone_otp + click_submit_default + + expect(page).to have_content(t('idv.titles.session.review', app_name: APP_NAME)) + expect(current_path).to eq(idv_review_path) + fill_in 'Password', with: user.password + click_idv_continue + expect(current_path).to eq(idv_personal_key_path) + expect(user.reload.active_profile.present?).to eq(true) + end + end + + context 'a user is phone rate limited and mail rate limited', :js do + scenario 'the user is not able to start proofing' do + user = create( + :profile, + :verify_by_mail_pending, + :with_pii, + :verification_cancelled, + :letter_sends_rate_limited, + ).user + RateLimiter.new(user: user, rate_limit_type: :proof_address).increment_to_limited! + + start_idv_from_sp + sign_in_live_with_2fa(user) + + expect(current_path).to eq(idv_phone_errors_failure_path) + expect(page).to_not have_content(t('idv.failure.phone.warning.gpo.button')) + + # Visiting the letter request URL should redirect to phone failure + visit idv_request_letter_path + expect(current_path).to eq(idv_phone_errors_failure_path) + end + end +end diff --git a/spec/features/idv/steps/confirmation_step_spec.rb b/spec/features/idv/steps/confirmation_step_spec.rb deleted file mode 100644 index e3e4571d74d..00000000000 --- a/spec/features/idv/steps/confirmation_step_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -require 'rails_helper' - -RSpec.feature 'idv confirmation step', js: true do - include IdvStepHelper - - let(:sp) { nil } - let(:address_verification_mechanism) { :phone } - - before do - start_idv_from_sp(sp) - complete_idv_steps_before_confirmation_step(address_verification_mechanism) - end - - it 'shows status content for phone verification progress' do - expect(page).to have_content(t('idv.messages.confirm')) - expect_step_indicator_current_step(t('step_indicator.flows.idv.secure_account')) - expect(page).to have_css( - '.step-indicator__step--complete', - text: t('step_indicator.flows.idv.verify_phone_or_address'), - ) - expect(page).not_to have_content(t('step_indicator.flows.idv.get_a_letter')) - end - - it 'allows the user to refresh and still displays the personal key' do - # Visit the current path is the same as refreshing - expect(page).to have_content(t('idv.messages.confirm')) - expect(page).to have_content(t('forms.personal_key_partial.acknowledgement.header')) - visit current_path - expect(page).not_to have_content(t('idv.messages.confirm')) - expect(page).to have_content(t('forms.personal_key_partial.acknowledgement.header')) - end - - it 'displays information providing details about personal key' do - expect(page).to have_content(t('forms.personal_key_partial.acknowledgement.text')) - expect(page).to have_content(t('forms.personal_key_partial.acknowledgement.help_link_text')) - end - - context 'verifying by gpo' do - let(:address_verification_mechanism) { :gpo } - - it 'shows status content for gpo verification progress' do - expect(page).to have_content(t('idv.titles.come_back_later')) - expect(page).to have_content(t('step_indicator.flows.idv.get_a_letter')) - expect(page).not_to have_content(t('step_indicator.flows.idv.verify_phone_or_address')) - end - end - - context 'with associated sp' do - let(:sp) { :oidc } - - it "forces the user to click the 'acknowledge' checkbox before proceeding" do - click_continue - - expect(page).to have_content(t('forms.validation.required_checkbox')) - expect(current_path).to eq(idv_personal_key_path) - - acknowledge_and_confirm_personal_key - expect(page).to have_current_path(sign_up_completed_path) - end - - it 'redirects to the completions page and then to the SP' do - acknowledge_and_confirm_personal_key - - expect(page).to have_current_path(sign_up_completed_path) - - click_agree_and_continue - - expect(current_url).to start_with('http://localhost:7654/auth/result') - end - end -end diff --git a/spec/features/idv/steps/gpo_otp_verification_step_spec.rb b/spec/features/idv/steps/gpo_otp_verification_step_spec.rb index dc5f0560a3b..f28ec15ac44 100644 --- a/spec/features/idv/steps/gpo_otp_verification_step_spec.rb +++ b/spec/features/idv/steps/gpo_otp_verification_step_spec.rb @@ -85,6 +85,7 @@ context 'coming from an "I did not receive my letter" link in a reminder email' do it 'renders an alternate ui', :js do visit idv_verify_by_mail_enter_code_url(did_not_receive_letter: 1) + verify_no_spam_warning_banner expect(current_path).to eql(new_user_session_path) fill_in_credentials_and_submit(user.email, user.password) @@ -98,60 +99,88 @@ end end - context 'with gpo personal key after verification' do - it 'shows the user a personal key after verification' do - sign_in_live_with_2fa(user) + context 'has gpo_confirmation_code sent before present day' do + before do + gpo_confirmation_code.update!(updated_at: Time.zone.now - 1.day) + end + context 'with gpo personal key after verification' do + it 'shows the user a personal key after verification' do + sign_in_live_with_2fa(user) - expect(current_path).to eq idv_verify_by_mail_enter_code_path - expect(page).to have_content t('idv.messages.gpo.resend') + expect(current_path).to eq idv_verify_by_mail_enter_code_path + verify_no_spam_warning_banner + expect(page).to have_content t('idv.messages.gpo.resend') - gpo_confirmation_code - fill_in t('idv.gpo.form.otp_label'), with: otp - click_button t('idv.gpo.form.submit') + fill_in t('idv.gpo.form.otp_label'), with: otp + click_button t('idv.gpo.form.submit') - profile.reload + profile.reload - expect(page).to have_current_path(idv_personal_key_path) - expect(page).to have_content(t('account.index.verification.success')) - expect(page).to have_content(t('step_indicator.flows.idv.get_a_letter')) + expect(page).to have_current_path(idv_personal_key_path) + expect(page).to have_content(t('account.index.verification.success')) + expect(page).to have_content(t('step_indicator.flows.idv.get_a_letter')) - expect(profile.active).to be(true) - expect(profile.deactivation_reason).to be(nil) + expect(profile.active).to be(true) + expect(profile.deactivation_reason).to be(nil) - expect(user.events.account_verified.size).to eq 1 + expect(user.events.account_verified.size).to eq 1 + end end - end - context 'with gpo feature disabled' do - before do - allow(IdentityConfig.store).to receive(:gpo_verification_enabled?).and_return(true) + context 'with gpo feature disabled' do + before do + allow(IdentityConfig.store).to receive(:gpo_verification_enabled?).and_return(true) + end + + it 'allows a user to verify their account for an existing pending profile' do + sign_in_live_with_2fa(user) + + expect(current_path).to eq idv_verify_by_mail_enter_code_path + expect(page).to have_content t('idv.messages.gpo.resend') + + verify_no_spam_warning_banner + gpo_confirmation_code + fill_in t('idv.gpo.form.otp_label'), with: otp + click_button t('idv.gpo.form.submit') + + expect(user.events.account_verified.size).to eq 1 + expect(page).to_not have_content(t('account.index.verification.reactivate_button')) + end end - it 'allows a user to verify their account for an existing pending profile' do + it 'allows a user to cancel and start over within the banner' do sign_in_live_with_2fa(user) expect(current_path).to eq idv_verify_by_mail_enter_code_path - expect(page).to have_content t('idv.messages.gpo.resend') + expect(page).to have_content t('idv.gpo.alert_info') + expect(page).to have_content strip_tags(t('idv.gpo.change_to_verification_code_html')) + expect(page).to have_content t('idv.gpo.wrong_address') + expect(page).to have_content Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE[:address1] + verify_no_spam_warning_banner + + click_on t('idv.gpo.clear_and_start_over') - gpo_confirmation_code - fill_in t('idv.gpo.form.otp_label'), with: otp - click_button t('idv.gpo.form.submit') + expect(current_path).to eq idv_confirm_start_over_path - expect(user.events.account_verified.size).to eq 1 - expect(page).to_not have_content(t('account.index.verification.reactivate_button')) + click_idv_continue + + expect(current_path).to eq idv_welcome_path end end - it 'allows a user to cancel and start over within the banner' do + it 'allows a user to cancel and start over in the footer' do + gpo_confirmation_code + another_gpo_confirmation_code = create( + :gpo_confirmation_code, + profile: profile, + otp_fingerprint: Pii::Fingerprinter.fingerprint(otp), + ) sign_in_live_with_2fa(user) expect(current_path).to eq idv_verify_by_mail_enter_code_path - expect(page).to have_content t('idv.gpo.alert_info') - expect(page).to have_content strip_tags(t('idv.gpo.change_to_verification_code_html')) - expect(page).to have_content t('idv.gpo.wrong_address') - expect(page).to have_content Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE[:address1] + verify_spam_warning_banner_present(another_gpo_confirmation_code.updated_at) - click_on t('idv.gpo.clear_and_start_over') + click_on t('idv.messages.clear_and_start_over') expect(current_path).to eq idv_confirm_start_over_path @@ -160,16 +189,41 @@ expect(current_path).to eq idv_welcome_path end - it 'allows a user to cancel and start over in the footer' do - sign_in_live_with_2fa(user) + context 'user cancels idv from enter code page after getting rate limited', :js do + it 'redirects to welcome page' do + RateLimiter.new(user: user, rate_limit_type: :proof_address).increment_to_limited! - expect(current_path).to eq idv_verify_by_mail_enter_code_path - click_on t('idv.messages.clear_and_start_over') + sign_in_live_with_2fa(user) - expect(current_path).to eq idv_confirm_start_over_path + click_on t('idv.messages.clear_and_start_over') + expect(current_path).to eq idv_confirm_start_over_path + click_idv_continue - click_idv_continue + expect(current_path).to eq idv_welcome_path + end + end - expect(current_path).to eq idv_welcome_path + def verify_no_spam_warning_banner + expect(page).not_to have_content( + t( + 'idv.gpo.alert_spam_warning_html', + date_letter_was_sent: I18n.l( + Time.zone.now, + format: :event_date, + ), + ).split('').first, + ) + end + + def verify_spam_warning_banner_present(code_sent_at = Time.zone.now) + expect(page).to have_content strip_tags( + t( + 'idv.gpo.alert_spam_warning_html', + date_letter_was_sent: I18n.l( + code_sent_at, + format: :event_date, + ), + ), + ) end end diff --git a/spec/features/remember_device/sp_expiration_spec.rb b/spec/features/remember_device/sp_expiration_spec.rb index 333ce427294..99af39df96f 100644 --- a/spec/features/remember_device/sp_expiration_spec.rb +++ b/spec/features/remember_device/sp_expiration_spec.rb @@ -66,7 +66,7 @@ def visit_sp(protocol, aal) end it 'does require MFA when AAL2 request is sent after configured AAL2 timeframe' do - travel_to(AAL2_REMEMBER_DEVICE_EXPIRATION.from_now + 1.day) do + travel_to(expiration_time.from_now + 1.day) do visit_idp_from_sp_with_ial1_aal2(protocol) sign_in_user(user) @@ -85,9 +85,11 @@ def visit_sp(protocol, aal) RSpec.feature 'remember device sp expiration' do include SamlAuthHelper - AAL1_REMEMBER_DEVICE_EXPIRATION = + + aal1_remember_device_expiration = IdentityConfig.store.remember_device_expiration_hours_aal_1.hours - AAL2_REMEMBER_DEVICE_EXPIRATION = + + aal2_remember_device_expiration = IdentityConfig.store.remember_device_expiration_minutes_aal_2.minutes let(:user) do @@ -109,7 +111,7 @@ def visit_sp(protocol, aal) before do allow(IdentityConfig.store).to receive(:otp_delivery_blocklist_maxretry).and_return(1000) allow(IdentityConfig.store).to receive(:second_mfa_reminder_account_age_in_days). - and_return([AAL1_REMEMBER_DEVICE_EXPIRATION, AAL2_REMEMBER_DEVICE_EXPIRATION].max.in_days + 2) + and_return([aal1_remember_device_expiration, aal2_remember_device_expiration].max.in_days + 2) ServiceProvider.find_by(issuer: OidcAuthHelper::OIDC_IAL1_ISSUER).update!( default_aal: aal, @@ -126,9 +128,9 @@ def visit_sp(protocol, aal) let(:aal) { 2 } let(:ial) { 1 } - it_behaves_like 'expiring remember device for an sp config', AAL2_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal2_remember_device_expiration, :oidc - it_behaves_like 'expiring remember device for an sp config', AAL2_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal2_remember_device_expiration, :saml end @@ -136,9 +138,9 @@ def visit_sp(protocol, aal) let(:aal) { 1 } let(:ial) { 2 } - it_behaves_like 'expiring remember device for an sp config', AAL2_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal2_remember_device_expiration, :oidc - it_behaves_like 'expiring remember device for an sp config', AAL2_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal2_remember_device_expiration, :saml end @@ -146,9 +148,9 @@ def visit_sp(protocol, aal) let(:aal) { 2 } let(:ial) { 2 } - it_behaves_like 'expiring remember device for an sp config', AAL2_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal2_remember_device_expiration, :oidc - it_behaves_like 'expiring remember device for an sp config', AAL2_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal2_remember_device_expiration, :saml end @@ -156,9 +158,9 @@ def visit_sp(protocol, aal) let(:aal) { 1 } let(:ial) { 1 } - it_behaves_like 'expiring remember device for an sp config', AAL1_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal1_remember_device_expiration, :oidc - it_behaves_like 'expiring remember device for an sp config', AAL1_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal1_remember_device_expiration, :saml end @@ -166,9 +168,9 @@ def visit_sp(protocol, aal) let(:aal) { 1 } let(:ial) { 1 } - it_behaves_like 'expiring remember device for an sp config', AAL2_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal2_remember_device_expiration, :oidc, 2 - it_behaves_like 'expiring remember device for an sp config', AAL2_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal2_remember_device_expiration, :saml, 2 end end diff --git a/spec/fixtures/git_log_changelog.yml b/spec/fixtures/git_log_changelog.yml index 08b34519a77..4cecbe2d4f8 100644 --- a/spec/fixtures/git_log_changelog.yml +++ b/spec/fixtures/git_log_changelog.yml @@ -130,3 +130,18 @@ commit_changelog_with_commas_in_change: pr_number: '7000' commit_messages: - 'changelog:Internal,Changelog,Allow listing one, two, and more items with commas' +dependabot_dependency_update: + commit_log: | + title: Bump libphonenumber-js from 1.10.46 to 1.10.47 (#9332) + body:... + + Signed-off-by: dependabot[bot] + DELIMITER + title: Bump libphonenumber-js from 1.10.46 to 1.10.47 (#9332) + category: Internal + subcategory: Dependencies + change: Update dependencies to latest versions + pr_number: '9332' + commit_messages: + - '...' + - 'Signed-off-by: dependabot[bot] ' diff --git a/spec/jobs/reports/monthly_key_metrics_report_spec.rb b/spec/jobs/reports/monthly_key_metrics_report_spec.rb index ae244517381..897992e888a 100644 --- a/spec/jobs/reports/monthly_key_metrics_report_spec.rb +++ b/spec/jobs/reports/monthly_key_metrics_report_spec.rb @@ -11,14 +11,16 @@ let(:report_folder) do 'int/monthly-key-metrics-report/2021/2021-03-02.monthly-key-metrics-report' end - let(:account_reuse_s3_path) do - "#{report_folder}/account_reuse.csv" - end - let(:total_profiles_s3_path) do - "#{report_folder}/total_profiles.csv" - end - let(:account_deletion_rate_s3_path) do - "#{report_folder}/account_deletion_rate.csv" + let(:account_reuse_s3_path) { "#{report_folder}/account_reuse.csv" } + let(:total_profiles_s3_path) { "#{report_folder}/total_profiles.csv" } + let(:account_deletion_rate_s3_path) { "#{report_folder}/account_deletion_rate.csv" } + let(:total_user_count_s3_path) { "#{report_folder}/total_user_count.csv" } + let(:s3_metadata) do + { + body: anything, + content_type: 'text/csv', + bucket: 'reports-bucket.1234-us-west-1', + } end before do @@ -80,23 +82,22 @@ it 'uploads a file to S3 based on the report date' do expect(subject).to receive(:upload_file_to_s3_bucket).with( path: account_reuse_s3_path, - body: anything, - content_type: 'text/csv', - bucket: 'reports-bucket.1234-us-west-1', + **s3_metadata, ).exactly(1).time.and_call_original expect(subject).to receive(:upload_file_to_s3_bucket).with( path: total_profiles_s3_path, - body: anything, - content_type: 'text/csv', - bucket: 'reports-bucket.1234-us-west-1', + **s3_metadata, ).exactly(1).time.and_call_original expect(subject).to receive(:upload_file_to_s3_bucket).with( path: account_deletion_rate_s3_path, - body: anything, - content_type: 'text/csv', - bucket: 'reports-bucket.1234-us-west-1', + **s3_metadata, + ).exactly(1).time.and_call_original + + expect(subject).to receive(:upload_file_to_s3_bucket).with( + path: total_user_count_s3_path, + **s3_metadata, ).exactly(1).time.and_call_original subject.perform(report_date) diff --git a/spec/lib/identity_job_log_subscriber_spec.rb b/spec/lib/identity_job_log_subscriber_spec.rb index e5dbb7ddd87..d870471dad0 100644 --- a/spec/lib/identity_job_log_subscriber_spec.rb +++ b/spec/lib/identity_job_log_subscriber_spec.rb @@ -309,10 +309,12 @@ it 'is compatible with job classes that do not inherit from ApplicationJob' do # rubocop:disable Rails/ApplicationJob - class SampleJob < ActiveJob::Base; def perform(_); end; end + sample_job_class = Class.new(ActiveJob::Base) do + def perform(_); end + end # rubocop:enable Rails/ApplicationJob - job = SampleJob.new + job = sample_job_class.new event = ActiveSupport::Notifications::Event.new( 'enqueue.active_job', diff --git a/spec/lib/telephony/pinpoint/sms_sender_spec.rb b/spec/lib/telephony/pinpoint/sms_sender_spec.rb index 5b3d9ce7eae..4ea38e7d289 100644 --- a/spec/lib/telephony/pinpoint/sms_sender_spec.rb +++ b/spec/lib/telephony/pinpoint/sms_sender_spec.rb @@ -10,7 +10,7 @@ let(:mock_client) { Pinpoint::MockClient.new(sms_config) } # Monkeypatch library class so we can use it for argument matching - class Aws::Credentials + class Aws::Credentials # rubocop:disable RSpec/LeakyConstantDeclaration def ==(other) self.access_key_id == other.access_key_id && self.secret_access_key == other.secret_access_key diff --git a/spec/presenters/two_factor_authentication/selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/selection_presenter_spec.rb index 3c57c2431fd..009b7180107 100644 --- a/spec/presenters/two_factor_authentication/selection_presenter_spec.rb +++ b/spec/presenters/two_factor_authentication/selection_presenter_spec.rb @@ -1,9 +1,11 @@ require 'rails_helper' RSpec.describe TwoFactorAuthentication::SelectionPresenter do - class PlaceholderPresenter < TwoFactorAuthentication::SelectionPresenter - def method - :missing + let(:placeholder_presenter_class) do + Class.new(TwoFactorAuthentication::SelectionPresenter) do + def method + :missing + end end end @@ -107,14 +109,14 @@ def method describe '#label' do context 'with no configuration' do it 'raises with missing translation' do - expect { PlaceholderPresenter.new(user: user).label }.to raise_error(RuntimeError) + expect { placeholder_presenter_class.new(user: user).label }.to raise_error(RuntimeError) end end context 'with configuration' do it 'raises with missing translation' do expect do - PlaceholderPresenter.new(configuration: 1, user: user).label + placeholder_presenter_class.new(configuration: 1, user: user).label end.to raise_error(RuntimeError) end end @@ -123,14 +125,14 @@ def method describe '#info' do context 'with no configuration' do it 'raises with missing translation' do - expect { PlaceholderPresenter.new(user: user).info }.to raise_error(RuntimeError) + expect { placeholder_presenter_class.new(user: user).info }.to raise_error(RuntimeError) end end context 'with configuration' do it 'raises with missing translation' do expect do - PlaceholderPresenter.new(configuration: 1, user: user).info + placeholder_presenter_class.new(configuration: 1, user: user).info end.to raise_error(RuntimeError) end end diff --git a/spec/presenters/two_factor_authentication/set_up_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/set_up_selection_presenter_spec.rb index 768ab03ede8..eec0b2649ba 100644 --- a/spec/presenters/two_factor_authentication/set_up_selection_presenter_spec.rb +++ b/spec/presenters/two_factor_authentication/set_up_selection_presenter_spec.rb @@ -1,9 +1,11 @@ require 'rails_helper' RSpec.describe TwoFactorAuthentication::SetUpSelectionPresenter do - class PlaceholderPresenter < TwoFactorAuthentication::SetUpSelectionPresenter - def method - :missing + let(:placeholder_presenter_class) do + Class.new(TwoFactorAuthentication::SetUpSelectionPresenter) do + def method + :missing + end end end @@ -93,13 +95,13 @@ def method describe '#label' do it 'raises with missing translation' do - expect { PlaceholderPresenter.new(user: user).label }.to raise_error(RuntimeError) + expect { placeholder_presenter_class.new(user: user).label }.to raise_error(RuntimeError) end end describe '#info' do it 'raises with missing translation' do - expect { PlaceholderPresenter.new(user: user).info }.to raise_error(RuntimeError) + expect { placeholder_presenter_class.new(user: user).info }.to raise_error(RuntimeError) end end end diff --git a/spec/presenters/two_factor_authentication/sign_in_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/sign_in_selection_presenter_spec.rb index d1c1ccbe709..e31720e78fa 100644 --- a/spec/presenters/two_factor_authentication/sign_in_selection_presenter_spec.rb +++ b/spec/presenters/two_factor_authentication/sign_in_selection_presenter_spec.rb @@ -1,16 +1,18 @@ require 'rails_helper' RSpec.describe TwoFactorAuthentication::SignInSelectionPresenter do - class PlaceholderPresenter < TwoFactorAuthentication::SignInSelectionPresenter - def method - :missing + let(:placeholder_presenter_class) do + Class.new(TwoFactorAuthentication::SignInSelectionPresenter) do + def method + :missing + end end end let(:user) { build(:user) } let(:configuration) { create(:phone_configuration, user: user) } - subject(:presenter) { PlaceholderPresenter.new(user: user, configuration: configuration) } + subject(:presenter) { placeholder_presenter_class.new(user: user, configuration: configuration) } describe '#render_in' do it 'renders captured block content' do diff --git a/spec/routing/gpo_verification_routing_spec.rb b/spec/routing/gpo_verification_routing_spec.rb index 0d064234cc8..427169ef1bf 100644 --- a/spec/routing/gpo_verification_routing_spec.rb +++ b/spec/routing/gpo_verification_routing_spec.rb @@ -1,17 +1,23 @@ require 'rails_helper' RSpec.describe 'GPO verification routes' do - GET_ROUTES = %w[ - verify/usps - ].freeze + let(:get_routes) do + %w[ + verify/usps + ] + end - CREATE_ROUTES = %w[ - verify/usps - ].freeze + let(:create_routes) do + %w[ + verify/usps + ] + end - PUT_ROUTES = %w[ - verify/usps - ].freeze + let(:put_routes) do + %w[ + verify/usps + ] + end before do allow(FeatureManagement).to receive(:gpo_verification_enabled?). @@ -27,17 +33,17 @@ end it 'does not route to endpoints controlled by feature flag' do - GET_ROUTES.each do |route| + get_routes.each do |route| expect(get: route). to route_to(controller: 'pages', action: 'page_not_found', path: route) end - CREATE_ROUTES.each do |route| + create_routes.each do |route| expect(post: route). to route_to(controller: 'pages', action: 'page_not_found', path: route) end - PUT_ROUTES.each do |route| + put_routes.each do |route| expect(put: route). to route_to(controller: 'pages', action: 'page_not_found', path: route) end @@ -52,15 +58,15 @@ end it 'routes to endpoints controlled by feature flag' do - GET_ROUTES.each do |route| + get_routes.each do |route| expect(get: route).to be_routable end - CREATE_ROUTES.each do |route| + create_routes.each do |route| expect(post: route).to be_routable end - PUT_ROUTES.each do |route| + put_routes.each do |route| expect(put: route).to be_routable end end diff --git a/spec/scripts/changelog_check_spec.rb b/spec/scripts/changelog_check_spec.rb index 9213daeb227..aad6f8eebdb 100644 --- a/spec/scripts/changelog_check_spec.rb +++ b/spec/scripts/changelog_check_spec.rb @@ -8,7 +8,7 @@ it 'builds a git log into structured changelog objects' do git_log = git_fixtures.values.pluck('commit_log').join("\n") changelog_entries = generate_changelog(git_log) - expect(changelog_entries.length).to eq 8 + expect(changelog_entries.length).to eq 9 fixture_and_changelog = git_fixtures.values.filter do |x| x['category'].present? end.zip(changelog_entries) diff --git a/spec/services/doc_auth/acuant/request_spec.rb b/spec/services/doc_auth/acuant/request_spec.rb index d0ee51c2b95..c8ced684564 100644 --- a/spec/services/doc_auth/acuant/request_spec.rb +++ b/spec/services/doc_auth/acuant/request_spec.rb @@ -1,10 +1,12 @@ require 'rails_helper' RSpec.describe DocAuth::Acuant::Request do - class SimpleAcuantRequest < DocAuth::Acuant::Request - def handle_http_response(http_response) - http_response.body.upcase! - http_response + let(:simple_acuant_request) do + Class.new(DocAuth::Acuant::Request) do + def handle_http_response(http_response) + http_response.body.upcase! + http_response + end end end @@ -38,7 +40,7 @@ def handle_http_response(http_response) end subject do - request = SimpleAcuantRequest.new(config: config) + request = simple_acuant_request.new(config: config) allow(request).to receive(:path).and_return(path) allow(request).to receive(:body).and_return(request_body) allow(request).to receive(:method).and_return(request_method) diff --git a/spec/services/frontend_logger_spec.rb b/spec/services/frontend_logger_spec.rb index 577e7c5aa03..2ace9159697 100644 --- a/spec/services/frontend_logger_spec.rb +++ b/spec/services/frontend_logger_spec.rb @@ -1,15 +1,19 @@ require 'rails_helper' RSpec.describe FrontendLogger do - module ExampleAnalyticsEvents - def example_method_handler(ok:, **rest) - track_event('example', ok: ok, rest: rest) + let(:example_analytics_mixin) do + Module.new do + def example_method_handler(ok:, **rest) + track_event('example', ok: ok, rest: rest) + end end end let(:analytics_class) do + mixin = example_analytics_mixin + Class.new(FakeAnalytics) do - include ExampleAnalyticsEvents + include mixin end end let(:analytics) { analytics_class.new } diff --git a/spec/services/reporting/total_user_count_report_spec.rb b/spec/services/reporting/total_user_count_report_spec.rb new file mode 100644 index 00000000000..0c0525b515a --- /dev/null +++ b/spec/services/reporting/total_user_count_report_spec.rb @@ -0,0 +1,73 @@ +require 'rails_helper' +require 'csv' + +RSpec.describe Reporting::TotalUserCountReport do + let(:report_date) do + Date.new(2021, 3, 1).in_time_zone('UTC') + end + let(:expected_report) do + [ + ['All-time user count'], + [expected_count], + ] + end + + subject(:report) { described_class.new(report_date) } + + before { travel_to report_date } + + shared_examples 'a report with that user counted' do + let(:expected_count) { 1 } + it 'includes that user in the count' do + expect(subject.total_user_count_report).to eq expected_report + end + end + + describe '#total_user_count_report' do + context 'with no users' do + let(:expected_count) { 0 } + + it 'returns a report with a count of zero' do + expect(subject.total_user_count_report).to eq expected_report + end + end + + context 'with one ordinary user' do + let!(:user) { create(:user) } + it_behaves_like 'a report with that user counted' + end + + context 'with a suspended user' do + let!(:suspended_user) { create(:user, :suspended) } + + it 'has a suspended user' do + expect(suspended_user).to be_suspended + expect(User.count).to eq 1 + end + + it_behaves_like 'a report with that user counted' + end + + context 'with an unconfirmed user' do + let!(:unconfirmed_user) { create(:user, :unconfirmed) } + + it 'has an unconfirmed user' do + expect(unconfirmed_user).to_not be_confirmed + expect(User.count).to eq 1 + end + + it_behaves_like 'a report with that user counted' + end + + context 'with a user rejected for fraud' do + let!(:fraud_user) { create(:user, :fraud_rejection) } + + it 'has a user rejected for fraud' do + expect(fraud_user).to be_fraud_rejection + expect(User.count).to eq 1 + end + + it_behaves_like 'a report with that user counted' + end + end +end diff --git a/spec/services/service_provider_updater_spec.rb b/spec/services/service_provider_updater_spec.rb index 5706e7b8f56..b398c92d381 100644 --- a/spec/services/service_provider_updater_spec.rb +++ b/spec/services/service_provider_updater_spec.rb @@ -29,9 +29,9 @@ active: true, approved: true, help_text: { - sign_in: { en: 'A new different sign-in help text' }, - sign_up: { en: 'A new different help text' }, - forgot_password: { en: 'A new different forgot password help text' }, + sign_in: { en: 'A new different sign-in help text' }, + sign_up: { en: 'A new different help text' }, + forgot_password: { en: 'A new different forgot password help text' }, }, } 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 c822c491bad..75206170cb2 100644 --- a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb +++ b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb @@ -111,7 +111,6 @@ it 'maps enrollment address fields' do expect(proofer).to receive(:request_enroll) do |applicant| - ADDR = Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS expect(applicant).to have_attributes( address: Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS[ :identity_doc_address1 diff --git a/spec/support/controller_helper.rb b/spec/support/controller_helper.rb index e5cb0c4ad8b..19937fa3bb4 100644 --- a/spec/support/controller_helper.rb +++ b/spec/support/controller_helper.rb @@ -16,7 +16,8 @@ def sign_in_before_2fa(user = create(:user, :fully_registered)) def stub_sign_in(user = build(:user, password: VALID_PASSWORD)) allow(request.env['warden']).to receive(:authenticate!).and_return(user) allow(request.env['warden']).to receive(:session).and_return(user: {}) - allow(controller).to receive(:user_session).and_return(authn_at: Time.zone.now) + allow(controller).to receive(:user_session). + and_return({ authn_at: Time.zone.now }.with_indifferent_access) controller.user_session[:auth_method] ||= TwoFactorAuthenticatable::AuthMethod::SMS allow(controller).to receive(:current_user).and_return(user) allow(controller).to receive(:confirm_two_factor_authenticated).and_return(true) @@ -54,7 +55,7 @@ def stub_idv_steps_before_verify_step(user) end def stub_verify_steps_one_and_two(user) - user_session = {} + user_session = ActiveSupport::HashWithIndifferentAccess.new stub_sign_in(user) idv_session = Idv::Session.new( user_session: user_session, current_user: user, @@ -73,20 +74,6 @@ def stub_verify_steps_one_and_two(user) allow(subject).to receive(:user_session).and_return(user_session) end - def stub_user_with_applicant_data(user, applicant) - user_session = {} - stub_sign_in(user) - idv_session = Idv::Session.new( - user_session: user_session, current_user: user, - service_provider: nil - ) - idv_session.applicant = applicant.with_indifferent_access - idv_session.resolution_successful = true - 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) - end - def stub_user_with_pending_profile(user) allow(user).to receive(:pending_profile).and_return(pending_profile) allow(user).to receive(:gpo_verification_pending_profile?). diff --git a/yarn.lock b/yarn.lock index 48f47699c08..665fbc239b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1052,17 +1052,17 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f" integrity sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA== -"@eslint-community/eslint-utils@^4.2.0": +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.4.0": - version "4.5.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884" - integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== +"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.5.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.9.1.tgz#449dfa81a57a1d755b09aa58d826c1262e4283b4" + integrity sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA== "@eslint/eslintrc@^2.0.3": version "2.0.3" @@ -1493,10 +1493,10 @@ resolved "https://registry.yarnpkg.com/@types/js-levenshtein/-/js-levenshtein-1.1.1.tgz#ba05426a43f9e4e30b631941e0aa17bf0c890ed5" integrity sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g== -"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== +"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.13" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" + integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== "@types/json5@^0.0.29": version "0.0.29" @@ -1581,6 +1581,11 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== +"@types/semver@^7.5.0": + version "7.5.3" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.3.tgz#9a726e116beb26c24f1ccd6850201e1246122e04" + integrity sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw== + "@types/serve-index@^1.9.1": version "1.9.1" resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" @@ -1661,119 +1666,89 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz#9f05d42fa8fb9f62304cc2f5c2805e03c01c2620" - integrity sha512-ky7EFzPhqz3XlhS7vPOoMDaQnQMn+9o5ICR9CPr/6bw8HrFkzhMSxuA3gRfiJVvs7geYrSeawGJjZoZQKCOglQ== +"@typescript-eslint/eslint-plugin@^6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz#f4024b9f63593d0c2b5bd6e4ca027e6f30934d4f" + integrity sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw== dependencies: - "@typescript-eslint/scope-manager" "5.38.1" - "@typescript-eslint/type-utils" "5.38.1" - "@typescript-eslint/utils" "5.38.1" - debug "^4.3.4" - ignore "^5.2.0" - regexpp "^3.2.0" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/parser@^6.7.4": - version "6.7.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.7.4.tgz#23d1dd4fe5d295c7fa2ab651f5406cd9ad0bd435" - integrity sha512-I5zVZFY+cw4IMZUeNCU7Sh2PO5O57F7Lr0uyhgCJmhN/BuTlnc55KxPonR4+EM3GBdfiCyGZye6DgMjtubQkmA== - dependencies: - "@typescript-eslint/scope-manager" "6.7.4" - "@typescript-eslint/types" "6.7.4" - "@typescript-eslint/typescript-estree" "6.7.4" - "@typescript-eslint/visitor-keys" "6.7.4" + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.7.5" + "@typescript-eslint/type-utils" "6.7.5" + "@typescript-eslint/utils" "6.7.5" + "@typescript-eslint/visitor-keys" "6.7.5" debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" -"@typescript-eslint/scope-manager@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.38.1.tgz#f87b289ef8819b47189351814ad183e8801d5764" - integrity sha512-BfRDq5RidVU3RbqApKmS7RFMtkyWMM50qWnDAkKgQiezRtLKsoyRKIvz1Ok5ilRWeD9IuHvaidaLxvGx/2eqTQ== +"@typescript-eslint/parser@^6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.7.5.tgz#8d7ca3d1fbd9d5a58cc4d30b2aa797a760137886" + integrity sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw== dependencies: - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/visitor-keys" "5.38.1" + "@typescript-eslint/scope-manager" "6.7.5" + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/typescript-estree" "6.7.5" + "@typescript-eslint/visitor-keys" "6.7.5" + debug "^4.3.4" -"@typescript-eslint/scope-manager@6.7.4": - version "6.7.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.4.tgz#a484a17aa219e96044db40813429eb7214d7b386" - integrity sha512-SdGqSLUPTXAXi7c3Ob7peAGVnmMoGzZ361VswK2Mqf8UOYcODiYvs8rs5ILqEdfvX1lE7wEZbLyELCW+Yrql1A== +"@typescript-eslint/scope-manager@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz#1cf33b991043886cd67f4f3600b8e122fc14e711" + integrity sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A== dependencies: - "@typescript-eslint/types" "6.7.4" - "@typescript-eslint/visitor-keys" "6.7.4" + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/visitor-keys" "6.7.5" -"@typescript-eslint/type-utils@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.38.1.tgz#7f038fcfcc4ade4ea76c7c69b2aa25e6b261f4c1" - integrity sha512-UU3j43TM66gYtzo15ivK2ZFoDFKKP0k03MItzLdq0zV92CeGCXRfXlfQX5ILdd4/DSpHkSjIgLLLh1NtkOJOAw== +"@typescript-eslint/type-utils@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz#0a65949ec16588d8956f6d967f7d9c84ddb2d72a" + integrity sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g== dependencies: - "@typescript-eslint/typescript-estree" "5.38.1" - "@typescript-eslint/utils" "5.38.1" + "@typescript-eslint/typescript-estree" "6.7.5" + "@typescript-eslint/utils" "6.7.5" debug "^4.3.4" - tsutils "^3.21.0" - -"@typescript-eslint/types@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.38.1.tgz#74f9d6dcb8dc7c58c51e9fbc6653ded39e2e225c" - integrity sha512-QTW1iHq1Tffp9lNfbfPm4WJabbvpyaehQ0SrvVK2yfV79SytD9XDVxqiPvdrv2LK7DGSFo91TB2FgWanbJAZXg== - -"@typescript-eslint/types@6.7.4": - version "6.7.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.4.tgz#5d358484d2be986980c039de68e9f1eb62ea7897" - integrity sha512-o9XWK2FLW6eSS/0r/tgjAGsYasLAnOWg7hvZ/dGYSSNjCh+49k5ocPN8OmG5aZcSJ8pclSOyVKP2x03Sj+RrCA== + ts-api-utils "^1.0.1" -"@typescript-eslint/typescript-estree@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.1.tgz#657d858d5d6087f96b638ee383ee1cff52605a1e" - integrity sha512-99b5e/Enoe8fKMLdSuwrfH/C0EIbpUWmeEKHmQlGZb8msY33qn1KlkFww0z26o5Omx7EVjzVDCWEfrfCDHfE7g== - dependencies: - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/visitor-keys" "5.38.1" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" +"@typescript-eslint/types@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.5.tgz#4571320fb9cf669de9a95d9849f922c3af809790" + integrity sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ== -"@typescript-eslint/typescript-estree@6.7.4": - version "6.7.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.4.tgz#f2baece09f7bb1df9296e32638b2e1130014ef1a" - integrity sha512-ty8b5qHKatlNYd9vmpHooQz3Vki3gG+3PchmtsA4TgrZBKWHNjWfkQid7K7xQogBqqc7/BhGazxMD5vr6Ha+iQ== +"@typescript-eslint/typescript-estree@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz#4578de1a26e9f24950f029a4f00d1bfe41f15a39" + integrity sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg== dependencies: - "@typescript-eslint/types" "6.7.4" - "@typescript-eslint/visitor-keys" "6.7.4" + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/visitor-keys" "6.7.5" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.38.1.tgz#e3ac37d7b33d1362bb5adf4acdbe00372fb813ef" - integrity sha512-oIuUiVxPBsndrN81oP8tXnFa/+EcZ03qLqPDfSZ5xIJVm7A9V0rlkQwwBOAGtrdN70ZKDlKv+l1BeT4eSFxwXA== - dependencies: - "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.38.1" - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/typescript-estree" "5.38.1" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - -"@typescript-eslint/visitor-keys@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.1.tgz#508071bfc6b96d194c0afe6a65ad47029059edbc" - integrity sha512-bSHr1rRxXt54+j2n4k54p4fj8AHJ49VDWtjpImOpzQj4qjAiOpPni+V1Tyajh19Api1i844F757cur8wH3YvOA== - dependencies: - "@typescript-eslint/types" "5.38.1" - eslint-visitor-keys "^3.3.0" +"@typescript-eslint/utils@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.5.tgz#ab847b53d6b65e029314b8247c2336843dba81ab" + integrity sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.7.5" + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/typescript-estree" "6.7.5" + semver "^7.5.4" -"@typescript-eslint/visitor-keys@6.7.4": - version "6.7.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.4.tgz#80dfecf820fc67574012375859085f91a4dff043" - integrity sha512-pOW37DUhlTZbvph50x5zZCkFn3xzwkGtNoJHzIM3svpiSkJzwOYr/kVBaXmf+RAQiUDs1AHEZVNPg6UJCJpwRA== +"@typescript-eslint/visitor-keys@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz#84c68d6ceb5b12d5246b918b84f2b79affd6c2f1" + integrity sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg== dependencies: - "@typescript-eslint/types" "6.7.4" + "@typescript-eslint/types" "6.7.5" eslint-visitor-keys "^3.4.1" "@ungap/promise-all-settled@1.1.2": @@ -3294,7 +3269,7 @@ eslint-plugin-react@^7.31.8: semver "^6.3.0" string.prototype.matchall "^4.0.7" -eslint-scope@5.1.1, eslint-scope@^5.1.1: +eslint-scope@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -5765,11 +5740,6 @@ regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3: define-properties "^1.1.3" functions-have-names "^1.2.2" -regexpp@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - regexpu-core@^4.7.1: version "4.7.1" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" @@ -6044,7 +6014,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.4, semver@^7.3.7, semver@^7.5.4: +semver@^7.3.4, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -6686,23 +6656,11 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1.8.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - tslib@^2.1.0, tslib@^2.5.0, tslib@^2.6.0: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"