diff --git a/Gemfile b/Gemfile index 170805bac18..fe5eb36727d 100644 --- a/Gemfile +++ b/Gemfile @@ -51,7 +51,6 @@ gem 'rack-headers_filter' gem 'rack-timeout', require: false gem 'redacted_struct' gem 'redis', '>= 3.2.0' -gem 'redis-namespace' gem 'redis-session-store', github: '18F/redis-session-store', tag: 'v0.12-18f' gem 'retries' gem 'rotp', '~> 6.1' diff --git a/Gemfile.lock b/Gemfile.lock index d0cdd966ca7..0f3e99521c1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -60,67 +60,67 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.0.4.1) - actionpack (= 7.0.4.1) - activesupport (= 7.0.4.1) + actioncable (7.0.4.3) + actionpack (= 7.0.4.3) + activesupport (= 7.0.4.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.4.1) - actionpack (= 7.0.4.1) - activejob (= 7.0.4.1) - activerecord (= 7.0.4.1) - activestorage (= 7.0.4.1) - activesupport (= 7.0.4.1) + actionmailbox (7.0.4.3) + actionpack (= 7.0.4.3) + activejob (= 7.0.4.3) + activerecord (= 7.0.4.3) + activestorage (= 7.0.4.3) + activesupport (= 7.0.4.3) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.4.1) - actionpack (= 7.0.4.1) - actionview (= 7.0.4.1) - activejob (= 7.0.4.1) - activesupport (= 7.0.4.1) + actionmailer (7.0.4.3) + actionpack (= 7.0.4.3) + actionview (= 7.0.4.3) + activejob (= 7.0.4.3) + activesupport (= 7.0.4.3) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.4.1) - actionview (= 7.0.4.1) - activesupport (= 7.0.4.1) + actionpack (7.0.4.3) + actionview (= 7.0.4.3) + activesupport (= 7.0.4.3) rack (~> 2.0, >= 2.2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.4.1) - actionpack (= 7.0.4.1) - activerecord (= 7.0.4.1) - activestorage (= 7.0.4.1) - activesupport (= 7.0.4.1) + actiontext (7.0.4.3) + actionpack (= 7.0.4.3) + activerecord (= 7.0.4.3) + activestorage (= 7.0.4.3) + activesupport (= 7.0.4.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.4.1) - activesupport (= 7.0.4.1) + actionview (7.0.4.3) + activesupport (= 7.0.4.3) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.4.1) - activesupport (= 7.0.4.1) + activejob (7.0.4.3) + activesupport (= 7.0.4.3) globalid (>= 0.3.6) - activemodel (7.0.4.1) - activesupport (= 7.0.4.1) - activerecord (7.0.4.1) - activemodel (= 7.0.4.1) - activesupport (= 7.0.4.1) - activestorage (7.0.4.1) - actionpack (= 7.0.4.1) - activejob (= 7.0.4.1) - activerecord (= 7.0.4.1) - activesupport (= 7.0.4.1) + activemodel (7.0.4.3) + activesupport (= 7.0.4.3) + activerecord (7.0.4.3) + activemodel (= 7.0.4.3) + activesupport (= 7.0.4.3) + activestorage (7.0.4.3) + actionpack (= 7.0.4.3) + activejob (= 7.0.4.3) + activerecord (= 7.0.4.3) + activesupport (= 7.0.4.3) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.4.1) + activesupport (7.0.4.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -390,7 +390,7 @@ GEM zeitwerk (~> 2.5) lru_redux (1.1.0) lumberjack (1.2.8) - mail (2.8.0.1) + mail (2.8.1) mini_mime (>= 0.1.1) net-imap net-pop @@ -469,7 +469,7 @@ GEM nio4r (~> 2.0) raabro (1.4.0) racc (1.6.2) - rack (2.2.6.3) + rack (2.2.6.4) rack-attack (6.5.0) rack (>= 1.0, < 3) rack-cors (1.1.1) @@ -485,20 +485,20 @@ GEM rack_session_access (0.2.0) builder (>= 2.0.0) rack (>= 1.0.0) - rails (7.0.4.1) - actioncable (= 7.0.4.1) - actionmailbox (= 7.0.4.1) - actionmailer (= 7.0.4.1) - actionpack (= 7.0.4.1) - actiontext (= 7.0.4.1) - actionview (= 7.0.4.1) - activejob (= 7.0.4.1) - activemodel (= 7.0.4.1) - activerecord (= 7.0.4.1) - activestorage (= 7.0.4.1) - activesupport (= 7.0.4.1) + rails (7.0.4.3) + actioncable (= 7.0.4.3) + actionmailbox (= 7.0.4.3) + actionmailer (= 7.0.4.3) + actionpack (= 7.0.4.3) + actiontext (= 7.0.4.3) + actionview (= 7.0.4.3) + activejob (= 7.0.4.3) + activemodel (= 7.0.4.3) + activerecord (= 7.0.4.3) + activestorage (= 7.0.4.3) + activesupport (= 7.0.4.3) bundler (>= 1.15.0) - railties (= 7.0.4.1) + railties (= 7.0.4.3) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -516,9 +516,9 @@ GEM rails-i18n (7.0.6) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) - railties (7.0.4.1) - actionpack (= 7.0.4.1) - activesupport (= 7.0.4.1) + railties (7.0.4.3) + actionpack (= 7.0.4.3) + activesupport (= 7.0.4.3) method_source rake (>= 12.2) thor (~> 1.0) @@ -534,8 +534,6 @@ GEM redis-client (>= 0.9.0) redis-client (0.14.0) connection_pool - redis-namespace (1.8.1) - redis (>= 3.0.4) regexp_parser (2.6.1) reline (0.2.7) io-console (~> 0.5) @@ -653,7 +651,7 @@ GEM unicode-display_width (>= 1.1.1, < 3) thor (1.2.1) thread_safe (0.3.6) - timeout (0.3.1) + timeout (0.3.2) tpm-key_attestation (0.11.0) bindata (~> 2.4) openssl (> 2.0, < 3.1) @@ -802,7 +800,6 @@ DEPENDENCIES rails-erd (>= 1.6.0) redacted_struct redis (>= 3.2.0) - redis-namespace redis-session-store! retries rotp (~> 6.1) diff --git a/app/controllers/account_reset/delete_account_controller.rb b/app/controllers/account_reset/delete_account_controller.rb index a9d9d7625b8..94e02f119ec 100644 --- a/app/controllers/account_reset/delete_account_controller.rb +++ b/app/controllers/account_reset/delete_account_controller.rb @@ -3,7 +3,7 @@ class DeleteAccountController < ApplicationController def show render :show and return unless token - result = AccountReset::ValidateGrantedToken.new(token).call + result = AccountReset::ValidateGrantedToken.new(token, request, analytics).call analytics.account_reset_granted_token_validation(**result.to_h) if result.success? @@ -15,13 +15,9 @@ def show def delete granted_token = session.delete(:granted_token) - result = AccountReset::DeleteAccount.new(granted_token).call + result = AccountReset::DeleteAccount.new(granted_token, request, analytics).call analytics.account_reset_delete(**result.to_h.except(:email)) - irs_attempts_api_tracker.account_reset_account_deleted( - success: result.success?, - failure_reason: irs_attempts_api_tracker.parse_failure_reason(result), - ) if result.success? handle_successful_deletion(result) else diff --git a/app/controllers/account_reset/request_controller.rb b/app/controllers/account_reset/request_controller.rb index 03c07fb65cb..9c0f9dd3099 100644 --- a/app/controllers/account_reset/request_controller.rb +++ b/app/controllers/account_reset/request_controller.rb @@ -18,7 +18,7 @@ def create private def create_account_reset_request - response = AccountReset::CreateRequest.new(current_user).call + response = AccountReset::CreateRequest.new(current_user, sp_session[:issuer]).call irs_attempts_api_tracker.account_reset_request_submitted( success: response.success?, ) diff --git a/app/controllers/concerns/idv/step_utilities_concern.rb b/app/controllers/concerns/idv/step_utilities_concern.rb index 227d3e697a3..abe4a2c268c 100644 --- a/app/controllers/concerns/idv/step_utilities_concern.rb +++ b/app/controllers/concerns/idv/step_utilities_concern.rb @@ -20,7 +20,7 @@ def confirm_pii_from_doc end def confirm_profile_not_already_confirmed - return unless idv_session.profile_confirmation == true + return unless idv_session.verify_info_step_complete? redirect_to idv_review_url end diff --git a/app/controllers/concerns/idv/verify_info_concern.rb b/app/controllers/concerns/idv/verify_info_concern.rb index 72a3768cef1..0635f2c248f 100644 --- a/app/controllers/concerns/idv/verify_info_concern.rb +++ b/app/controllers/concerns/idv/verify_info_concern.rb @@ -113,10 +113,10 @@ def async_state_done(current_async_state) delete_async if form_response.success? - idv_session.resolution_successful = true + idv_session.mark_verify_info_step_complete! redirect_to idv_phone_url else - idv_session.resolution_successful = false + idv_session.invalidate_verify_info_step! end analytics.idv_doc_auth_verify_proofing_results(**form_response.to_h) @@ -213,11 +213,10 @@ def save_legacy_state end def skip_legacy_steps - idv_session.profile_confirmation = true + idv_session.mark_verify_info_step_complete! idv_session.vendor_phone_confirmation = false idv_session.user_phone_confirmation = false idv_session.address_verification_mechanism = 'phone' - idv_session.resolution_successful = 'phone' end def add_proofing_costs(results) diff --git a/app/controllers/idv/gpo_controller.rb b/app/controllers/idv/gpo_controller.rb index 5e5b02188ff..6bf55c01dda 100644 --- a/app/controllers/idv/gpo_controller.rb +++ b/app/controllers/idv/gpo_controller.rb @@ -69,7 +69,7 @@ def confirm_user_completed_idv_profile_step # If the user has a pending profile, they may have completed idv in a # different session and need a letter resent now return if current_user.decorate.pending_profile_requires_verification? - return if idv_session.profile_confirmation == true + return if idv_session.verify_info_step_complete? redirect_to idv_doc_auth_url end diff --git a/app/controllers/idv/gpo_verify_controller.rb b/app/controllers/idv/gpo_verify_controller.rb index a3a30a9f1b1..b18ba0db301 100644 --- a/app/controllers/idv/gpo_verify_controller.rb +++ b/app/controllers/idv/gpo_verify_controller.rb @@ -68,12 +68,8 @@ def create private def next_step - if IdentityConfig.store.gpo_personal_key_after_otp - enable_personal_key_generation - idv_personal_key_url - else - sign_up_completed_url - end + enable_personal_key_generation + idv_personal_key_url end def throttle diff --git a/app/controllers/idv/in_person/verify_info_controller.rb b/app/controllers/idv/in_person/verify_info_controller.rb index 9d706a81474..21747e62fb5 100644 --- a/app/controllers/idv/in_person/verify_info_controller.rb +++ b/app/controllers/idv/in_person/verify_info_controller.rb @@ -110,7 +110,7 @@ def confirm_ssn_step_complete def confirm_profile_not_already_confirmed # todo: should this instead be like so? # return unless idv_session.resolution_successful == true - return unless idv_session.profile_confirmation == true + return unless idv_session.verify_info_step_complete? redirect_to idv_phone_url end diff --git a/app/controllers/idv/personal_key_controller.rb b/app/controllers/idv/personal_key_controller.rb index 0a15479184e..32b7b405600 100644 --- a/app/controllers/idv/personal_key_controller.rb +++ b/app/controllers/idv/personal_key_controller.rb @@ -65,11 +65,7 @@ def finish_idv_session irs_attempts_api_tracker.idv_personal_key_generated - if idv_session.address_verification_mechanism == 'gpo' - if !IdentityConfig.store.gpo_personal_key_after_otp - flash.now[:success] = t('idv.messages.mail_sent') - end - else + if idv_session.address_verification_mechanism != 'gpo' flash.now[:success] = t('idv.messages.confirm') end flash[:allow_confirmations_continue] = true diff --git a/app/controllers/idv/review_controller.rb b/app/controllers/idv/review_controller.rb index 121b1740167..4449c67cfe3 100644 --- a/app/controllers/idv/review_controller.rb +++ b/app/controllers/idv/review_controller.rb @@ -130,8 +130,7 @@ def next_step end def gpo_user_flow? - idv_session.address_verification_mechanism == 'gpo' && - IdentityConfig.store.gpo_personal_key_after_otp + idv_session.address_verification_mechanism == 'gpo' end def handle_request_enroll_exception(err) diff --git a/app/controllers/idv/session_errors_controller.rb b/app/controllers/idv/session_errors_controller.rb index 2ae86c6b691..f3bb6d80e8e 100644 --- a/app/controllers/idv/session_errors_controller.rb +++ b/app/controllers/idv/session_errors_controller.rb @@ -66,7 +66,7 @@ def confirm_two_factor_authenticated_or_user_id_in_session def confirm_idv_session_step_needed return unless user_fully_authenticated? - redirect_to idv_phone_url if idv_session.profile_confirmation == true + redirect_to idv_phone_url if idv_session.verify_info_step_complete? end def ignore_form_step_wait_requests diff --git a/app/controllers/idv/ssn_controller.rb b/app/controllers/idv/ssn_controller.rb index dffa968fc6d..76ddf64111a 100644 --- a/app/controllers/idv/ssn_controller.rb +++ b/app/controllers/idv/ssn_controller.rb @@ -38,7 +38,7 @@ def update if form_response.success? flow_session['pii_from_doc'][:ssn] = params[:doc_auth][:ssn] idv_session.invalidate_steps_after_ssn! - redirect_to idv_verify_info_url + redirect_to next_url else @error_message = form_response.first_error_message render :show, locals: extra_view_variables @@ -55,6 +55,14 @@ def extra_view_variables private + def next_url + if @pii[:state] == 'PR' + idv_address_url + else + idv_verify_info_url + end + end + def analytics_arguments { flow_path: flow_path, diff --git a/app/controllers/idv/verify_info_controller.rb b/app/controllers/idv/verify_info_controller.rb index 3969f3cf8d5..2601be0f70e 100644 --- a/app/controllers/idv/verify_info_controller.rb +++ b/app/controllers/idv/verify_info_controller.rb @@ -128,7 +128,7 @@ def increment_step_counts # copied from verify_base_step. May want reconciliation with phone_step def process_async_state(current_async_state) if current_async_state.none? - idv_session.resolution_successful = false + idv_session.invalidate_verify_info_step! render :show elsif current_async_state.in_progress? render 'shared/wait' @@ -138,7 +138,7 @@ def process_async_state(current_async_state) render :show delete_async - idv_session.resolution_successful = false + idv_session.invalidate_verify_info_step! log_idv_verification_submitted_event( success: false, diff --git a/app/forms/idv/state_id_form.rb b/app/forms/idv/state_id_form.rb index 1ad31ec5d4a..eb5608def4f 100644 --- a/app/forms/idv/state_id_form.rb +++ b/app/forms/idv/state_id_form.rb @@ -3,7 +3,9 @@ class StateIdForm include ActiveModel::Model include FormStateIdValidator - ATTRIBUTES = %i[first_name last_name dob state_id_jurisdiction state_id_number].freeze + ATTRIBUTES = %i[first_name last_name dob state_id_address1 state_id_address2 + state_id_city state_id_zipcode state_id_jurisdiction state_id_number + same_address_as_id].freeze attr_accessor(*ATTRIBUTES) diff --git a/app/helpers/session_timeout_warning_helper.rb b/app/helpers/session_timeout_warning_helper.rb index 4a974aade9b..5b9c7081346 100644 --- a/app/helpers/session_timeout_warning_helper.rb +++ b/app/helpers/session_timeout_warning_helper.rb @@ -23,10 +23,6 @@ def timeout_refresh_path end def session_modal - if user_fully_authenticated? - FullySignedInModalPresenter.new(view_context: self, expiration: expires_at) - else - PartiallySignedInModalPresenter.new(view_context: self, expiration: expires_at) - end + SessionTimeoutModalPresenter.new(user_fully_authenticated: user_fully_authenticated?) end end diff --git a/app/jobs/fraud_rejection_daily_job.rb b/app/jobs/fraud_rejection_daily_job.rb index 67c2840f818..474a21ea449 100644 --- a/app/jobs/fraud_rejection_daily_job.rb +++ b/app/jobs/fraud_rejection_daily_job.rb @@ -3,13 +3,20 @@ class FraudRejectionDailyJob < ApplicationJob def perform(_date) profiles_eligible_for_fraud_rejection.find_each do |profile| - analytics.automatic_fraud_rejection(verified_at: profile.verified_at) + analytics.automatic_fraud_rejection( + rejection_date: Time.zone.today, + verified_at: profile.verified_at, + ) profile.reject_for_fraud(notify_user: false) end end private + def analytics(user: AnonymousUser.new) + Analytics.new(user: user, request: nil, session: {}, sp: nil) + end + def profiles_eligible_for_fraud_rejection Profile.where( fraud_review_pending: true, diff --git a/app/jobs/heartbeat_job.rb b/app/jobs/heartbeat_job.rb index e024838f24c..3c092e9203d 100644 --- a/app/jobs/heartbeat_job.rb +++ b/app/jobs/heartbeat_job.rb @@ -5,11 +5,6 @@ def perform IdentityJobLogSubscriber.new.logger.info( { name: 'queue_metric.good_job', - # borrowed from: https://github.com/bensheldon/good_job/blob/main/engine/app/controllers/good_job/dashboards_controller.rb#L35 - num_finished: GoodJob::Execution.finished.count, - num_unfinished: GoodJob::Execution.unfinished.count, - num_running: GoodJob::Execution.running.count, - num_errors: GoodJob::Execution.where.not(error: nil).count, }.to_json, ) diff --git a/app/jobs/psql_stats_job.rb b/app/jobs/psql_stats_job.rb deleted file mode 100644 index a6bf42e1df6..00000000000 --- a/app/jobs/psql_stats_job.rb +++ /dev/null @@ -1,75 +0,0 @@ -class PsqlStatsJob < ApplicationJob - queue_as :default - - # gather data on bloat for each table - # https://github.com/ioguix/pgsql-bloat-estimation/blob/master/table/table_bloat.sql - QUERY = <<-SQL - SELECT current_database(), schemaname, tblname, bs*tblpages AS real_size, - (tblpages-est_tblpages)*bs AS extra_size, - CASE WHEN tblpages - est_tblpages > 0 - THEN 100 * (tblpages - est_tblpages)/tblpages::float - ELSE 0 - END AS extra_pct, fillfactor, - CASE WHEN tblpages - est_tblpages_ff > 0 - THEN (tblpages-est_tblpages_ff)*bs - ELSE 0 - END AS bloat_size, - CASE WHEN tblpages - est_tblpages_ff > 0 - THEN 100 * (tblpages - est_tblpages_ff)/tblpages::float - ELSE 0 - END AS bloat_pct, is_na - FROM ( - SELECT ceil( reltuples / ( (bs-page_hdr)/tpl_size ) ) + ceil( toasttuples / 4 ) AS est_tblpages, - ceil( reltuples / ( (bs-page_hdr)*fillfactor/(tpl_size*100) ) ) + ceil( toasttuples / 4 ) AS est_tblpages_ff, - tblpages, fillfactor, bs, tblid, schemaname, tblname, heappages, toastpages, is_na - FROM ( - SELECT - ( 4 + tpl_hdr_size + tpl_data_size + (2*ma) - - CASE WHEN tpl_hdr_size%ma = 0 THEN ma ELSE tpl_hdr_size%ma END - - CASE WHEN ceil(tpl_data_size)::int%ma = 0 THEN ma ELSE ceil(tpl_data_size)::int%ma END - ) AS tpl_size, bs - page_hdr AS size_per_block, (heappages + toastpages) AS tblpages, heappages, - toastpages, reltuples, toasttuples, bs, page_hdr, tblid, schemaname, tblname, fillfactor, is_na - FROM ( - SELECT - tbl.oid AS tblid, ns.nspname AS schemaname, tbl.relname AS tblname, tbl.reltuples, - tbl.relpages AS heappages, coalesce(toast.relpages, 0) AS toastpages, - coalesce(toast.reltuples, 0) AS toasttuples, - coalesce(substring( - array_to_string(tbl.reloptions, ' ') - FROM 'fillfactor=([0-9]+)')::smallint, 100) AS fillfactor, - current_setting('block_size')::numeric AS bs, - CASE WHEN version()~'mingw32' OR version()~'64-bit|x86_64|ppc64|ia64|amd64' THEN 8 ELSE 4 END AS ma, - 24 AS page_hdr, - 23 + CASE WHEN MAX(coalesce(s.null_frac,0)) > 0 THEN ( 7 + count(s.attname) ) / 8 ELSE 0::int END - + CASE WHEN bool_or(att.attname = 'oid' and att.attnum < 0) THEN 4 ELSE 0 END AS tpl_hdr_size, - sum( (1-coalesce(s.null_frac, 0)) * coalesce(s.avg_width, 0) ) AS tpl_data_size, - bool_or(att.atttypid = 'pg_catalog.name'::regtype) - OR sum(CASE WHEN att.attnum > 0 THEN 1 ELSE 0 END) <> count(s.attname) AS is_na - FROM pg_attribute AS att - JOIN pg_class AS tbl ON att.attrelid = tbl.oid - JOIN pg_namespace AS ns ON ns.oid = tbl.relnamespace - LEFT JOIN pg_stats AS s ON s.schemaname=ns.nspname - AND s.tablename = tbl.relname AND s.inherited=false AND s.attname=att.attname - LEFT JOIN pg_class AS toast ON tbl.reltoastrelid = toast.oid - WHERE NOT att.attisdropped AND ns.nspname='public' -- remove `AND ns.nspname='public'` to include system namespaces - AND tbl.relkind in ('r','m') - GROUP BY 1,2,3,4,5,6,7,8,9,10 - ORDER BY 2,3 - ) AS s - ) AS s2 - ) AS s3 - ORDER BY schemaname, tblname; - SQL - - def perform(_now) - ar_result = ActiveRecord::Base.connection.exec_query(QUERY) - IdentityJobLogSubscriber.reports_logger.info( - { - name: 'psql_bloat_statistics', - table_data: ar_result.index_by { |r| r['tblname'] || 'none' }, - }.to_json, - ) - - true - end -end diff --git a/app/jobs/reports/duplicate_ssn_report.rb b/app/jobs/reports/duplicate_ssn_report.rb new file mode 100644 index 00000000000..42e3ef7a629 --- /dev/null +++ b/app/jobs/reports/duplicate_ssn_report.rb @@ -0,0 +1,86 @@ +require 'csv' + +module Reports + class DuplicateSsnReport < BaseReport + REPORT_NAME = 'duplicate-ssn-report' + + attr_reader :report_date + + def initialize(report_date = nil) + @report_date = report_date + end + + def perform(report_date) + @report_date = report_date + + csv = report_body + + save_report(REPORT_NAME, csv, extension: 'csv') + end + + def start + report_date.beginning_of_day + end + + def finish + report_date.end_of_day + end + + # @return [String] + def report_body + # note, this will table scan until we add an index, for a once-a-day job it may be ok + todays_profiles = Profile. + select(:id, :ssn_signature). + where(active: true, activated_at: start..finish) + + todays_profile_ids = todays_profiles.map(&:id).to_set + + ssn_signatures = todays_profiles.map(&:ssn_signature).uniq + + profiles_connected_by_ssn = Profile. + includes(:user). + where(ssn_signature: ssn_signatures). + to_a + + profiles_connected_by_ssn.sort_by!(&:id).reverse! + + count_by_ssn = profiles_connected_by_ssn. + group_by(&:ssn_signature). + transform_values(&:count) + count_by_ssn_active = profiles_connected_by_ssn. + select(&:active?). + group_by(&:ssn_signature). + transform_values(&:count) + + CSV.generate do |csv| + csv << %w[ + new_account + uuid + account_created_at + identity_verified_at + profile_active + ssn_fingerprint + count_ssn_fingerprint + count_active_ssn_fingerprint + ] + + profiles_connected_by_ssn.each do |profile| + ssn_count = count_by_ssn[profile.ssn_signature] + ssn_count_active = count_by_ssn_active[profile.ssn_signature] + next if ssn_count < 2 + + csv << [ + todays_profile_ids.include?(profile.id), + profile.user.uuid, + profile.user.created_at.in_time_zone('UTC').iso8601, + profile.activated_at&.in_time_zone('UTC')&.iso8601, + profile.active, + profile.ssn_signature, + ssn_count, + ssn_count_active, + ] + end + end + end + end +end diff --git a/app/models/account_reset_request.rb b/app/models/account_reset_request.rb index 673ea0e5d91..9080493eb66 100644 --- a/app/models/account_reset_request.rb +++ b/app/models/account_reset_request.rb @@ -2,6 +2,12 @@ class AccountResetRequest < ApplicationRecord self.ignored_columns = %w[reported_fraud_at] belongs_to :user + # rubocop:disable Rails/InverseOf + belongs_to :requesting_service_provider, + class_name: 'ServiceProvider', + foreign_key: 'requesting_issuer', + primary_key: 'issuer' + # rubocop:enable Rails/InverseOf def granted_token_valid? granted_token.present? && !granted_token_expired? diff --git a/app/presenters/fully_signed_in_modal_presenter.rb b/app/presenters/fully_signed_in_modal_presenter.rb deleted file mode 100644 index 3ac8feea939..00000000000 --- a/app/presenters/fully_signed_in_modal_presenter.rb +++ /dev/null @@ -1,42 +0,0 @@ -class FullySignedInModalPresenter - include ActionView::Helpers::TranslationHelper - - def initialize(view_context:, expiration:) - @view_context = view_context - @expiration = expiration - end - - def message - t( - 'notices.timeout_warning.signed_in.message_html', - time_left_in_session: view_context.render( - CountdownComponent.new(expiration: expiration, start_immediately: false), - ), - ) - end - - def sr_message - t( - 'notices.timeout_warning.signed_in.sr_message_html', - time_left_in_session: view_context.render( - CountdownComponent.new( - expiration: expiration, - update_interval: 30.seconds, - start_immediately: false, - ), - ), - ) - end - - def continue - t('notices.timeout_warning.signed_in.continue') - end - - def sign_out - t('notices.timeout_warning.signed_in.sign_out') - end - - private - - attr_reader :expiration, :view_context -end diff --git a/app/presenters/partially_signed_in_modal_presenter.rb b/app/presenters/partially_signed_in_modal_presenter.rb deleted file mode 100644 index 655d87b7811..00000000000 --- a/app/presenters/partially_signed_in_modal_presenter.rb +++ /dev/null @@ -1,42 +0,0 @@ -class PartiallySignedInModalPresenter - include ActionView::Helpers::TranslationHelper - - def initialize(view_context:, expiration:) - @view_context = view_context - @expiration = expiration - end - - def message - t( - 'notices.timeout_warning.partially_signed_in.message_html', - time_left_in_session: view_context.render( - CountdownComponent.new(expiration: expiration, start_immediately: false), - ), - ) - end - - def sr_message - t( - 'notices.timeout_warning.partially_signed_in.sr_message_html', - time_left_in_session: view_context.render( - CountdownComponent.new( - expiration: expiration, - update_interval: 30.seconds, - start_immediately: false, - ), - ), - ) - end - - def continue - t('notices.timeout_warning.partially_signed_in.continue') - end - - def sign_out - t('notices.timeout_warning.partially_signed_in.sign_out') - end - - private - - attr_reader :expiration, :view_context -end diff --git a/app/presenters/session_timeout_modal_presenter.rb b/app/presenters/session_timeout_modal_presenter.rb new file mode 100644 index 00000000000..c7e5b8f82a6 --- /dev/null +++ b/app/presenters/session_timeout_modal_presenter.rb @@ -0,0 +1,18 @@ +class SessionTimeoutModalPresenter + def initialize(user_fully_authenticated:) + @user_fully_authenticated = user_fully_authenticated + end + + def translation_scope + if user_fully_authenticated? + [:notices, :timeout_warning, :signed_in] + else + [:notices, :timeout_warning, :partially_signed_in] + end + end + + private + + attr_reader :user_fully_authenticated + alias_method :user_fully_authenticated?, :user_fully_authenticated +end diff --git a/app/services/account_reset/create_request.rb b/app/services/account_reset/create_request.rb index d0c9611d496..7d2dba04e4e 100644 --- a/app/services/account_reset/create_request.rb +++ b/app/services/account_reset/create_request.rb @@ -1,7 +1,8 @@ module AccountReset class CreateRequest - def initialize(user) + def initialize(user, requesting_issuer) @user = user + @requesting_issuer = requesting_issuer end def call @@ -17,7 +18,7 @@ def call private - attr_reader :user + attr_reader :user, :requesting_issuer def create_request request = AccountResetRequest.create_or_find_by(user: user) @@ -27,6 +28,7 @@ def create_request cancelled_at: nil, granted_at: nil, granted_token: nil, + requesting_issuer: requesting_issuer, ) request end diff --git a/app/services/account_reset/delete_account.rb b/app/services/account_reset/delete_account.rb index f0ceee8745e..998d5aaeac8 100644 --- a/app/services/account_reset/delete_account.rb +++ b/app/services/account_reset/delete_account.rb @@ -2,9 +2,12 @@ module AccountReset class DeleteAccount include ActiveModel::Model include GrantedTokenValidator + include TrackIrsEvent - def initialize(token) + def initialize(token, request, analytics) @token = token + @request = request + @analytics = analytics end def call @@ -12,6 +15,7 @@ def call track_account_age track_mfa_method_counts + track_irs_event if sp extra = extra_analytics_attributes @@ -22,7 +26,7 @@ def call private - attr_reader :success, :account_age, :mfa_method_counts + attr_reader :success, :account_age, :mfa_method_counts, :request, :analytics # @return [Integer, nil] number of days since the account was confirmed (rounded) or nil if # the account was not confirmed diff --git a/app/services/account_reset/track_irs_event.rb b/app/services/account_reset/track_irs_event.rb new file mode 100644 index 00000000000..57a8349d419 --- /dev/null +++ b/app/services/account_reset/track_irs_event.rb @@ -0,0 +1,36 @@ +# Mixin for account reset event tracking +# Assumes these methods exist on the including class: +# - sp +# - success +# - errors +# - request +# - analytics +module AccountReset::TrackIrsEvent + def track_irs_event + irs_attempts_api_tracker.account_reset_account_deleted( + success: success, + failure_reason: event_failure_reason.presence, + ) + end + + def irs_attempts_api_tracker + @irs_attempts_api_tracker ||= IrsAttemptsApi::Tracker.new( + session_id: nil, + request: request, + user: user, + sp: sp, + cookie_device_uuid: cookies[:device], + sp_request_uri: nil, + enabled_for_session: sp.irs_attempts_api_enabled?, + analytics: analytics, + ) + end + + def cookies + request.cookie_jar + end + + def event_failure_reason + errors.is_a?(ActiveModel::Errors) ? errors.messages.to_hash : errors + end +end diff --git a/app/services/account_reset/validate_granted_token.rb b/app/services/account_reset/validate_granted_token.rb index 6558a3042ce..4550d0615d1 100644 --- a/app/services/account_reset/validate_granted_token.rb +++ b/app/services/account_reset/validate_granted_token.rb @@ -2,20 +2,23 @@ module AccountReset class ValidateGrantedToken include ActiveModel::Model include GrantedTokenValidator + include TrackIrsEvent - def initialize(token) + def initialize(token, request, analytics) @token = token + @request = request + @analytics = analytics end def call @success = valid? - + track_irs_event if !success && sp FormResponse.new(success: success, errors: errors, extra: extra_analytics_attributes) end private - attr_reader :success + attr_reader :success, :request, :analytics def extra_analytics_attributes { diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 88992adc829..445d71961fd 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -163,11 +163,13 @@ def authentication_confirmation_reset track_event('Authentication Confirmation: Reset selected') end - # @param [Date] verified_at + # @param [Date] rejection_date Date of the rejection + # @param [Date] verified_at Date when profile was verified # Tracks when a profile is automatically rejected due to being under review for 30 days - def automatic_fraud_rejection(verified_at:, **extra) + def automatic_fraud_rejection(rejection_date:, verified_at:, **extra) track_event( 'Fraud: Automatic Fraud Rejection', + rejection_date: rejection_date, verified_at: verified_at, **extra, ) diff --git a/app/services/idv/flows/doc_auth_flow.rb b/app/services/idv/flows/doc_auth_flow.rb index 539a1b707d6..2fdec534840 100644 --- a/app/services/idv/flows/doc_auth_flow.rb +++ b/app/services/idv/flows/doc_auth_flow.rb @@ -23,9 +23,8 @@ class DocAuthFlow < Flow::BaseFlow { name: :getting_started }, { name: :verify_id }, { name: :verify_info }, - *([name: :secure_account] if !IdentityConfig.store.gpo_personal_key_after_otp), { name: :get_a_letter }, - *([name: :secure_account] if IdentityConfig.store.gpo_personal_key_after_otp), + { name: :secure_account }, ].freeze OPTIONAL_SHOW_STEPS = {}.freeze diff --git a/app/services/idv/session.rb b/app/services/idv/session.rb index 91024db8fe8..e5c9d65af1c 100644 --- a/app/services/idv/session.rb +++ b/app/services/idv/session.rb @@ -128,7 +128,7 @@ def in_person_enrollment? end def verify_info_step_complete? - resolution_successful && profile_confirmation + resolution_successful end def address_step_complete? @@ -155,6 +155,15 @@ def invalidate_steps_after_ssn! invalidate_phone_step! end + def mark_verify_info_step_complete! + session[:resolution_successful] = true + # This is here to maintain backwards compadibility with old code. + # Once the code that checks `profile_confirmation` is removed from prod + # this setter and eventually the value in the Idv::Session struct itself + # can be removed. + session[:profile_confirmation] = true + end + def invalidate_verify_info_step! session[:resolution_successful] = nil session[:profile_confirmation] = nil diff --git a/app/services/irs_attempts_api/redis_client.rb b/app/services/irs_attempts_api/redis_client.rb index a549258826c..f5c0c0e9c7b 100644 --- a/app/services/irs_attempts_api/redis_client.rb +++ b/app/services/irs_attempts_api/redis_client.rb @@ -2,10 +2,7 @@ module IrsAttemptsApi class RedisClient cattr_accessor :redis_pool do ConnectionPool.new(size: IdentityConfig.store.redis_irs_attempt_api_pool_size) do - Redis::Namespace.new( - 'irs-attempt-api', - redis: Redis.new(url: IdentityConfig.store.redis_irs_attempt_api_url), - ) + Redis.new(url: IdentityConfig.store.redis_irs_attempt_api_url) end end @@ -29,7 +26,7 @@ def read_events(timestamp:, batch_size: 5000) end def key(timestamp) - timestamp.in_time_zone('UTC').change(min: 0, sec: 0).iso8601 + 'irs-attempt-api:' + timestamp.in_time_zone('UTC').change(min: 0, sec: 0).iso8601 end def self.clear_attempts! diff --git a/app/services/pii/attributes.rb b/app/services/pii/attributes.rb index d8605821ec4..facebec8d23 100644 --- a/app/services/pii/attributes.rb +++ b/app/services/pii/attributes.rb @@ -5,6 +5,7 @@ module Pii Attributes = RedactedStruct.new( :first_name, :middle_name, :last_name, + :state_id_address1, :state_id_address2, :state_id_city, :state_id_zipcode, :address1, :address2, :city, :state, :zipcode, :same_address_as_id, :ssn, :dob, :phone, :prev_address1, :prev_address2, :prev_city, :prev_state, :prev_zipcode, diff --git a/app/services/redis_rate_limiter.rb b/app/services/redis_rate_limiter.rb index bed1f940fb4..18bbe69b23a 100644 --- a/app/services/redis_rate_limiter.rb +++ b/app/services/redis_rate_limiter.rb @@ -47,6 +47,6 @@ def increment(now = Time.zone.now) # @return [String] def build_key(now) rounded_seconds = (now.to_i / interval) * interval - "redis-rate-limiter:#{key}:#{rounded_seconds}" + "throttle:redis-rate-limiter:#{key}:#{rounded_seconds}" end end diff --git a/app/services/throttle.rb b/app/services/throttle.rb index b5c4a992c78..da690f3a6a8 100644 --- a/app/services/throttle.rb +++ b/app/services/throttle.rb @@ -143,9 +143,9 @@ def increment_to_throttled! def key if @user - "throttle:#{@user.id}:#{throttle_type}" + "throttle:throttle:#{@user.id}:#{throttle_type}" else - "throttle:#{@target}:#{throttle_type}" + "throttle:throttle:#{@target}:#{throttle_type}" end end diff --git a/app/validators/account_reset/granted_token_validator.rb b/app/validators/account_reset/granted_token_validator.rb index bd33ff73bfe..ac9d794d3ea 100644 --- a/app/validators/account_reset/granted_token_validator.rb +++ b/app/validators/account_reset/granted_token_validator.rb @@ -45,5 +45,10 @@ def account_reset_request def user account_reset_request&.user || AnonymousUser.new end + + def sp + return @sp if defined?(@sp) + @sp = account_reset_request&.requesting_service_provider + end end end diff --git a/app/views/idv/in_person/state_id.html.erb b/app/views/idv/in_person/state_id.html.erb index 1d05c544bbb..e8ed815f215 100644 --- a/app/views/idv/in_person/state_id.html.erb +++ b/app/views/idv/in_person/state_id.html.erb @@ -3,7 +3,7 @@ <% if updating_state_id %> <%= render PageHeadingComponent.new.with_content(t('in_person_proofing.headings.update_state_id')) %> <% else %> - <%= render PageHeadingComponent.new.with_content(t('in_person_proofing.headings.state_id')) %> + <%= render PageHeadingComponent.new.with_content(t('in_person_proofing.headings.state_id_milestone_2')) %> <% end %>

@@ -84,32 +84,89 @@ %> -

+
<%= render ValidatedFieldComponent.new( - name: :state_id_jurisdiction, - collection: us_states_territories, + name: :state_id_number, form: f, - hint: t('in_person_proofing.form.state_id.state_id_jurisdiction_hint'), - label: t('in_person_proofing.form.state_id.state_id_jurisdiction'), + hint: t('in_person_proofing.form.state_id.state_id_number_hint'), + input_html: { value: pii[:state_id_number] }, + label: t('in_person_proofing.form.state_id.state_id_number'), label_html: { class: 'usa-label' }, - prompt: t('in_person_proofing.form.state_id.state_id_jurisdiction_prompt'), + maxlength: 255, required: true, - selected: pii[:state_id_jurisdiction], ) %>
+ + <% if IdentityConfig.store.in_person_capture_secondary_id_enabled %> + <%= render ValidatedFieldComponent.new( + name: :state_id_address1, + form: f, + input_html: { value: pii[:state_id_address1] }, + label: t('in_person_proofing.form.state_id.address1'), + label_html: { class: 'usa-label' }, + maxlength: 255, + required: true, + ) %> -
+ <%= render ValidatedFieldComponent.new( + name: :state_id_address2, + form: f, + input_html: { value: pii[:state_id_address2] }, + label: t('in_person_proofing.form.state_id.address2'), + label_html: { class: 'usa-label' }, + maxlength: 255, + required: false, + ) %> + + <%= render ValidatedFieldComponent.new( + name: :state_id_city, + form: f, + input_html: { value: pii[:state_id_city] }, + label: t('in_person_proofing.form.state_id.city'), + label_html: { class: 'usa-label' }, + maxlength: 255, + required: true, + ) %> + <% end %> +
<%= render ValidatedFieldComponent.new( - name: :state_id_number, + name: :state_id_jurisdiction, + collection: us_states_territories, form: f, - hint: t('in_person_proofing.form.state_id.state_id_number_hint'), - input_html: { value: pii[:state_id_number] }, - label: t('in_person_proofing.form.state_id.state_id_number'), + hint: t('in_person_proofing.form.state_id.state_id_jurisdiction_hint'), + label: t('in_person_proofing.form.state_id.state_id_jurisdiction'), label_html: { class: 'usa-label' }, - maxlength: 255, + prompt: t('in_person_proofing.form.state_id.state_id_jurisdiction_prompt'), required: true, + selected: pii[:state_id_jurisdiction], ) %>
+ <% if IdentityConfig.store.in_person_capture_secondary_id_enabled %> +
+ <%= render ValidatedFieldComponent.new( + name: :state_id_zipcode, + form: f, + input_html: { value: pii[:state_id_zipcode] }, + label: t('in_person_proofing.form.state_id.zipcode'), + label_html: { class: 'usa-label' }, + maxlength: 255, + required: true, + ) %> +
+ <%= render ValidatedFieldComponent.new( + as: :radio_buttons, + checked: pii[:same_address_as_id], + collection: [ + [t('in_person_proofing.form.state_id.same_address_as_id_yes'), true], + [t('in_person_proofing.form.state_id.same_address_as_id_no'), false], + ], + form: f, + label: t('in_person_proofing.form.state_id.same_address_as_id'), + name: :same_address_as_id, + required: true, + wrapper: :uswds_radio_buttons, + ) %> + <% end %> <%= f.submit do %> <% if updating_state_id %> diff --git a/app/views/idv/shared/_verify.html.erb b/app/views/idv/shared/_verify.html.erb index 2fc5aca3561..efdd21aa577 100644 --- a/app/views/idv/shared/_verify.html.erb +++ b/app/views/idv/shared/_verify.html.erb @@ -20,7 +20,9 @@ locals:
<%= t('idv.form.dob') %>:
-
<%= pii[:dob] %>
+
+ <%= I18n.l(Date.parse(pii[:dob]), format: I18n.t('time.formats.event_date')) %> +
<% if !remote_identity_proofing %>
diff --git a/app/views/session_timeout/_warning.html.erb b/app/views/session_timeout/_warning.html.erb index 652ad083fa9..c4871223b5d 100644 --- a/app/views/session_timeout/_warning.html.erb +++ b/app/views/session_timeout/_warning.html.erb @@ -8,16 +8,54 @@

- <%= modal_presenter.message %> + <%= t( + # i18n-tasks-use t('notices.timeout_warning.signed_in.message_html') + # i18n-tasks-use t('notices.timeout_warning.partially_signed_in.message_html') + 'message_html', + scope: modal_presenter.translation_scope, + time_left_in_session: render( + CountdownComponent.new( + expiration: Time.zone.now, + start_immediately: false, + ), + ), + ) %>

- <%= modal_presenter.sr_message %> + <%= t( + # i18n-tasks-use t('notices.timeout_warning.signed_in.live_region_message_html') + # i18n-tasks-use t('notices.timeout_warning.partially_signed_in.live_region_message_html') + 'live_region_message_html', + scope: modal_presenter.translation_scope, + time_left_in_session: render( + CountdownComponent.new( + expiration: Time.zone.now, + update_interval: 30.seconds, + start_immediately: false, + ), + ), + ) %>

- <%= button_tag modal_presenter.continue, - id: 'session-keepalive-btn', - class: 'usa-button usa-button--big usa-button--full-width margin-bottom-2' %> - <%= link_to modal_presenter.sign_out, - destroy_user_session_path, - class: 'usa-button usa-button--big usa-button--full-width usa-button--outline' %> + <%= render ButtonComponent.new( + type: :button, + id: 'session-keepalive-btn', + big: true, + full_width: true, + class: 'margin-bottom-2', + ).with_content( + # i18n-tasks-use t('notices.timeout_warning.signed_in.continue') + # i18n-tasks-use t('notices.timeout_warning.partially_signed_in.continue') + t('continue', scope: modal_presenter.translation_scope), + ) %> + <%= render ButtonComponent.new( + action: ->(**tag_options, &block) { link_to(destroy_user_session_path, **tag_options, &block) }, + big: true, + full_width: true, + outline: true, + ).with_content( + # i18n-tasks-use t('notices.timeout_warning.signed_in.sign_out') + # i18n-tasks-use t('notices.timeout_warning.partially_signed_in.sign_out') + t('sign_out', scope: modal_presenter.translation_scope), + ) %> <% end %> diff --git a/config/application.yml.default b/config/application.yml.default index 65a69014dd5..c421f959f4a 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -116,7 +116,6 @@ good_job_max_threads: 5 good_job_queues: 'default:5;low:1;*' good_job_queue_select_limit: 5_000 gpo_designated_receiver_pii: '{}' -gpo_personal_key_after_otp: false hide_phone_mfa_signup: false identity_pki_disabled: false identity_pki_local_dev: false diff --git a/config/initializers/job_configurations.rb b/config/initializers/job_configurations.rb index 3685875a79e..2702485bb7a 100644 --- a/config/initializers/job_configurations.rb +++ b/config/initializers/job_configurations.rb @@ -1,4 +1,3 @@ -cron_1m = '* * * * *' cron_5m = '0/5 * * * *' cron_1h = '0 * * * *' cron_24h = '0 0 * * *' @@ -105,12 +104,6 @@ class: 'HeartbeatJob', cron: cron_5m, }, - # Queue psql stats job to GoodJob - psql_stats_job: { - class: 'PsqlStatsJob', - cron: cron_1m, - args: -> { [Time.zone.now] }, - }, # Queue usps proofing job to GoodJob get_usps_proofing_results_job: { class: 'GetUspsProofingResultsJob', @@ -146,6 +139,12 @@ cron: cron_24h, args: -> { [Time.zone.today] }, }, + # Send Duplicate SSN report to S3 + duplicate_ssn: { + class: 'Reports::DuplicateSsnReport', + cron: cron_24h, + args: -> { [Time.zone.today] }, + }, } end # rubocop:enable Metrics/BlockLength diff --git a/config/initializers/redis.rb b/config/initializers/redis.rb index ca87a0a353e..f8599f44a30 100644 --- a/config/initializers/redis.rb +++ b/config/initializers/redis.rb @@ -3,8 +3,5 @@ end REDIS_THROTTLE_POOL = ConnectionPool.new(size: IdentityConfig.store.redis_throttle_pool_size) do - Redis::Namespace.new( - 'throttle', - redis: Redis.new(url: IdentityConfig.store.redis_throttle_url), - ) + Redis.new(url: IdentityConfig.store.redis_throttle_url) end diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb index 06c02d9ec19..ca4d31ebe04 100644 --- a/config/initializers/simple_form.rb +++ b/config/initializers/simple_form.rb @@ -77,8 +77,8 @@ # Helper proc to define different types of radio button wrappers radio_button_builder = proc do |name, bordered| - item_label_class = 'usa-radio__label width-full text-no-wrap' + - (bordered ? '' : ' margin-top-0') + item_label_class = 'usa-radio__label width-full' + + (bordered ? ' text-no-wrap' : ' margin-top-0') legend_class = 'usa-label' + (bordered ? '' : ' margin-bottom-2') input_class = 'usa-radio__input' + (bordered ? ' usa-radio__input--bordered' : '') @@ -98,7 +98,9 @@ cr.use :input, class: input_class end end - gr.wrapper(:grid_column_gap, tag: :div, class: 'grid-col-4 tablet:grid-col-6') {} + if bordered + gr.wrapper(:grid_column_gap, tag: :div, class: 'grid-col-4 tablet:grid-col-6') {} + end end b.use :error, wrap_with: { tag: 'div', class: 'usa-error-message' } end diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml index ef8b12b0005..bf02a3e1993 100644 --- a/config/locales/idv/en.yml +++ b/config/locales/idv/en.yml @@ -153,7 +153,6 @@ en: resend: Send me another letter timeframe_html: Letters are sent the next business day via USPS First Class Mail and typically take 3 to 7 business days to arrive. - mail_sent: Your letter is on its way otp_delivery_method_description: If you entered a landline above, please select “Phone call” below. personal_key: This is your new personal key. Write it down and keep it in a safe place. You will need it if you ever lose your password. diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml index 9ae6520090d..62e927149fa 100644 --- a/config/locales/idv/es.yml +++ b/config/locales/idv/es.yml @@ -163,7 +163,6 @@ es: timeframe_html: Las cartas se envían al día siguiente por First Class Mail de USPS y suelen tardar entre 3 y 7 días hábiles en llegar. - mail_sent: Su carta está en camino otp_delivery_method_description: Si ha introducido un teléfono fijo más arriba, seleccione “Llamada telefónica” más abajo. personal_key: Esta es su nueva clave personal. Escríbala y guárdela en un lugar diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml index f34e8031dae..a7406a5ff3c 100644 --- a/config/locales/idv/fr.yml +++ b/config/locales/idv/fr.yml @@ -172,7 +172,6 @@ fr: timeframe_html: Les lettres sont envoyées les jours ouvrables par courriel de première classe de USPS et prennent généralement entre trois à sept jours ouvrables pour être reçues. - mail_sent: Votre lettre est en route otp_delivery_method_description: Si vous avez saisi une ligne fixe ci-dessus, veuillez sélectionner « Appel téléphonique » ci-dessous. personal_key: Il s’agit de votre nouvelle clé personnelle. Notez-la et diff --git a/config/locales/in_person_proofing/en.yml b/config/locales/in_person_proofing/en.yml index 35724d29f4b..b332767ad65 100644 --- a/config/locales/in_person_proofing/en.yml +++ b/config/locales/in_person_proofing/en.yml @@ -95,6 +95,9 @@ en: same_address_choice_yes: This address is on my state-issued ID state_prompt: '- Select -' state_id: + address1: Address + address2: Address line 2 (optional) + city: City date_hint: day: 'Example: 28' month: 'Example: 4' @@ -112,11 +115,15 @@ en: missing_month_day_year: Enter a date of birth range_min_age: You must be over 13 years of age to use %{app_name} range_overflow: Enter a date that is in the past + same_address_as_id: Is your current residential address listed on your state-issued ID? + same_address_as_id_no: No, my current residential address is not listed on my state-issued ID + same_address_as_id_yes: Yes, my current residential address is listed on my state-issued ID state_id_jurisdiction: State state_id_jurisdiction_hint: Select the state shown on your ID state_id_jurisdiction_prompt: '- Select -' state_id_number: ID number state_id_number_hint: May include letters and numbers + zipcode: ZIP Code headings: address: Enter your current address barcode: You’re ready to verify your identity in person @@ -126,7 +133,7 @@ en: po_search: location: Find a participating Post Office prepare: Verify your identity in person - state_id: Enter the information on your ID + state_id_milestone_2: Enter the information on your state-issued ID switch_back: Switch back to your computer to prepare to verify your identity in person update_address: Update your current address update_state_id: Update the information on your ID diff --git a/config/locales/in_person_proofing/es.yml b/config/locales/in_person_proofing/es.yml index 03f7fa8d6d2..28278be9204 100644 --- a/config/locales/in_person_proofing/es.yml +++ b/config/locales/in_person_proofing/es.yml @@ -106,6 +106,9 @@ es: same_address_choice_yes: Esta dirección aparece en mi cédula de identidad emitida por el estado state_prompt: '- Seleccione -' state_id: + address1: Dirección + address2: Línea de dirección 2 (opcional) + city: Ciudad date_hint: day: 'Ejemplo: 28' month: 'Ejemplo: 4' @@ -124,11 +127,18 @@ es: missing_month_day_year: Introduce una fecha de nacimiento range_min_age: Debe tener más de 13 años para usar %{app_name} range_overflow: Ingrese una fecha que esté en el pasado + same_address_as_id: ¿Tu domicilio actual aparece en tu identificación emitida + por el estado? + same_address_as_id_no: No, mi domicilio actual no aparece en mi identificación + emitida por el estado. + same_address_as_id_yes: Sí, mi domicilio actual aparece en mi identificación + emitida por el estado state_id_jurisdiction: Estado state_id_jurisdiction_hint: Seleccione el estado que aparece en su cédula state_id_jurisdiction_prompt: '- Seleccione -' state_id_number: Número de cédula state_id_number_hint: Puede incluir letras y números + zipcode: Código postal headings: address: Ingrese su dirección actual barcode: Está listo para verificar su identidad en persona @@ -138,7 +148,7 @@ es: po_search: location: Encuentre una oficina de correos participante prepare: Verifique su identidad en persona - state_id: Ingrese la información de su cédula + state_id_milestone_2: Ingresa la información de tu identificación emitida por el estado switch_back: Vuelva a su computadora para prepararse para verificar su identidad en persona update_address: Actualizar su dirección actual diff --git a/config/locales/in_person_proofing/fr.yml b/config/locales/in_person_proofing/fr.yml index e2b5b154caa..d54eb1ceb35 100644 --- a/config/locales/in_person_proofing/fr.yml +++ b/config/locales/in_person_proofing/fr.yml @@ -108,6 +108,9 @@ fr: same_address_choice_yes: Cette adresse figure sur mon document d’identité nationale state_prompt: '- Sélectionnez -' state_id: + address1: Adresse + address2: Adresse Ligne 2 (optional) + city: Ville date_hint: day: 'Exemple: 28' month: 'Exemple: 4' @@ -126,11 +129,18 @@ fr: missing_month_day_year: Entrez une date de naissance range_min_age: Vous devez avoir plus de 13 ans pour utiliser %{app_name} range_overflow: Entrez une date qui est dans le passé + same_address_as_id: L’adresse de votre domicile actuel figure-t-elle sur votre + carte d’identité délivrée par l’État? + same_address_as_id_no: Non, l’adresse de mon domicile actuel ne figure pas sur + ma carte d’identité délivrée par l’État + same_address_as_id_yes: Oui, l’adresse de mon domicile actuel figure sur ma + carte d’identité délivrée par l’État state_id_jurisdiction: État state_id_jurisdiction_hint: Sélectionnez l’État figurant sur votre document d’identité state_id_jurisdiction_prompt: '- Sélectionnez -' state_id_number: Numéro d’identification state_id_number_hint: Peut comprendre des lettres et des chiffres + zipcode: Code postal headings: address: Entrez votre adresse actuelle barcode: Vous êtes prêt à vérifier votre identité en personne @@ -140,7 +150,8 @@ fr: po_search: location: Trouver un bureau de poste participant prepare: Vérifiez votre identité en personne - state_id: Saisissez les informations figurant sur votre document d’identité + state_id_milestone_2: Saisissez les informations figurant sur votre carte + d’identité délivrée par l’État switch_back: Retournez sur votre ordinateur pour vous préparer à vérifier votre identité en personne update_address: Mettre à jour votre adresse actuelle diff --git a/config/locales/notices/en.yml b/config/locales/notices/en.yml index eb8f8ee4e82..1d4e0b82ee8 100644 --- a/config/locales/notices/en.yml +++ b/config/locales/notices/en.yml @@ -47,18 +47,20 @@ en: timeout_warning: partially_signed_in: continue: Continue sign in + live_region_message_html: You will be signed out in %{time_left_in_session}. + Select “keep me signed in” to stay logged in. Select “sign me out” to + sign out. message_html: For your security, in %{time_left_in_session} we will cancel your sign in. sign_out: Cancel sign in - sr_message_html: You will be signed out in %{time_left_in_session}. Select “keep - me signed in” to stay logged in. Select “sign me out” to sign out. signed_in: continue: Keep me signed in + live_region_message_html: You will be signed out in %{time_left_in_session}. + Select “keep me signed in” to stay logged in. Select “sign me out” to + sign out. message_html: For your security, we will sign you out in %{time_left_in_session} unless you tell us otherwise. sign_out: Sign me out - sr_message_html: You will be signed out in %{time_left_in_session}. Select “keep - me signed in” to stay logged in. Select “sign me out” to sign out. totp_configured: An authentication app was added to your account. totp_disabled: Your authentication app was deleted from your account. use_diff_email: diff --git a/config/locales/notices/es.yml b/config/locales/notices/es.yml index 04550a8c2b3..8ace2e6f115 100644 --- a/config/locales/notices/es.yml +++ b/config/locales/notices/es.yml @@ -49,19 +49,19 @@ es: timeout_warning: partially_signed_in: continue: Continuar el inicio de sesión + live_region_message_html: Tu sesión se cerrará en %{time_left_in_session}. + Selecciona “seguir conectado” para mantener tu sesión activa. + Seleccione “desconécteme” para cerrar la sesión. message_html: Para su seguridad, en %{time_left_in_session} cancelaremos su acceso. sign_out: Cancelar el inicio de sesión - sr_message_html: Tu sesión se cerrará en %{time_left_in_session}. Selecciona - “seguir conectado” para mantener tu sesión activa. Seleccione - “desconécteme” para cerrar la sesión. signed_in: continue: Manténgame conectado + live_region_message_html: Tu sesión se cerrará en %{time_left_in_session}. + Selecciona “seguir conectado” para mantener tu sesión activa. + Seleccione “desconécteme” para cerrar la sesión. message_html: Para su seguridad, terminaremos su sesión en %{time_left_in_session} a menos que nos indique lo contrario. sign_out: Desconécteme - sr_message_html: Tu sesión se cerrará en %{time_left_in_session}. Selecciona - “seguir conectado” para mantener tu sesión activa. Seleccione - “desconécteme” para cerrar la sesión. totp_configured: Una aplicación de autenticación fue agregada a tu cuenta. totp_disabled: Tu aplicación de autenticación fue eliminada de tu cuenta. use_diff_email: diff --git a/config/locales/notices/fr.yml b/config/locales/notices/fr.yml index 30fa3da0fea..42e0db9d12e 100644 --- a/config/locales/notices/fr.yml +++ b/config/locales/notices/fr.yml @@ -49,20 +49,20 @@ fr: timeout_warning: partially_signed_in: continue: Continuer la connexion + live_region_message_html: Vous serez déconnecté dans %{time_left_in_session}. + Sélectionnez « garder ma connexion » pour rester connecté. + Sélectionnez « déconnectez-moi » pour vous déconnecter. message_html: Pour votre sécurité, nous annulerons votre connexion dans %{time_left_in_session}. sign_out: Annuler la connexion - sr_message_html: Vous serez déconnecté dans %{time_left_in_session}. - Sélectionnez « garder ma connexion » pour rester connecté. - Sélectionnez « déconnectez-moi » pour vous déconnecter. signed_in: continue: Gardez ma connexion active + live_region_message_html: Vous serez déconnecté dans %{time_left_in_session}. + Sélectionnez « garder ma connexion » pour rester connecté. + Sélectionnez « déconnectez-moi » pour vous déconnecter. message_html: Pour votre sécurité, nous vous déconnecterons dans %{time_left_in_session}, sauf en cas d’avis contraire de votre part. sign_out: Déconnectez-moi - sr_message_html: Vous serez déconnecté dans %{time_left_in_session}. - Sélectionnez « garder ma connexion » pour rester connecté. - Sélectionnez « déconnectez-moi » pour vous déconnecter. totp_configured: Une application d’authentification a été ajoutée à votre compte. totp_disabled: Votre application d’authentification a été supprimée de votre compte. use_diff_email: diff --git a/config/routes.rb b/config/routes.rb index c79649eaadd..b4d4e9db409 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -251,7 +251,6 @@ get '/second_mfa_setup' => 'users/mfa_selection#index' patch '/second_mfa_setup' => 'users/mfa_selection#update' get '/phone_setup' => 'users/phone_setup#index' - patch '/phone_setup' => 'users/phone_setup#create' # TODO: Remove after next deploy post '/phone_setup' => 'users/phone_setup#create' get '/users/two_factor_authentication' => 'users/two_factor_authentication#show', as: :user_two_factor_authentication # route name is used by two_factor_authentication gem diff --git a/db/primary_migrate/20230309201053_add_sp_issuer_field_to_account_reset_request.rb b/db/primary_migrate/20230309201053_add_sp_issuer_field_to_account_reset_request.rb new file mode 100644 index 00000000000..44c34f3aed9 --- /dev/null +++ b/db/primary_migrate/20230309201053_add_sp_issuer_field_to_account_reset_request.rb @@ -0,0 +1,5 @@ +class AddSpIssuerFieldToAccountResetRequest < ActiveRecord::Migration[7.0] + def change + add_column :account_reset_requests, :requesting_issuer, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 7a161d48c19..2edc4ee8038 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_03_07_203559) do +ActiveRecord::Schema[7.0].define(version: 2023_03_09_201053) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "pgcrypto" @@ -26,6 +26,7 @@ t.string "granted_token" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false + t.string "requesting_issuer" t.index ["cancelled_at", "granted_at", "requested_at"], name: "index_account_reset_requests_on_timestamps" t.index ["granted_token"], name: "index_account_reset_requests_on_granted_token", unique: true t.index ["request_token"], name: "index_account_reset_requests_on_request_token", unique: true diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 1f70d93cd85..3b100b9cb70 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -193,7 +193,6 @@ def self.build_store(config_map) config.add(:good_job_queues, type: :string) config.add(:good_job_queue_select_limit, type: :integer) config.add(:gpo_designated_receiver_pii, type: :json, options: { symbolize_names: true }) - config.add(:gpo_personal_key_after_otp, type: :boolean) config.add(:hide_phone_mfa_signup, type: :boolean) config.add(:hmac_fingerprinter_key, type: :string) config.add(:hmac_fingerprinter_key_queue, type: :json) diff --git a/package.json b/package.json index 4333a229151..b20906934eb 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "source-map-loader": "^4.0.0", - "webpack": "^5.74.0", + "webpack": "^5.76.1", "webpack-assets-manifest": "^5.1.0", "webpack-cli": "^4.10.0", "zxcvbn": "4.4.2" @@ -68,7 +68,7 @@ "dirty-chai": "^2.0.1", "dom-accessibility-api": "^0.5.14", "eslint": "^8.24.0", - "eslint-plugin-import": "^2.26.0", + "eslint-plugin-import": "^2.27.5", "eslint-plugin-jsx-a11y": "^6.6.1", "eslint-plugin-mocha": "^10.1.0", "eslint-plugin-prettier": "^4.2.1", diff --git a/spec/controllers/account_reset/delete_account_controller_spec.rb b/spec/controllers/account_reset/delete_account_controller_spec.rb index 8e0ca01ad78..1c22945b5c5 100644 --- a/spec/controllers/account_reset/delete_account_controller_spec.rb +++ b/spec/controllers/account_reset/delete_account_controller_spec.rb @@ -34,26 +34,6 @@ expect(response).to redirect_to account_reset_confirm_delete_account_url end - it 'logs a good token to the attempts api' do - user = create(:user, :signed_up, :with_backup_code) - create(:phone_configuration, user: user, phone: Faker::PhoneNumber.cell_phone) - create_list(:webauthn_configuration, 2, user: user) - create_account_reset_request_for(user) - grant_request(user) - - session[:granted_token] = AccountResetRequest.first.granted_token - stub_attempts_tracker - - expect(@irs_attempts_api_tracker).to receive(:account_reset_account_deleted).with( - success: true, - failure_reason: nil, - ) - - delete :delete - - expect(response).to redirect_to account_reset_confirm_delete_account_url - end - it 'redirects to root if the token does not match one in the DB' do session[:granted_token] = 'foo' stub_analytics @@ -74,18 +54,6 @@ expect(flash[:error]).to eq(invalid_token_message) end - it 'logs an error in irs attempts tracker' do - session[:granted_token] = 'foo' - stub_attempts_tracker - - expect(@irs_attempts_api_tracker).to receive(:account_reset_account_deleted).with( - success: false, - failure_reason: invalid_token_error, - ) - - delete :delete - end - it 'displays a flash and redirects to root if the token is missing' do stub_analytics properties = { diff --git a/spec/controllers/concerns/idv/step_indicator_concern_spec.rb b/spec/controllers/concerns/idv/step_indicator_concern_spec.rb index cdfb9f763b9..622bd9c8d24 100644 --- a/spec/controllers/concerns/idv/step_indicator_concern_spec.rb +++ b/spec/controllers/concerns/idv/step_indicator_concern_spec.rb @@ -11,36 +11,82 @@ before { stub_sign_in(user) } describe '#step_indicator_steps' do + def force_gpo + idv_session = instance_double(Idv::Session) + allow(idv_session).to receive(:method_missing). + with(:address_verification_mechanism). + and_return('gpo') + allow(controller).to receive(:idv_session).and_return(idv_session) + end + subject(:steps) { controller.step_indicator_steps } - it 'returns doc auth steps' do - expect(steps).to eq Idv::Flows::DocAuthFlow::STEP_INDICATOR_STEPS - end + context 'without an in-person proofing component' do + let(:doc_auth_step_indicator_steps) do + [ + { name: :getting_started }, + { name: :verify_id }, + { name: :verify_info }, + { name: :verify_phone_or_address }, + { name: :secure_account }, + ] + end - context 'with pending profile' do - let(:profile) { create(:profile, deactivation_reason: :gpo_verification_pending) } + let(:doc_auth_step_indicator_steps_gpo) do + [ + { name: :getting_started }, + { name: :verify_id }, + { name: :verify_info }, + { name: :get_a_letter }, + { name: :secure_account }, + ] + end - it 'returns doc auth gpo steps' do - expect(steps).to eq Idv::Flows::DocAuthFlow::STEP_INDICATOR_STEPS_GPO + context 'without a pending profile' do + it 'returns doc auth steps' do + expect(steps).to eq doc_auth_step_indicator_steps + end end - end - context 'with gpo address verification method' do - before do - idv_session = instance_double(Idv::Session) - allow(idv_session).to receive(:method_missing). - with(:address_verification_mechanism). - and_return('gpo') - allow(controller).to receive(:idv_session).and_return(idv_session) + context 'with a pending profile' do + let(:profile) { create(:profile, deactivation_reason: :gpo_verification_pending) } + + it 'returns doc auth gpo steps' do + expect(steps).to eq doc_auth_step_indicator_steps_gpo + end end - it 'returns doc auth gpo steps' do - expect(steps).to eq Idv::Flows::DocAuthFlow::STEP_INDICATOR_STEPS_GPO + context 'with gpo address verification method' do + before { force_gpo } + + it 'returns doc auth gpo steps' do + expect(steps).to eq doc_auth_step_indicator_steps_gpo + end end end context 'with in person proofing component' do - context 'with proofing component via pending profile' do + let(:in_person_step_indicator_steps) do + [ + { name: :find_a_post_office }, + { name: :verify_info }, + { name: :verify_phone_or_address }, + { name: :secure_account }, + { name: :go_to_the_post_office }, + ] + end + + let(:in_person_step_indicator_steps_gpo) do + [ + { name: :find_a_post_office }, + { name: :verify_info }, + { name: :secure_account }, + { name: :get_a_letter }, + { name: :go_to_the_post_office }, + ] + end + + context 'via pending profile' do let(:profile) do create( :profile, @@ -50,30 +96,24 @@ end it 'returns in person gpo steps' do - expect(steps).to eq Idv::Flows::InPersonFlow::STEP_INDICATOR_STEPS_GPO + expect(steps).to eq in_person_step_indicator_steps_gpo end end - context 'with proofing component via current idv session' do + context 'via current idv session' do before do ProofingComponent.create(user: user, document_check: Idp::Constants::Vendors::USPS) end it 'returns in person steps' do - expect(steps).to eq Idv::Flows::InPersonFlow::STEP_INDICATOR_STEPS + expect(steps).to eq in_person_step_indicator_steps end context 'with gpo address verification method' do - before do - idv_session = instance_double(Idv::Session) - allow(idv_session).to receive(:method_missing). - with(:address_verification_mechanism). - and_return('gpo') - allow(controller).to receive(:idv_session).and_return(idv_session) - end + before { force_gpo } it 'returns in person gpo steps' do - expect(steps).to eq Idv::Flows::InPersonFlow::STEP_INDICATOR_STEPS_GPO + expect(steps).to eq in_person_step_indicator_steps_gpo end end end diff --git a/spec/controllers/concerns/idv_step_concern_spec.rb b/spec/controllers/concerns/idv_step_concern_spec.rb index e60cc8692e9..9a4384acae9 100644 --- a/spec/controllers/concerns/idv_step_concern_spec.rb +++ b/spec/controllers/concerns/idv_step_concern_spec.rb @@ -128,8 +128,7 @@ def show context 'the user has completed the verify info step' do it 'does not redirect and renders the view' do - idv_session.profile_confirmation = true - idv_session.resolution_successful = 'phone' + idv_session.resolution_successful = true get :show @@ -140,7 +139,6 @@ def show context 'the user has not completed the verify info step' do it 'redirects to the remote verify info step' do - idv_session.profile_confirmation = nil idv_session.resolution_successful = nil get :show @@ -151,7 +149,6 @@ def show context 'the user has not completed the verify info step with an in-person enrollment' do it 'redirects to the in-person verify info step' do - idv_session.profile_confirmation = nil idv_session.resolution_successful = nil ProofingComponent.find_or_create_by( diff --git a/spec/controllers/idv/gpo_verify_controller_spec.rb b/spec/controllers/idv/gpo_verify_controller_spec.rb index 5dabc273dea..45c2c356f39 100644 --- a/spec/controllers/idv/gpo_verify_controller_spec.rb +++ b/spec/controllers/idv/gpo_verify_controller_spec.rb @@ -125,14 +125,6 @@ disavowal_event_count = user.events.where(event_type: :account_verified, ip: '0.0.0.0'). where.not(disavowal_token_fingerprint: nil).count expect(disavowal_event_count).to eq 1 - expect(response).to redirect_to(sign_up_completed_url) - end - - it 'redirects to the personal key page if new gpo flow is enabled' do - allow(IdentityConfig.store).to receive(:gpo_personal_key_after_otp).and_return(true) - - action - expect(response).to redirect_to(idv_personal_key_url) end @@ -205,7 +197,7 @@ disavowal_event_count = user.events.where(event_type: :account_verified, ip: '0.0.0.0'). where.not(disavowal_token_fingerprint: nil).count expect(disavowal_event_count).to eq 1 - expect(response).to redirect_to(sign_up_completed_url) + expect(response).to redirect_to(idv_personal_key_url) end end end diff --git a/spec/controllers/idv/personal_key_controller_spec.rb b/spec/controllers/idv/personal_key_controller_spec.rb index a01e4a1e972..513533f31a0 100644 --- a/spec/controllers/idv/personal_key_controller_spec.rb +++ b/spec/controllers/idv/personal_key_controller_spec.rb @@ -131,14 +131,7 @@ def index subject.idv_session.address_verification_mechanism = 'gpo' end - it 'sets flash.now[:success]' do - get :show - expect(flash[:success]).to eq t('idv.messages.mail_sent') - end - it 'does not show a flash in new gpo flow' do - allow(IdentityConfig.store).to receive(:gpo_personal_key_after_otp).and_return(true) - get :show expect(flash[:success]).to eq nil end @@ -206,8 +199,6 @@ def index context 'with gpo personal key after verification' do it 'redirects to sign up completed_url for a sp' do - allow(IdentityConfig.store).to receive(:gpo_personal_key_after_otp). - and_return(true) allow(subject).to receive(:pending_profile?).and_return(false) subject.session[:sp] = { ial2: true } diff --git a/spec/controllers/idv/phone_controller_spec.rb b/spec/controllers/idv/phone_controller_spec.rb index 8c60e77ca28..7fb7a9b8525 100644 --- a/spec/controllers/idv/phone_controller_spec.rb +++ b/spec/controllers/idv/phone_controller_spec.rb @@ -78,7 +78,6 @@ context 'when the user has not finished the verify step' do before do subject.idv_session.applicant = nil - subject.idv_session.profile_confirmation = nil subject.idv_session.resolution_successful = nil allow(controller).to receive(:confirm_idv_applicant_created).and_call_original diff --git a/spec/controllers/idv/review_controller_spec.rb b/spec/controllers/idv/review_controller_spec.rb index 80e1b1a203a..bf0c77b6ab7 100644 --- a/spec/controllers/idv/review_controller_spec.rb +++ b/spec/controllers/idv/review_controller_spec.rb @@ -18,8 +18,7 @@ current_user: user, service_provider: nil, ) - idv_session.profile_confirmation = true - idv_session.resolution_successful = 'phone' + idv_session.resolution_successful = true idv_session.vendor_phone_confirmation = true idv_session.user_phone_confirmation = true idv_session.applicant = applicant.with_indifferent_access @@ -610,7 +609,6 @@ def show end it 'redirects to come back later page' do - allow(IdentityConfig.store).to receive(:gpo_personal_key_after_otp).and_return(true) put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } expect(response).to redirect_to idv_come_back_later_url diff --git a/spec/controllers/idv/session_errors_controller_spec.rb b/spec/controllers/idv/session_errors_controller_spec.rb index 60ba2af7959..0431345e16f 100644 --- a/spec/controllers/idv/session_errors_controller_spec.rb +++ b/spec/controllers/idv/session_errors_controller_spec.rb @@ -34,7 +34,7 @@ end context 'the user is authenticated and has confirmed their profile' do - let(:idv_session_profile_confirmation) { true } + let(:verify_info_step_complete) { true } let(:user) { build(:user) } it 'redirects to the phone url' do @@ -102,12 +102,12 @@ describe Idv::SessionErrorsController do let(:idv_session) { double } - let(:idv_session_profile_confirmation) { false } + let(:verify_info_step_complete) { false } let(:user) { nil } before do - allow(idv_session).to receive(:profile_confirmation). - and_return(idv_session_profile_confirmation) + allow(idv_session).to receive(:verify_info_step_complete?). + and_return(verify_info_step_complete) allow(controller).to receive(:idv_session).and_return(idv_session) stub_sign_in(user) if user stub_analytics diff --git a/spec/controllers/idv/ssn_controller_spec.rb b/spec/controllers/idv/ssn_controller_spec.rb index 1d2ec862b51..19a9a49da96 100644 --- a/spec/controllers/idv/ssn_controller_spec.rb +++ b/spec/controllers/idv/ssn_controller_spec.rb @@ -110,6 +110,14 @@ expect(flow_session['pii_from_doc'][:ssn]).to eq(ssn) end + it 'redirects to address controller for Puerto Rico addresses' do + flow_session['pii_from_doc'][:state] = 'PR' + + put :update, params: params + + expect(response).to redirect_to(idv_address_url) + end + it 'sends analytics_submitted event with correct step count' do get :show put :update, params: params diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb index ef51bd651bd..32718d46f86 100644 --- a/spec/controllers/idv/verify_info_controller_spec.rb +++ b/spec/controllers/idv/verify_info_controller_spec.rb @@ -112,7 +112,7 @@ context 'when the user has already verified their info' do it 'redirects to the review controller' do - controller.idv_session.profile_confirmation = true + controller.idv_session.resolution_successful = true get :show diff --git a/spec/features/account_reset/delete_account_spec.rb b/spec/features/account_reset/delete_account_spec.rb index 61d0d786310..cb0f3ef5e53 100644 --- a/spec/features/account_reset/delete_account_spec.rb +++ b/spec/features/account_reset/delete_account_spec.rb @@ -2,11 +2,23 @@ describe 'Account Reset Request: Delete Account', email: true do include PushNotificationsHelper + include OidcAuthHelper + include IrsAttemptsApiTrackingHelper let(:user) { create(:user, :signed_up) } let(:user_email) { user.email_addresses.first.email } let(:push_notification_url) { 'http://localhost/push_notifications' } + let(:service_provider) do + create( + :service_provider, + active: true, + redirect_uris: ['http://localhost:7654/auth/result'], + ial: 2, + irs_attempts_api_enabled: true, + ) + end + context 'as an IAL1 user' do it 'allows the user to delete their account after 24 hours' do signin(user_email, user.password) @@ -173,4 +185,92 @@ expect(page).to have_current_path(new_user_session_path) end end + + context 'logs IRS attempts api events' do + before do + allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled).and_return(true) + mock_irs_attempts_api_encryption_key + end + + it 'allows the user to delete their account after 24 hours and log irs event' do + visit_idp_from_ial1_oidc_sp( + client_id: service_provider.issuer, + ) + + signin(user_email, user.password) + click_link t('two_factor_authentication.login_options_link_text') + click_link t('two_factor_authentication.account_reset.link') + expect(page). + to have_content strip_tags( + t('account_reset.recovery_options.try_method_again'), + ) + click_link t('account_reset.request.yes_continue') + expect(page). + to have_content strip_tags( + t('account_reset.request.delete_account'), + ) + click_button t('account_reset.request.yes_continue') + + expect(page). + to have_content strip_tags( + t('account_reset.confirm_request.instructions_start'), + ) + expect(page). + to have_content user_email + expect(page). + to have_content strip_tags( + t('account_reset.confirm_request.instructions_end'), + ) + expect(page).to have_content t('account_reset.confirm_request.security_note') + expect(page).to have_content t('account_reset.confirm_request.close_window') + + reset_email + set_new_browser_session + events = irs_attempts_api_tracked_events(timestamp: Time.zone.now) + expected_event_types = %w[mfa-login-phone-otp-sent login-email-and-password-auth + account-reset-request-submitted] + received_event_types = events.map(&:event_type) + + expect(events.count).to eq received_event_types.count + expect(received_event_types).to match_array(expected_event_types) + + travel_to(Time.zone.now + 2.days + 1.second) do + AccountReset::GrantRequestsAndSendEmails.new.perform(Time.zone.today) + open_last_email + click_email_link_matching(/delete_account\?token/) + + expect(page).to have_content(t('account_reset.delete_account.title')) + expect(page).to have_current_path(account_reset_delete_account_path) + + click_button t('account_reset.request.yes_continue') + + expect(page).to have_content( + strip_tags( + t( + 'account_reset.confirm_delete_account.info_html', + email: user_email, + link: t('account_reset.confirm_delete_account.link_text'), + ), + ), + ) + expect(page).to have_current_path(account_reset_confirm_delete_account_path) + expect(User.where(id: user.id)).to be_empty + deleted_user = DeletedUser.find_by(user_id: user.id) + expect(deleted_user.user_id).to eq(user.id) + expect(deleted_user.uuid).to eq(user.uuid) + expect(last_email.subject).to eq t('user_mailer.account_reset_complete.subject') + + click_link t('account_reset.confirm_delete_account.link_text') + + expect(page).to have_current_path(sign_up_email_path) + + events = irs_attempts_api_tracked_events(timestamp: Time.zone.now) + expected_event_types = %w[account-reset-account-deleted] + received_event_types = events.map(&:event_type) + + expect(events.count).to eq received_event_types.count + expect(received_event_types).to match_array(expected_event_types) + end + end + end end diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb index 9496457689e..eadab798730 100644 --- a/spec/features/idv/analytics_spec.rb +++ b/spec/features/idv/analytics_spec.rb @@ -69,9 +69,6 @@ 'IdV: USPS address letter enqueued' => { enqueued_at: Time.zone.now.utc, resend: false, proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', address_check: 'gpo_letter' } }, 'IdV: review complete' => { success: true, proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', address_check: 'gpo_letter' }, fraud_review_pending: false, fraud_rejection: false, deactivation_reason: 'gpo_verification_pending' }, 'IdV: final resolution' => { success: true, proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', address_check: 'gpo_letter' }, fraud_review_pending: false, fraud_rejection: false, deactivation_reason: 'gpo_verification_pending' }, - 'IdV: personal key visited' => { address_verification_method: 'gpo', proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', address_check: 'gpo_letter' } }, - 'IdV: personal key acknowledgment toggled' => { checked: true, proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', address_check: 'gpo_letter' } }, - 'IdV: personal key submitted' => { address_verification_method: 'gpo', proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', address_check: 'gpo_letter' }, fraud_review_pending: false, fraud_rejection: false, deactivation_reason: 'gpo_verification_pending' }, 'IdV: come back later visited' => { proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', address_check: 'gpo_letter' } }, } end @@ -185,7 +182,6 @@ enter_gpo_flow gpo_step complete_review_step(user) - acknowledge_and_confirm_personal_key end it 'records all of the events' do diff --git a/spec/features/idv/clearing_and_restarting_spec.rb b/spec/features/idv/clearing_and_restarting_spec.rb index e0ea19bdbc0..6de48def5ba 100644 --- a/spec/features/idv/clearing_and_restarting_spec.rb +++ b/spec/features/idv/clearing_and_restarting_spec.rb @@ -9,7 +9,6 @@ before do start_idv_from_sp complete_idv_steps_with_gpo_before_confirmation_step(user) - acknowledge_and_confirm_personal_key unless IdentityConfig.store.gpo_personal_key_after_otp end context 'before signing out' do diff --git a/spec/features/idv/doc_auth/address_step_spec.rb b/spec/features/idv/doc_auth/address_step_spec.rb index fffcb328841..b55352e51b2 100644 --- a/spec/features/idv/doc_auth/address_step_spec.rb +++ b/spec/features/idv/doc_auth/address_step_spec.rb @@ -48,12 +48,16 @@ complete_doc_auth_steps_before_document_capture_step complete_document_capture_step_with_yml('spec/fixtures/puerto_rico_resident.yml') complete_ssn_step - click_button t('idv.buttons.change_address_label') end it 'shows address guidance and hint text' do + expect(page).to have_current_path(idv_address_url) expect(page.body).to include(t('doc_auth.info.address_guidance_puerto_rico_html')) expect(page).to have_content(t('forms.example')) + fill_in 'idv_form_address1', with: '123 Calle Carlos' + fill_in 'idv_form_address2', with: 'URB Las Gladiolas' + click_button t('forms.buttons.submit.update') + expect(page).to have_current_path(idv_verify_info_path) end end end diff --git a/spec/features/idv/in_person_spec.rb b/spec/features/idv/in_person_spec.rb index 40e8e4e86aa..4b5af8607eb 100644 --- a/spec/features/idv/in_person_spec.rb +++ b/spec/features/idv/in_person_spec.rb @@ -8,6 +8,8 @@ before do allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true) + allow(IdentityConfig.store).to receive(:in_person_capture_secondary_id_enabled). + and_return(false) end context 'ThreatMetrix review pending' do @@ -45,7 +47,7 @@ expect(page).to have_content(t('headings.verify')) expect(page).to have_text(InPersonHelper::GOOD_FIRST_NAME) expect(page).to have_text(InPersonHelper::GOOD_LAST_NAME) - expect(page).to have_text(InPersonHelper::GOOD_DOB) + expect(page).to have_text(InPersonHelper::GOOD_DOB_FORMATTED_EVENT) expect(page).to have_text(InPersonHelper::GOOD_STATE_ID_NUMBER) expect(page).to have_text(InPersonHelper::GOOD_ADDRESS1) expect(page).to have_text(InPersonHelper::GOOD_CITY) @@ -132,8 +134,10 @@ complete_prepare_step(user) # state ID page - expect_in_person_step_indicator_current_step(t('step_indicator.flows.idv.verify_info')) - expect(page).to have_content(t('in_person_proofing.headings.state_id')) + expect_in_person_step_indicator_current_step( + t('step_indicator.flows.idv.verify_info'), + ) + expect(page).to have_content(t('in_person_proofing.headings.state_id_milestone_2')) complete_state_id_step(user) # address page @@ -152,7 +156,7 @@ expect(page).to have_content(t('headings.verify')) expect(page).to have_text(InPersonHelper::GOOD_FIRST_NAME) expect(page).to have_text(InPersonHelper::GOOD_LAST_NAME) - expect(page).to have_text(InPersonHelper::GOOD_DOB) + expect(page).to have_text(InPersonHelper::GOOD_DOB_FORMATTED_EVENT) expect(page).to have_text(InPersonHelper::GOOD_STATE_ID_NUMBER) expect(page).to have_text(InPersonHelper::GOOD_ADDRESS1) expect(page).to have_text(InPersonHelper::GOOD_CITY) @@ -169,6 +173,7 @@ # click update address button click_button t('idv.buttons.change_address_label') expect(page).to have_content(t('in_person_proofing.headings.update_address')) + choose t('in_person_proofing.form.address.same_address_choice_yes') click_button t('forms.buttons.submit.update') expect(page).to have_content(t('headings.verify')) @@ -375,8 +380,6 @@ click_on t('idv.buttons.mail.send') expect_in_person_gpo_step_indicator_current_step(t('step_indicator.flows.idv.secure_account')) complete_review_step - expect_in_person_gpo_step_indicator_current_step(t('step_indicator.flows.idv.secure_account')) - acknowledge_and_confirm_personal_key unless IdentityConfig.store.gpo_personal_key_after_otp 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')) @@ -403,7 +406,6 @@ click_on t('idv.troubleshooting.options.verify_by_mail') click_on t('idv.buttons.mail.send') complete_review_step - acknowledge_and_confirm_personal_key unless IdentityConfig.store.gpo_personal_key_after_otp click_idv_continue click_on t('account.index.verification.reactivate_button') click_on t('idv.messages.clear_and_start_over') @@ -483,4 +485,28 @@ expect(page).to have_current_path(idv_in_person_step_path(step: :ssn), wait: 10) end end + + context 'validate_id_and_residential_addresses feature flag enabled', allow_browser_log: true do + let(:user) { user_with_2fa } + + before do + allow(IdentityConfig.store).to receive(:in_person_capture_secondary_id_enabled). + and_return(true) + end + + it 'captures the address, address line 2, city, state and zip code' do + sign_in_and_2fa_user(user) + begin_in_person_proofing(user) + search_for_post_office + + # location page + location = page.find_all('.location-collection-item')[1] + location.click_button(t('in_person_proofing.body.location.location_button')) + + # prepare page + complete_prepare_step(user) + + complete_state_id_step(user, same_address_as_id: false, include_address: true) + end + end end diff --git a/spec/features/idv/steps/confirmation_step_spec.rb b/spec/features/idv/steps/confirmation_step_spec.rb index 0052d2d5733..b1da498fe6f 100644 --- a/spec/features/idv/steps/confirmation_step_spec.rb +++ b/spec/features/idv/steps/confirmation_step_spec.rb @@ -31,8 +31,7 @@ let(:address_verification_mechanism) { :gpo } it 'shows status content for gpo verification progress' do - expect(page).to have_content(t('idv.messages.mail_sent')) - expect_step_indicator_current_step(t('step_indicator.flows.idv.secure_account')) + 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 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 9d2f85f1576..13b96d0e147 100644 --- a/spec/features/idv/steps/gpo_otp_verification_step_spec.rb +++ b/spec/features/idv/steps/gpo_otp_verification_step_spec.rb @@ -74,8 +74,6 @@ context 'with gpo personal key after verification' do it 'shows the user a personal key after verification' do - allow(IdentityConfig.store).to receive(:gpo_personal_key_after_otp). - and_return(true) sign_in_live_with_2fa(user) expect(current_path).to eq idv_gpo_verify_path diff --git a/spec/features/idv/steps/gpo_step_spec.rb b/spec/features/idv/steps/gpo_step_spec.rb index 78c54d4b6fd..1c3c254ae6c 100644 --- a/spec/features/idv/steps/gpo_step_spec.rb +++ b/spec/features/idv/steps/gpo_step_spec.rb @@ -50,7 +50,6 @@ def complete_idv_and_return_to_gpo_step click_on t('idv.buttons.mail.send') fill_in 'Password', with: user_password click_continue - acknowledge_and_confirm_personal_key unless IdentityConfig.store.gpo_personal_key_after_otp visit root_path click_on t('idv.buttons.cancel') first(:link, t('links.sign_out')).click @@ -103,12 +102,6 @@ def expect_user_to_be_unverified(user) click_on(t('idv.buttons.mail.send')) fill_in 'Password', with: new_password click_continue - page.find( - 'label', - text: t('forms.personal_key.required_checkbox'), - wait: 5, - ).click - click_continue set_new_browser_session visit_idp_from_ial2_oidc_sp signin(user.email, new_password) diff --git a/spec/features/idv/steps/in_person/verify_info_spec.rb b/spec/features/idv/steps/in_person/verify_info_spec.rb index 6f408fb9760..7aacb6188f1 100644 --- a/spec/features/idv/steps/in_person/verify_info_spec.rb +++ b/spec/features/idv/steps/in_person/verify_info_spec.rb @@ -28,11 +28,7 @@ expect(page).to have_content(t('headings.verify')) expect(page).to have_text(InPersonHelper::GOOD_FIRST_NAME) expect(page).to have_text(InPersonHelper::GOOD_LAST_NAME) - i18n_dob = I18n.l( - Date.parse(InPersonHelper::GOOD_DOB), - format: I18n.t('time.formats.event_date'), - ) - expect(page).to have_text(i18n_dob) + expect(page).to have_text(InPersonHelper::GOOD_DOB_FORMATTED_EVENT) expect(page).to have_text(InPersonHelper::GOOD_STATE_ID_NUMBER) expect(page).to have_text(InPersonHelper::GOOD_ADDRESS1) expect(page).to have_text(InPersonHelper::GOOD_CITY) diff --git a/spec/features/idv/steps/in_person/verify_step_spec.rb b/spec/features/idv/steps/in_person/verify_step_spec.rb index e9100dec707..a2a5d0342a0 100644 --- a/spec/features/idv/steps/in_person/verify_step_spec.rb +++ b/spec/features/idv/steps/in_person/verify_step_spec.rb @@ -25,7 +25,7 @@ expect(page).to have_content(t('headings.verify')) expect(page).to have_text(InPersonHelper::GOOD_FIRST_NAME) expect(page).to have_text(InPersonHelper::GOOD_LAST_NAME) - expect(page).to have_text(InPersonHelper::GOOD_DOB) + expect(page).to have_text(InPersonHelper::GOOD_DOB_FORMATTED_EVENT) expect(page).to have_text(InPersonHelper::GOOD_STATE_ID_NUMBER) expect(page).to have_text(InPersonHelper::GOOD_ADDRESS1) expect(page).to have_text(InPersonHelper::GOOD_CITY) diff --git a/spec/features/idv/steps/review_step_spec.rb b/spec/features/idv/steps/review_step_spec.rb index 75cd6674a9f..0a5f9b5eacb 100644 --- a/spec/features/idv/steps/review_step_spec.rb +++ b/spec/features/idv/steps/review_step_spec.rb @@ -82,7 +82,6 @@ end it 'sends you to the come_back_later page after review step' do - allow(IdentityConfig.store).to receive(:gpo_personal_key_after_otp).and_return(true) fill_in 'Password', with: user_password click_continue diff --git a/spec/features/saml/ial2_sso_spec.rb b/spec/features/saml/ial2_sso_spec.rb index 608bca8c411..d90e358698a 100644 --- a/spec/features/saml/ial2_sso_spec.rb +++ b/spec/features/saml/ial2_sso_spec.rb @@ -29,7 +29,6 @@ def perform_id_verification_with_gpo_without_confirming_code(user) click_on t('idv.buttons.mail.send') fill_in t('idv.form.password'), with: user.password click_continue - acknowledge_and_confirm_personal_key unless IdentityConfig.store.gpo_personal_key_after_otp click_link t('idv.cancel.actions.exit', app_name: APP_NAME) end @@ -44,7 +43,6 @@ def update_mailing_address click_on t('idv.buttons.mail.resend') fill_in t('idv.form.password'), with: user.password click_continue - acknowledge_and_confirm_personal_key unless IdentityConfig.store.gpo_personal_key_after_otp click_link t('idv.cancel.actions.exit', app_name: APP_NAME) end diff --git a/spec/features/users/verify_profile_spec.rb b/spec/features/users/verify_profile_spec.rb index 263093cbd09..047eb20e7c3 100644 --- a/spec/features/users/verify_profile_spec.rb +++ b/spec/features/users/verify_profile_spec.rb @@ -28,8 +28,8 @@ sign_in_live_with_2fa(user) fill_in t('forms.verify_profile.name'), with: otp click_button t('forms.verify_profile.submit') + acknowledge_and_confirm_personal_key - expect(page).to have_content(t('account.index.verification.success')) expect(page).to have_current_path(account_path) end diff --git a/spec/jobs/fraud_rejection_daily_job_spec.rb b/spec/jobs/fraud_rejection_daily_job_spec.rb index 9a1022acef3..cd683751bcc 100644 --- a/spec/jobs/fraud_rejection_daily_job_spec.rb +++ b/spec/jobs/fraud_rejection_daily_job_spec.rb @@ -18,6 +18,7 @@ expect { job.perform(Time.zone.today) }.to change { rejected_profiles.count }.by(1) expect(job_analytics).to have_logged_event( 'Fraud: Automatic Fraud Rejection', + rejection_date: Time.zone.today, verified_at: rejected_profiles.first.verified_at, ) end diff --git a/spec/jobs/get_usps_proofing_results_job_spec.rb b/spec/jobs/get_usps_proofing_results_job_spec.rb index bd17b08c838..1ffce6a2238 100644 --- a/spec/jobs/get_usps_proofing_results_job_spec.rb +++ b/spec/jobs/get_usps_proofing_results_job_spec.rb @@ -146,13 +146,13 @@ let(:job) { GetUspsProofingResultsJob.new } let(:job_analytics) { FakeAnalytics.new } let(:transaction_start_date_time) do - ActiveSupport::TimeZone['Central Time (US & Canada)'].strptime( + ActiveSupport::TimeZone[-6].strptime( '12/17/2020 033855', '%m/%d/%Y %H%M%S', ).in_time_zone('UTC') end let(:transaction_end_date_time) do - ActiveSupport::TimeZone['Central Time (US & Canada)'].strptime( + ActiveSupport::TimeZone[-6].strptime( '12/17/2020 034055', '%m/%d/%Y %H%M%S', ).in_time_zone('UTC') @@ -414,7 +414,7 @@ context 'a custom delay greater than zero is set' do let(:user) { pending_enrollment.user } let(:proofed_at_string) do - proofed_at = ActiveSupport::TimeZone['Central Time (US & Canada)'].now - 1.hour + proofed_at = ActiveSupport::TimeZone[-6].now proofed_at.strftime('%m/%d/%Y %H%M%S') end @@ -424,11 +424,12 @@ end it 'uses the custom delay when proofing passes' do - stub_request_passed_proofing_results(transactionEndDateTime: proofed_at_string) wait_until = nil freeze_time do - wait_until = Time.zone.now + 4.hours + stub_request_passed_proofing_results(transactionEndDateTime: proofed_at_string) + wait_until = Time.zone.now + + IdentityConfig.store.in_person_results_delay_in_hours.hours expect do job.perform(Time.zone.now) end.to have_enqueued_mail(UserMailer, :in_person_verified).with( @@ -448,11 +449,12 @@ end it 'uses the custom delay when proofing fails' do - stub_request_failed_proofing_results(transactionEndDateTime: proofed_at_string) wait_until = nil freeze_time do - wait_until = Time.zone.now + 4.hours + stub_request_failed_proofing_results(transactionEndDateTime: proofed_at_string) + wait_until = Time.zone.now + + IdentityConfig.store.in_person_results_delay_in_hours.hours expect do job.perform(Time.zone.now) end.to have_enqueued_mail(UserMailer, :in_person_failed).with( diff --git a/spec/jobs/heartbeat_job_spec.rb b/spec/jobs/heartbeat_job_spec.rb index 8838f4065cc..113531fa617 100644 --- a/spec/jobs/heartbeat_job_spec.rb +++ b/spec/jobs/heartbeat_job_spec.rb @@ -13,10 +13,6 @@ msg = JSON.parse(str, symbolize_names: true) expect(msg).to eq( name: 'queue_metric.good_job', - num_finished: 0, - num_unfinished: 0, - num_running: 0, - num_errors: 0, ) end diff --git a/spec/jobs/psql_stats_job_spec.rb b/spec/jobs/psql_stats_job_spec.rb deleted file mode 100644 index e77848ba73d..00000000000 --- a/spec/jobs/psql_stats_job_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'rails_helper' - -RSpec.describe PsqlStatsJob, type: :job do - describe '#perform' do - it 'returns true' do - result = PsqlStatsJob.new.perform(Time.zone.now) - - expect(result).to eq true - end - - it 'logs psql table bloat metrics' do - expect(IdentityJobLogSubscriber.reports_logger).to receive(:info) do |str| - msg = JSON.parse(str, symbolize_names: true) - expect(msg[:name]).to eq('psql_bloat_statistics') - expect(msg[:table_data][:users][:tblname]).to eq('users') - end - - PsqlStatsJob.new.perform(Time.zone.now) - end - end -end diff --git a/spec/jobs/reports/duplicate_ssn_report_spec.rb b/spec/jobs/reports/duplicate_ssn_report_spec.rb new file mode 100644 index 00000000000..afe816a4442 --- /dev/null +++ b/spec/jobs/reports/duplicate_ssn_report_spec.rb @@ -0,0 +1,102 @@ +require 'rails_helper' +require 'csv' + +RSpec.describe Reports::DuplicateSsnReport do + let(:report_date) { Date.new(2022, 2, 2) } + + describe '#perform' do + subject(:report) { described_class.new } + + it 'runs the report and uploads to S3' do + expect(report).to receive(:save_report) + + report.perform(report_date) + end + end + + describe '#report_body' do + subject(:report_body) { described_class.new(report_date).report_body } + + context 'with no data' do + it 'is an empty report' do + csv = CSV.parse(report_body, headers: true) + + expect(csv).to be_empty + end + end + + context 'with data' do + let(:ssn_fingerprint1) { 'aaa' } + let(:ssn_fingerprint2) { 'bbb' } + + let!(:unique_profile) do + create( + :profile, + :active, + ssn_signature: ssn_fingerprint1, + activated_at: report_date, + ) + end + + let!(:fingerprint2_today_profile) do + create( + :profile, + :active, + ssn_signature: ssn_fingerprint2, + activated_at: report_date, + ).tap(&:reload) + end + + let!(:fingerprint2_previous_profiles) do + [ + create( + :profile, + active: false, + ssn_signature: ssn_fingerprint2, + activated_at: report_date - 10.days, + ), + create( + :profile, + active: false, + ssn_signature: ssn_fingerprint2, + activated_at: nil, + ), + ].map(&:reload) + end + + it 'creates csv with corresponding data', aggregate_failures: true do + csv = CSV.parse(report_body, headers: true) + expect(csv.length).to eq(3) + + expect(csv.find { |r| r['uuid'] == unique_profile.user.uuid }). + to be_nil, 'does not include unique users in the report' + + today_user = fingerprint2_today_profile.user + today_row = csv.find { |r| r['uuid'] == today_user.uuid } + + expect_row_matches_profile(row: today_row, profile: fingerprint2_today_profile) + expect(today_row['new_account']).to eq('true') + + fingerprint2_previous_profiles.each do |profile| + row = csv.find { |r| r['uuid'] == profile.user.uuid } + + expect_row_matches_profile(row:, profile:) + expect(row['new_account']).to eq('false') + end + end + + def expect_row_matches_profile(row:, profile:) + expect(row).to be + expect(row['uuid']).to eq(profile.user.uuid) + expect(Time.zone.parse(row['account_created_at']).to_i).to eq(profile.user.created_at.to_i) + if profile.activated_at + expect(Time.zone.parse(row['identity_verified_at']).to_i).to eq(profile.activated_at.to_i) + end + expect(row['profile_active']).to eq(profile.active.to_s) + expect(row['ssn_fingerprint']).to eq(ssn_fingerprint2) + expect(row['count_ssn_fingerprint']).to eq('3') + expect(row['count_active_ssn_fingerprint']).to eq('1') + end + end + end +end diff --git a/spec/jobs/risc_delivery_job_spec.rb b/spec/jobs/risc_delivery_job_spec.rb index 79d1a94ed31..530636e0217 100644 --- a/spec/jobs/risc_delivery_job_spec.rb +++ b/spec/jobs/risc_delivery_job_spec.rb @@ -2,9 +2,9 @@ RSpec.describe RiscDeliveryJob do around do |ex| - REDIS_THROTTLE_POOL.with { |namespaced| namespaced.redis.flushdb } + REDIS_THROTTLE_POOL.with { |client| client.flushdb } ex.run - REDIS_THROTTLE_POOL.with { |namespaced| namespaced.redis.flushdb } + REDIS_THROTTLE_POOL.with { |client| client.flushdb } end describe '#perform' do diff --git a/spec/presenters/fully_signed_in_modal_presenter_spec.rb b/spec/presenters/fully_signed_in_modal_presenter_spec.rb deleted file mode 100644 index 12f229edb56..00000000000 --- a/spec/presenters/fully_signed_in_modal_presenter_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'rails_helper' - -describe FullySignedInModalPresenter do - include ActionView::Helpers::SanitizeHelper - - let(:expiration) { Time.zone.now + 1.minute + 1.second } - let(:lookup_context) { ActionView::LookupContext.new(ActionController::Base.view_paths) } - let(:view_context) { ActionView::Base.new(lookup_context, {}, nil) } - subject(:presenter) do - FullySignedInModalPresenter.new(view_context: view_context, expiration: expiration) - end - - around do |ex| - freeze_time { ex.run } - end - - describe '#message' do - it 'returns the fully signed in message' do - expect(strip_tags(presenter.message)).to eq t( - 'notices.timeout_warning.signed_in.message_html', - time_left_in_session: '1 minute and 1 second', - ) - end - end - - describe '#sr_message' do - it 'returns the fully signed in message for screen readers' do - expect(strip_tags(presenter.sr_message)).to eq t( - 'notices.timeout_warning.signed_in.sr_message_html', - time_left_in_session: '1 minute and 1 second', - ) - end - end - - describe '#continue' do - it 'uses the fully signed in localization' do - expect(presenter.continue).to eq t('notices.timeout_warning.signed_in.continue') - end - end - - describe '#sign_out' do - it 'uses the fully signed in localization' do - expect(presenter.sign_out).to eq t('notices.timeout_warning.signed_in.sign_out') - end - end -end diff --git a/spec/presenters/partially_signed_in_modal_presenter_spec.rb b/spec/presenters/partially_signed_in_modal_presenter_spec.rb deleted file mode 100644 index 8949e0fa53f..00000000000 --- a/spec/presenters/partially_signed_in_modal_presenter_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'rails_helper' - -describe PartiallySignedInModalPresenter do - include ActionView::Helpers::SanitizeHelper - - let(:expiration) { Time.zone.now + 1.minute + 1.second } - let(:lookup_context) { ActionView::LookupContext.new(ActionController::Base.view_paths) } - let(:view_context) { ActionView::Base.new(lookup_context, {}, nil) } - subject(:presenter) do - PartiallySignedInModalPresenter.new(view_context: view_context, expiration: expiration) - end - - around do |ex| - freeze_time { ex.run } - end - - describe '#message' do - it 'returns the partially signed in message' do - expect(strip_tags(presenter.message)).to eq t( - 'notices.timeout_warning.partially_signed_in.message_html', - time_left_in_session: '1 minute and 1 second', - ) - end - end - - describe '#sr_message' do - it 'returns the partially signed in message for screen readers' do - expect(strip_tags(presenter.sr_message)).to eq t( - 'notices.timeout_warning.partially_signed_in.sr_message_html', - time_left_in_session: '1 minute and 1 second', - ) - end - end - - describe '#continue' do - it 'uses the partially signed in localization' do - expect(presenter.continue).to eq t('notices.timeout_warning.partially_signed_in.continue') - end - end - - describe '#sign_out' do - it 'uses the partially signed in localization' do - expect(presenter.sign_out).to eq t('notices.timeout_warning.partially_signed_in.sign_out') - end - end -end diff --git a/spec/presenters/session_timeout_modal_presenter_spec.rb b/spec/presenters/session_timeout_modal_presenter_spec.rb new file mode 100644 index 00000000000..0025600fc1a --- /dev/null +++ b/spec/presenters/session_timeout_modal_presenter_spec.rb @@ -0,0 +1,27 @@ +require 'rails_helper' + +describe SessionTimeoutModalPresenter do + let(:user_fully_authenticated) { nil } + + subject(:presenter) { described_class.new(user_fully_authenticated:) } + + describe '#translation_scope' do + subject(:translation_scope) { presenter.translation_scope } + + context 'without fully authenticated user' do + let(:user_fully_authenticated) { false } + + it 'returns the partially signed in locale scope' do + expect(translation_scope).to eq([:notices, :timeout_warning, :partially_signed_in]) + end + end + + context 'with fully authenticated user' do + let(:user_fully_authenticated) { true } + + it 'returns the fully signed in locale scope' do + expect(translation_scope).to eq([:notices, :timeout_warning, :signed_in]) + end + end + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 3fe68c58dda..912de11754c 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -110,7 +110,7 @@ class Analytics Telephony::Test::Message.clear_messages Telephony::Test::Call.clear_calls PushNotification::LocalEventQueue.clear! - REDIS_THROTTLE_POOL.with { |namespaced| namespaced.redis.flushdb } + REDIS_THROTTLE_POOL.with { |client| client.flushdb } end config.before(:each) do diff --git a/spec/requests/rack_attack_spec.rb b/spec/requests/rack_attack_spec.rb index c186f48f0fe..a9aba39ce8e 100644 --- a/spec/requests/rack_attack_spec.rb +++ b/spec/requests/rack_attack_spec.rb @@ -394,7 +394,7 @@ context 'when the number of requests is under the limit' do it 'does not throttle the request' do (phone_setups_per_ip_limit - 1).times do - patch '/phone_setup', headers: { REMOTE_ADDR: '1.2.3.4' } + post '/phone_setup', headers: { REMOTE_ADDR: '1.2.3.4' } end expect(response.status).to eq(302) @@ -404,7 +404,7 @@ context 'when the number of requests is over the limit' do it 'throttles the request' do (phone_setups_per_ip_limit + 1).times do - patch '/phone_setup', headers: { REMOTE_ADDR: '1.2.3.4' } + post '/phone_setup', headers: { REMOTE_ADDR: '1.2.3.4' } end expect(response.status).to eq(429) diff --git a/spec/services/account_reset/create_request_spec.rb b/spec/services/account_reset/create_request_spec.rb index b01872f278f..b8f1436c0b1 100644 --- a/spec/services/account_reset/create_request_spec.rb +++ b/spec/services/account_reset/create_request_spec.rb @@ -1,7 +1,8 @@ require 'rails_helper' RSpec.describe AccountReset::CreateRequest do - subject(:create_request) { described_class.new(user) } + subject(:requesting_issuer) { 'example-issuer' } + subject(:create_request) { described_class.new(user, requesting_issuer) } describe '#call' do context 'when the user does not have a phone' do @@ -23,5 +24,16 @@ expect(response.to_h[:message_id]).to be_present end end + + context 'when requesting_issuer is passed' do + let(:user) { build(:user) } + + it 'it stores requesting_issuer' do + create_request.call + reset_request = AccountResetRequest.find_by(user_id: user.id) + + expect(reset_request.requesting_issuer).to eq requesting_issuer + end + end end end diff --git a/spec/services/account_reset/delete_account_spec.rb b/spec/services/account_reset/delete_account_spec.rb index f169e620689..75ed8e04f79 100644 --- a/spec/services/account_reset/delete_account_spec.rb +++ b/spec/services/account_reset/delete_account_spec.rb @@ -2,7 +2,25 @@ describe AccountReset::DeleteAccount do include AccountResetHelper + + let(:expired_token_message) do + t('errors.account_reset.granted_token_expired', app_name: APP_NAME) + end + let(:expired_token_error) { { token: [expired_token_message] } } let(:user) { create(:user) } + let(:request) { FakeRequest.new } + let(:analytics) { FakeAnalytics.new } + let(:fake_attempts_tracker) { IrsAttemptsApiTrackingHelper::FakeAttemptsTracker.new } + + let(:service_provider) do + create( + :service_provider, + active: true, + redirect_uris: ['http://localhost:7654/auth/result'], + ial: 2, + irs_attempts_api_enabled: true, + ) + end describe '#call' do it 'can be called even if DeletedUser exists' do @@ -10,7 +28,7 @@ grant_request(user) token = AccountResetRequest.where(user_id: user.id).first.granted_token DeletedUser.create_from_user(user) - AccountReset::DeleteAccount.new(token).call + AccountReset::DeleteAccount.new(token, request, analytics).call end context 'when user.confirmed_at is nil' do @@ -21,10 +39,47 @@ grant_request(user) token = AccountResetRequest.where(user_id: user.id).first.granted_token - expect { AccountReset::DeleteAccount.new(token).call }.to_not raise_error + expect do + AccountReset::DeleteAccount.new(token, request, analytics).call + end.to_not raise_error expect(User.find_by(id: user.id)).to be_nil end end + + context 'track irs event' do + before do + allow_any_instance_of(AccountReset::DeleteAccount).to receive( + :irs_attempts_api_tracker, + ).and_return(fake_attempts_tracker) + end + + it 'logs attempts api event with success true if the token is good' do + expect(fake_attempts_tracker).to receive(:account_reset_account_deleted).with( + success: true, + failure_reason: nil, + ) + + create_account_reset_request_for(user, service_provider.issuer) + grant_request(user) + token = AccountResetRequest.where(user_id: user.id).first.granted_token + AccountReset::DeleteAccount.new(token, request, analytics).call + end + + it 'logs attempts api event with failure reason if the token is expired' do + expect(fake_attempts_tracker).to receive(:account_reset_account_deleted).with( + success: false, + failure_reason: expired_token_error, + ) + + create_account_reset_request_for(user, service_provider.issuer) + grant_request(user) + + travel_to(Time.zone.now + 2.days) do + token = AccountResetRequest.first.granted_token + AccountReset::DeleteAccount.new(token, request, analytics).call + end + end + end end end diff --git a/spec/services/account_reset/validate_granted_token_spec.rb b/spec/services/account_reset/validate_granted_token_spec.rb new file mode 100644 index 00000000000..93b60bd773a --- /dev/null +++ b/spec/services/account_reset/validate_granted_token_spec.rb @@ -0,0 +1,49 @@ +require 'rails_helper' + +describe AccountReset::ValidateGrantedToken do + include AccountResetHelper + + let(:expired_token_message) do + t('errors.account_reset.granted_token_expired', app_name: APP_NAME) + end + let(:expired_token_error) { { token: [expired_token_message] } } + let(:user) { create(:user) } + let(:request) { FakeRequest.new } + let(:analytics) { FakeAnalytics.new } + let(:fake_attempts_tracker) { IrsAttemptsApiTrackingHelper::FakeAttemptsTracker.new } + + let(:service_provider) do + create( + :service_provider, + active: true, + redirect_uris: ['http://localhost:7654/auth/result'], + ial: 2, + irs_attempts_api_enabled: true, + ) + end + + describe '#call' do + context 'track irs event' do + before do + allow_any_instance_of(AccountReset::ValidateGrantedToken).to receive( + :irs_attempts_api_tracker, + ).and_return(fake_attempts_tracker) + end + + it 'logs attempts api event with failure reason if the token is expired' do + expect(fake_attempts_tracker).to receive(:account_reset_account_deleted).with( + success: false, + failure_reason: expired_token_error, + ) + + create_account_reset_request_for(user, service_provider.issuer) + grant_request(user) + + travel_to(Time.zone.now + 2.days) do + token = AccountResetRequest.first.granted_token + AccountReset::ValidateGrantedToken.new(token, request, analytics).call + end + end + end + end +end diff --git a/spec/services/redis_rate_limiter_spec.rb b/spec/services/redis_rate_limiter_spec.rb index 96a1c3034e8..dd3aa0d6a48 100644 --- a/spec/services/redis_rate_limiter_spec.rb +++ b/spec/services/redis_rate_limiter_spec.rb @@ -4,9 +4,9 @@ let(:now) { Time.zone.now } around do |ex| - REDIS_THROTTLE_POOL.with { |namespaced| namespaced.redis.flushdb } + REDIS_THROTTLE_POOL.with { |client| client.flushdb } ex.run - REDIS_THROTTLE_POOL.with { |namespaced| namespaced.redis.flushdb } + REDIS_THROTTLE_POOL.with { |client| client.flushdb } end let(:key) { 'some-unique-identifier' } diff --git a/spec/support/account_reset_helper.rb b/spec/support/account_reset_helper.rb index d82963627ce..54fb8e4364a 100644 --- a/spec/support/account_reset_helper.rb +++ b/spec/support/account_reset_helper.rb @@ -1,5 +1,5 @@ module AccountResetHelper - def create_account_reset_request_for(user) + def create_account_reset_request_for(user, requesting_issuer = nil) request = AccountResetRequest.create_or_find_by(user: user) request_token = SecureRandom.uuid request.update!( @@ -8,6 +8,7 @@ def create_account_reset_request_for(user) cancelled_at: nil, granted_at: nil, granted_token: nil, + requesting_issuer: requesting_issuer, ) request_token end diff --git a/spec/support/controller_helper.rb b/spec/support/controller_helper.rb index 61db17ce642..381f36df887 100644 --- a/spec/support/controller_helper.rb +++ b/spec/support/controller_helper.rb @@ -66,8 +66,7 @@ def stub_verify_steps_one_and_two(user) dob: 50.years.ago.to_date.to_s, ssn: '666-12-1234', }.with_indifferent_access - idv_session.profile_confirmation = true - idv_session.resolution_successful = 'phone' + 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) @@ -81,7 +80,7 @@ def stub_user_with_applicant_data(user, applicant) service_provider: nil ) idv_session.applicant = applicant.with_indifferent_access - idv_session.profile_confirmation = true + 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) diff --git a/spec/support/features/in_person_helper.rb b/spec/support/features/in_person_helper.rb index a75e5c9eb86..3bfa4a51281 100644 --- a/spec/support/features/in_person_helper.rb +++ b/spec/support/features/in_person_helper.rb @@ -7,7 +7,12 @@ module InPersonHelper GOOD_FIRST_NAME = Idp::Constants::MOCK_IDV_APPLICANT[:first_name] GOOD_LAST_NAME = Idp::Constants::MOCK_IDV_APPLICANT[:last_name] + # the date in the format '1938-10-06' GOOD_DOB = Idp::Constants::MOCK_IDV_APPLICANT[:dob] + # the date in the format 'October 6, 1938' + GOOD_DOB_FORMATTED_EVENT = I18n.l( + Date.parse(GOOD_DOB), format: I18n.t('time.formats.event_date') + ) GOOD_STATE_ID_JURISDICTION = Idp::Constants::MOCK_IDV_APPLICANT_FULL_STATE_ID_JURISDICTION GOOD_STATE_ID_NUMBER = Idp::Constants::MOCK_IDV_APPLICANT[:state_id_number] @@ -17,7 +22,7 @@ module InPersonHelper GOOD_ZIPCODE = Idp::Constants::MOCK_IDV_APPLICANT[:zipcode] GOOD_STATE = Idp::Constants::MOCK_IDV_APPLICANT_FULL_STATE - def fill_out_state_id_form_ok + def fill_out_state_id_form_ok(include_address: false) fill_in t('in_person_proofing.form.state_id.first_name'), with: GOOD_FIRST_NAME fill_in t('in_person_proofing.form.state_id.last_name'), with: GOOD_LAST_NAME year, month, day = GOOD_DOB.split('-') @@ -27,6 +32,14 @@ def fill_out_state_id_form_ok select GOOD_STATE_ID_JURISDICTION, from: t('in_person_proofing.form.state_id.state_id_jurisdiction') fill_in t('in_person_proofing.form.state_id.state_id_number'), with: GOOD_STATE_ID_NUMBER + + if include_address + fill_in t('in_person_proofing.form.state_id.address1'), with: GOOD_ADDRESS1 + fill_in t('in_person_proofing.form.state_id.address2'), with: GOOD_ADDRESS2 + fill_in t('in_person_proofing.form.state_id.city'), with: GOOD_CITY + fill_in t('in_person_proofing.form.state_id.zipcode'), with: GOOD_ZIPCODE + choose t('in_person_proofing.form.state_id.same_address_as_id_no') + end end def fill_out_address_form_ok @@ -67,11 +80,15 @@ def complete_prepare_step(_user = nil) click_link t('forms.buttons.continue') end - def complete_state_id_step(_user = nil) + def complete_state_id_step(_user = nil, same_address_as_id: true, include_address: false) # Wait for page to load before attempting to fill out form expect(page).to have_current_path(idv_in_person_step_path(step: :state_id), wait: 10) - fill_out_state_id_form_ok + include_address ? fill_out_state_id_form_ok(include_address: true) : fill_out_state_id_form_ok click_idv_continue + unless include_address && same_address_as_id + expect(page).to have_current_path(idv_in_person_step_path(step: :address), wait: 10) + expect_in_person_step_indicator_current_step(t('step_indicator.flows.idv.verify_info')) + end end def complete_address_step(_user = nil) diff --git a/spec/support/idv_examples/clearing_and_restarting.rb b/spec/support/idv_examples/clearing_and_restarting.rb index 8da95bfe913..be194690714 100644 --- a/spec/support/idv_examples/clearing_and_restarting.rb +++ b/spec/support/idv_examples/clearing_and_restarting.rb @@ -27,7 +27,6 @@ end fill_in 'Password', with: user.password click_idv_continue - acknowledge_and_confirm_personal_key unless IdentityConfig.store.gpo_personal_key_after_otp gpo_confirmation = GpoConfirmation.order(created_at: :desc).first diff --git a/yarn.lock b/yarn.lock index c97bd69e82e..1ca4dee6923 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1419,7 +1419,7 @@ "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== "@types/mime@*": version "3.0.1" @@ -1991,6 +1991,14 @@ aria-query@^5.0.0: resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.2.tgz#0b8a744295271861e1d933f8feca13f9b70cfdc1" integrity sha512-eigU3vhqSO+Z8BKDnVLN/ompjhf3pYzecKXz8+whRy+9gZu8n1TCGfwzQUUPnqdHl9ax1Hr9031orZ+UOEYr7Q== +array-buffer-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" + integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== + dependencies: + call-bind "^1.0.2" + is-array-buffer "^3.0.1" + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -2001,15 +2009,15 @@ array-flatten@^2.1.2: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== -array-includes@^3.1.4, array-includes@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb" - integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ== +array-includes@^3.1.5, array-includes@^3.1.6: + version "3.1.6" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" + integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.19.5" - get-intrinsic "^1.1.1" + es-abstract "^1.20.4" + get-intrinsic "^1.1.3" is-string "^1.0.7" array-union@^2.1.0: @@ -2017,23 +2025,24 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array.prototype.flat@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13" - integrity sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg== +array.prototype.flat@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2" + integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" + define-properties "^1.1.4" + es-abstract "^1.20.4" + es-shim-unscopables "^1.0.0" -array.prototype.flatmap@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz#a7e8ed4225f4788a70cd910abcf0791e76a5534f" - integrity sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg== +array.prototype.flatmap@^1.3.0, array.prototype.flatmap@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" + integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" es-shim-unscopables "^1.0.0" arrify@^1.0.1: @@ -2515,7 +2524,7 @@ compression@^1.7.4: concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== confusing-browser-globals@^1.0.10: version "1.0.10" @@ -2679,7 +2688,7 @@ data-urls@^3.0.2: whatwg-mimetype "^3.0.0" whatwg-url "^11.0.0" -debug@2.6.9, debug@^2.6.9: +debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -2760,9 +2769,9 @@ define-lazy-prop@^2.0.0: integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== define-properties@^1.1.3, define-properties@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" - integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== + version "1.2.0" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" + integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== dependencies: has-property-descriptors "^1.0.0" object-keys "^1.1.1" @@ -2952,41 +2961,60 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5: - version "1.20.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.3.tgz#90b143ff7aedc8b3d189bcfac7f1e3e3f81e9da1" - integrity sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw== +es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.5, es-abstract@^1.20.4: + version "1.21.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.2.tgz#a56b9695322c8a185dc25975aa3b8ec31d0e7eff" + integrity sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg== dependencies: + array-buffer-byte-length "^1.0.0" + available-typed-arrays "^1.0.5" call-bind "^1.0.2" + es-set-tostringtag "^2.0.1" es-to-primitive "^1.2.1" - function-bind "^1.1.1" function.prototype.name "^1.1.5" - get-intrinsic "^1.1.3" + get-intrinsic "^1.2.0" get-symbol-description "^1.0.0" + globalthis "^1.0.3" + gopd "^1.0.1" has "^1.0.3" has-property-descriptors "^1.0.0" + has-proto "^1.0.1" has-symbols "^1.0.3" - internal-slot "^1.0.3" - is-callable "^1.2.6" + internal-slot "^1.0.5" + is-array-buffer "^3.0.2" + is-callable "^1.2.7" is-negative-zero "^2.0.2" is-regex "^1.1.4" is-shared-array-buffer "^1.0.2" is-string "^1.0.7" + is-typed-array "^1.1.10" is-weakref "^1.0.2" - object-inspect "^1.12.2" + object-inspect "^1.12.3" object-keys "^1.1.1" object.assign "^4.1.4" regexp.prototype.flags "^1.4.3" safe-regex-test "^1.0.0" - string.prototype.trimend "^1.0.5" - string.prototype.trimstart "^1.0.5" + string.prototype.trim "^1.2.7" + string.prototype.trimend "^1.0.6" + string.prototype.trimstart "^1.0.6" + typed-array-length "^1.0.4" unbox-primitive "^1.0.2" + which-typed-array "^1.1.9" es-module-lexer@^0.9.0: version "0.9.3" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== +es-set-tostringtag@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" + integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== + dependencies: + get-intrinsic "^1.1.3" + has "^1.0.3" + has-tostringtag "^1.0.0" + es-shim-unscopables@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" @@ -3054,38 +3082,41 @@ eslint-config-airbnb@^19.0.4: object.assign "^4.1.2" object.entries "^1.1.5" -eslint-import-resolver-node@^0.3.6: - version "0.3.6" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" - integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== +eslint-import-resolver-node@^0.3.7: + version "0.3.7" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7" + integrity sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA== dependencies: debug "^3.2.7" - resolve "^1.20.0" + is-core-module "^2.11.0" + resolve "^1.22.1" -eslint-module-utils@^2.7.3: +eslint-module-utils@^2.7.4: version "2.7.4" resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz#4f3e41116aaf13a20792261e61d3a2e7e0583974" integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== dependencies: debug "^3.2.7" -eslint-plugin-import@^2.26.0: - version "2.26.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b" - integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== +eslint-plugin-import@^2.27.5: + version "2.27.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz#876a6d03f52608a3e5bb439c2550588e51dd6c65" + integrity sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow== dependencies: - array-includes "^3.1.4" - array.prototype.flat "^1.2.5" - debug "^2.6.9" + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + array.prototype.flatmap "^1.3.1" + debug "^3.2.7" doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.3" + eslint-import-resolver-node "^0.3.7" + eslint-module-utils "^2.7.4" has "^1.0.3" - is-core-module "^2.8.1" + is-core-module "^2.11.0" is-glob "^4.0.3" minimatch "^3.1.2" - object.values "^1.1.5" - resolve "^1.22.0" + object.values "^1.1.6" + resolve "^1.22.1" + semver "^6.3.0" tsconfig-paths "^3.14.1" eslint-plugin-jsx-a11y@^6.6.1: @@ -3580,10 +3611,10 @@ get-func-name@^2.0.0: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" - integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" + integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== dependencies: function-bind "^1.1.1" has "^1.0.3" @@ -3673,6 +3704,13 @@ globals@^13.15.0: dependencies: type-fest "^0.20.2" +globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -3749,6 +3787,11 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.1.1" +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" @@ -4002,12 +4045,12 @@ inquirer@^8.2.0: through "^2.3.6" wrap-ansi "^7.0.0" -internal-slot@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" - integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== +internal-slot@^1.0.3, internal-slot@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" + integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== dependencies: - get-intrinsic "^1.1.0" + get-intrinsic "^1.2.0" has "^1.0.3" side-channel "^1.0.4" @@ -4039,6 +4082,15 @@ is-arguments@^1.0.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" + integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + is-typed-array "^1.1.10" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -4066,15 +4118,15 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.6: +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.8.1, is-core-module@^2.9.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" - integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== +is-core-module@^2.11.0, is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== dependencies: has "^1.0.3" @@ -4093,7 +4145,7 @@ is-docker@^2.0.0, is-docker@^2.1.1: is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^3.0.0: version "3.0.0" @@ -4130,9 +4182,9 @@ is-node-process@^1.0.1: integrity sha512-5IcdXuf++TTNt3oGl9EBdkvndXA8gmc4bz/Y+mdEpWh3Mcn/+kOw6hI7LD5CocqJWMzeb0I0ClndRVNdEPuJXQ== is-number-object@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" - integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== dependencies: has-tostringtag "^1.0.0" @@ -4207,7 +4259,7 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typed-array@^1.1.10, is-typed-array@^1.1.3: +is-typed-array@^1.1.10, is-typed-array@^1.1.3, is-typed-array@^1.1.9: version "1.1.10" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== @@ -4351,10 +4403,10 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== dependencies: minimist "^1.2.0" @@ -4784,7 +4836,7 @@ mq-polyfill@^1.1.8: ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== ms@2.1.2: version "2.1.2" @@ -4941,10 +4993,10 @@ object-assign@4.1.1, object-assign@^4.1.0, object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-inspect@^1.12.2, object-inspect@^1.9.0: - version "1.12.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" - integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== +object-inspect@^1.12.3, object-inspect@^1.9.0: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== object-keys@^1.1.1: version "1.1.1" @@ -4987,14 +5039,14 @@ object.hasown@^1.1.1: define-properties "^1.1.4" es-abstract "^1.19.5" -object.values@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" - integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== +object.values@^1.1.5, object.values@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d" + integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" + define-properties "^1.1.4" + es-abstract "^1.20.4" obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" @@ -5626,7 +5678,7 @@ resolve-id-refs@0.1.0: resolved "https://registry.yarnpkg.com/resolve-id-refs/-/resolve-id-refs-0.1.0.tgz#3126624b887489da8fc0ae889632f8413ac6c3ec" integrity sha1-MSZiS4h0idqPwK6IljL4QTrGw+w= -resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.9.0: +resolve@^1.10.0, resolve@^1.14.2, resolve@^1.22.1, resolve@^1.9.0: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== @@ -6107,23 +6159,32 @@ string.prototype.matchall@^4.0.7: regexp.prototype.flags "^1.4.1" side-channel "^1.0.4" -string.prototype.trimend@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" - integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== +string.prototype.trim@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533" + integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.19.5" + es-abstract "^1.20.4" -string.prototype.trimstart@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" - integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== +string.prototype.trimend@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" + integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.19.5" + es-abstract "^1.20.4" + +string.prototype.trimstart@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" + integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" string_decoder@^1.1.1: version "1.3.0" @@ -6149,7 +6210,7 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== strip-final-newline@^2.0.0: version "2.0.0" @@ -6426,12 +6487,12 @@ trim-newlines@^3.0.0: integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== tsconfig-paths@^3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" - integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== + version "3.14.2" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" + integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== dependencies: "@types/json5" "^0.0.29" - json5 "^1.0.1" + json5 "^1.0.2" minimist "^1.2.6" strip-bom "^3.0.0" @@ -6509,6 +6570,15 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typed-array-length@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" + integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + is-typed-array "^1.1.9" + typescript@^4.8.4: version "4.8.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" @@ -6785,10 +6855,10 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@^5.74.0: - version "5.74.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980" - integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA== +webpack@^5.76.1: + version "5.76.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.1.tgz#7773de017e988bccb0f13c7d75ec245f377d295c" + integrity sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^0.0.51" @@ -6873,7 +6943,7 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-typed-array@^1.1.2: +which-typed-array@^1.1.2, which-typed-array@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==