From e9bc35b9abff0577278023dc3839d78eaa6e8e4a Mon Sep 17 00:00:00 2001 From: Alex Bradley Date: Mon, 13 Mar 2023 12:53:22 -0400 Subject: [PATCH 01/18] Improve analytics for FraudRejectionDailyJob (#7973) changelog: Internal, IdV Fraud, Improve analytics on FraudRejectiondailyJob Improves the analytics to show the day the profile was rejected to compare with the day the profile entered the fraud_review_pending state --- app/jobs/fraud_rejection_daily_job.rb | 9 ++++++++- app/services/analytics_events.rb | 6 ++++-- spec/jobs/fraud_rejection_daily_job_spec.rb | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) 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/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/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 From 50ddb2b66fffd61a9b48b442dca2bca2b4b9321d Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Mon, 13 Mar 2023 12:55:47 -0500 Subject: [PATCH 02/18] Fix test that is inconsistent across daylight saving time changes (#7977) * Fix test that is inconsistent across daylight saving time changes changelog: Internal, Testing, Fix test that is inconsistent across daylight saving time changes * refactor to work consistently across DST --- spec/jobs/get_usps_proofing_results_job_spec.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) 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( From 9036caff6904ef24e217ef56653ddb641fa1c545 Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Mon, 13 Mar 2023 13:56:33 -0500 Subject: [PATCH 03/18] Remove redis-namespace gem (#7971) changelog: Internal, Redis, Remove redis-namespace gem --- Gemfile | 1 - Gemfile.lock | 3 --- app/services/irs_attempts_api/redis_client.rb | 7 ++----- app/services/redis_rate_limiter.rb | 2 +- app/services/throttle.rb | 4 ++-- config/initializers/redis.rb | 5 +---- spec/jobs/risc_delivery_job_spec.rb | 4 ++-- spec/rails_helper.rb | 2 +- spec/services/redis_rate_limiter_spec.rb | 4 ++-- 9 files changed, 11 insertions(+), 21 deletions(-) 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..54b1c1549e6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) @@ -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/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/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/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/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/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/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' } From 232ae7a90fc5be58ecf9280d71bd75562086d843 Mon Sep 17 00:00:00 2001 From: Sonia Connolly Date: Mon, 13 Mar 2023 14:32:43 -0700 Subject: [PATCH 04/18] LG-9019 Users in Puerto Rico go automatically to Update Address page (#7976) * After SSN step, take users with Puerto Rico addresses directly to Update Address page To help users in Puerto Rico verify their identities, automatically take them to the Update Address page which has guidance on how to edit a Puerto Rico address to be more likely to pass the Lexisnexis Instant Verify step. changelog: User-Facing Improvements, Identity Verification, take users with Puerto Rico addresses directly to the Update Address page after entering their SSN. --- app/controllers/idv/ssn_controller.rb | 10 +++++++++- spec/controllers/idv/ssn_controller_spec.rb | 8 ++++++++ spec/features/idv/doc_auth/address_step_spec.rb | 6 +++++- 3 files changed, 22 insertions(+), 2 deletions(-) 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/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/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 From 98d8bbec707ec0829c0bba66dc20c090e434ead8 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Tue, 14 Mar 2023 08:13:11 -0400 Subject: [PATCH 05/18] Remove unused phone PATCH route (#7947) * Remove unused phone PATCH route changelog: Internal, Code Quality, Reconcile routes for adding phone * Update spec method RackAttack throttles for any non-GET request, not specifically on the PATCH method: https://github.com/18F/identity-idp/blob/e10b263/config/initializers/rack_attack.rb#L202 --- config/routes.rb | 1 - spec/requests/rack_attack_spec.rb | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) 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/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) From 3f7e670a2cf0b2a20df3f559cdd0660e1a7082d9 Mon Sep 17 00:00:00 2001 From: Justin Grevich Date: Tue, 14 Mar 2023 09:09:44 -0700 Subject: [PATCH 06/18] Remove PSQL stats job (#7981) * Remove PSQL stats job changelog: Internal, Background Jobs, Remove PSQL stats job * remove unused one minute cron interval --------- Co-authored-by: Mitchell Henke --- app/jobs/psql_stats_job.rb | 75 ----------------------- config/initializers/job_configurations.rb | 7 --- spec/jobs/psql_stats_job_spec.rb | 21 ------- 3 files changed, 103 deletions(-) delete mode 100644 app/jobs/psql_stats_job.rb delete mode 100644 spec/jobs/psql_stats_job_spec.rb 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/config/initializers/job_configurations.rb b/config/initializers/job_configurations.rb index 3685875a79e..932ca006262 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', 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 From 2586a25269eaa367ad693e52559d0f07bc8869df Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Tue, 14 Mar 2023 15:21:02 -0500 Subject: [PATCH 07/18] Upgrade webpack (#7986) changelog: Internal, Security, Upgrade webpack --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4333a229151..8056e7fc06c 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" diff --git a/yarn.lock b/yarn.lock index c97bd69e82e..d526bdb79fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6785,10 +6785,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" From 8c45dcf68f697775b2d7743f2e87bd341c6d313b Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Tue, 14 Mar 2023 15:52:04 -0500 Subject: [PATCH 08/18] Remove querying from good_jobs table in heartbeat job (#7984) changelog: Internal, Background Jobs, Remove querying from good_jobs table in heartbeat job --- app/jobs/heartbeat_job.rb | 5 ----- spec/jobs/heartbeat_job_spec.rb | 4 ---- 2 files changed, 9 deletions(-) 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/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 From 7517dc92a2825eee810f14d1897000cbffb80d64 Mon Sep 17 00:00:00 2001 From: Tomas Apodaca Date: Tue, 14 Mar 2023 15:10:58 -0700 Subject: [PATCH 09/18] LG-8930: Show formatted date of birth on in-person proofing verify step (#7975) * show formatted date of birth on IPP verify step changelog: User-Facing Improvements, Verify your information for in person proofing, Show formatted date of birth --- app/views/idv/shared/_verify.html.erb | 4 +++- spec/features/idv/in_person_spec.rb | 4 ++-- spec/features/idv/steps/in_person/verify_info_spec.rb | 6 +----- spec/features/idv/steps/in_person/verify_step_spec.rb | 2 +- spec/support/features/in_person_helper.rb | 5 +++++ 5 files changed, 12 insertions(+), 9 deletions(-) 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/spec/features/idv/in_person_spec.rb b/spec/features/idv/in_person_spec.rb index 40e8e4e86aa..66850c5b5da 100644 --- a/spec/features/idv/in_person_spec.rb +++ b/spec/features/idv/in_person_spec.rb @@ -45,7 +45,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) @@ -152,7 +152,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/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/support/features/in_person_helper.rb b/spec/support/features/in_person_helper.rb index a75e5c9eb86..87509e690b4 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] From 82accad8702e698896a047f74bfa0b75bedbd905 Mon Sep 17 00:00:00 2001 From: eileen-nava <80347702+eileen-nava@users.noreply.github.com> Date: Wed, 15 Mar 2023 09:36:52 -0400 Subject: [PATCH 10/18] LG-9002: Collect state id address on state id page (#7979) * Fix test that is inconsistent across daylight saving time changes (#7977) * Fix test that is inconsistent across daylight saving time changes changelog: Internal, Testing, Fix test that is inconsistent across daylight saving time changes * refactor to work consistently across DST * update state id page to include address fields * capture secondary address info on state id page * update translations * neaten up code * update translations * state id displays new fields but doesn't save to correct attributes * save to updated attributes * changelog: Upcoming Features, In-person proofing, collect state id address * fix html & yaml formatting * fix failing tests * add path expectation to spec * Fix bordered radio button styling * clean up build * respond to feedback --------- Co-authored-by: Mitchell Henke Co-authored-by: Sheldon Bachstein --- app/forms/idv/state_id_form.rb | 4 +- app/services/pii/attributes.rb | 1 + app/views/idv/in_person/state_id.html.erb | 85 +++++++++++++++++++---- config/initializers/simple_form.rb | 8 ++- config/locales/in_person_proofing/en.yml | 9 ++- config/locales/in_person_proofing/es.yml | 12 +++- config/locales/in_person_proofing/fr.yml | 13 +++- spec/features/idv/in_person_spec.rb | 33 ++++++++- spec/support/features/in_person_helper.rb | 18 ++++- 9 files changed, 157 insertions(+), 26 deletions(-) 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/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/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/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/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/spec/features/idv/in_person_spec.rb b/spec/features/idv/in_person_spec.rb index 66850c5b5da..89d6764b562 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 @@ -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 @@ -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')) @@ -483,4 +488,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/support/features/in_person_helper.rb b/spec/support/features/in_person_helper.rb index 87509e690b4..3bfa4a51281 100644 --- a/spec/support/features/in_person_helper.rb +++ b/spec/support/features/in_person_helper.rb @@ -22,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('-') @@ -32,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 @@ -72,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) From 244a837abb5e83f2d29632ffa525c605dd2df548 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Wed, 15 Mar 2023 10:40:53 -0400 Subject: [PATCH 11/18] Simplify session timeout modal presenter (#7985) * Simplify session timeout modal presenter changelog: Internal, Refactoring, Simplify logic for session timeout modal behavior * Mark translations as used See: https://github.com/18F/identity-idp/pull/7985/files#r1136148250 Co-Authored-By: Zach Margolis * Rename locale_scope as translation_scope See: https://github.com/18F/identity-idp/pull/7985/files#r1136148250 --------- Co-authored-by: Zach Margolis --- app/helpers/session_timeout_warning_helper.rb | 6 +-- .../fully_signed_in_modal_presenter.rb | 42 --------------- .../partially_signed_in_modal_presenter.rb | 42 --------------- .../session_timeout_modal_presenter.rb | 18 +++++++ app/views/session_timeout/_warning.html.erb | 54 ++++++++++++++++--- config/locales/notices/en.yml | 10 ++-- config/locales/notices/es.yml | 12 ++--- config/locales/notices/fr.yml | 12 ++--- .../fully_signed_in_modal_presenter_spec.rb | 46 ---------------- ...artially_signed_in_modal_presenter_spec.rb | 46 ---------------- .../session_timeout_modal_presenter_spec.rb | 27 ++++++++++ 11 files changed, 110 insertions(+), 205 deletions(-) delete mode 100644 app/presenters/fully_signed_in_modal_presenter.rb delete mode 100644 app/presenters/partially_signed_in_modal_presenter.rb create mode 100644 app/presenters/session_timeout_modal_presenter.rb delete mode 100644 spec/presenters/fully_signed_in_modal_presenter_spec.rb delete mode 100644 spec/presenters/partially_signed_in_modal_presenter_spec.rb create mode 100644 spec/presenters/session_timeout_modal_presenter_spec.rb 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/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/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/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/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 From 50b74c32dcca909c3a83f715202deb1f489a7cf9 Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Wed, 15 Mar 2023 09:55:32 -0500 Subject: [PATCH 12/18] Update Rails (#7983) changelog: Internal, Security, Update Rails with security patch --- Gemfile.lock | 112 +++++++++++++++++++++++++-------------------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 54b1c1549e6..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) @@ -651,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) From f4a5868a0a748a208bd4937e9b0ee632f713e634 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Wed, 15 Mar 2023 10:59:35 -0400 Subject: [PATCH 13/18] Jmax/lg 9084 remove personal key in gpo feature flag (#7948) Removed feature flag and related code. Refactored step_indicator_concern spec in the process. [skip changelog] Co-authored-by: Zach Margolis --- app/controllers/idv/gpo_verify_controller.rb | 8 +- .../idv/personal_key_controller.rb | 6 +- app/controllers/idv/review_controller.rb | 3 +- app/services/idv/flows/doc_auth_flow.rb | 3 +- config/application.yml.default | 1 - config/locales/idv/en.yml | 1 - config/locales/idv/es.yml | 1 - config/locales/idv/fr.yml | 1 - lib/identity_config.rb | 1 - .../idv/step_indicator_concern_spec.rb | 98 +++++++++++++------ .../idv/gpo_verify_controller_spec.rb | 10 +- .../idv/personal_key_controller_spec.rb | 9 -- .../controllers/idv/review_controller_spec.rb | 1 - spec/features/idv/analytics_spec.rb | 4 - .../idv/clearing_and_restarting_spec.rb | 1 - spec/features/idv/in_person_spec.rb | 3 - .../idv/steps/confirmation_step_spec.rb | 3 +- .../steps/gpo_otp_verification_step_spec.rb | 2 - spec/features/idv/steps/gpo_step_spec.rb | 7 -- spec/features/idv/steps/review_step_spec.rb | 1 - spec/features/saml/ial2_sso_spec.rb | 2 - spec/features/users/verify_profile_spec.rb | 2 +- .../idv_examples/clearing_and_restarting.rb | 1 - 23 files changed, 77 insertions(+), 92 deletions(-) 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/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/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/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/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/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/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/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/review_controller_spec.rb b/spec/controllers/idv/review_controller_spec.rb index 80e1b1a203a..7d265b81ad8 100644 --- a/spec/controllers/idv/review_controller_spec.rb +++ b/spec/controllers/idv/review_controller_spec.rb @@ -610,7 +610,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/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/in_person_spec.rb b/spec/features/idv/in_person_spec.rb index 89d6764b562..4b5af8607eb 100644 --- a/spec/features/idv/in_person_spec.rb +++ b/spec/features/idv/in_person_spec.rb @@ -380,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')) @@ -408,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') 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/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/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 From 67aa4bddbe9be25b86a58a511c6941260019551f Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Wed, 15 Mar 2023 11:24:53 -0400 Subject: [PATCH 14/18] Add a `#verify_info_step_complete?` and `mark_verify_info_step_complete!` methods to `Idv::Session` (#7958) The IdV Session manages the session variables during proofing and is inspected throughout to determine if a user should be on a particular step. This means the session contains a series of flags or artifacts that determine if steps were completed. The names of these and the impacts of changing them may not be obvious, so in this commit some methods for marking a step complete and checking that a step is complete were added alongside methods for invalidating a step. This is a pattern we intend to apply to other steps in the proofing flow. changelog: Improvement, FSM Retirement, Methods were added to the Idv Session class for determining whether the verify info step was completed and for marking the verify info step complete. --- .../concerns/idv/step_utilities_concern.rb | 2 +- app/controllers/concerns/idv/verify_info_concern.rb | 7 +++---- app/controllers/idv/gpo_controller.rb | 2 +- .../idv/in_person/verify_info_controller.rb | 2 +- app/controllers/idv/session_errors_controller.rb | 2 +- app/controllers/idv/verify_info_controller.rb | 4 ++-- app/services/idv/session.rb | 11 ++++++++++- spec/controllers/concerns/idv_step_concern_spec.rb | 5 +---- spec/controllers/idv/phone_controller_spec.rb | 1 - spec/controllers/idv/review_controller_spec.rb | 3 +-- .../controllers/idv/session_errors_controller_spec.rb | 8 ++++---- spec/controllers/idv/verify_info_controller_spec.rb | 2 +- spec/support/controller_helper.rb | 5 ++--- 13 files changed, 28 insertions(+), 26 deletions(-) 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/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/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/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/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/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/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 7d265b81ad8..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 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/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/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) From 09cadbf601ad012e92b1e5200c6deb65132d1fa5 Mon Sep 17 00:00:00 2001 From: Zach Margolis Date: Wed, 15 Mar 2023 08:26:22 -0700 Subject: [PATCH 15/18] Add report that monitors duplicate SSNs (LG-9191) (#7988) changelog: Internal, Reporting, Add report for duplicate SSNs --- app/jobs/reports/duplicate_ssn_report.rb | 75 +++++++++++++++ config/initializers/job_configurations.rb | 6 ++ .../jobs/reports/duplicate_ssn_report_spec.rb | 92 +++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 app/jobs/reports/duplicate_ssn_report.rb create mode 100644 spec/jobs/reports/duplicate_ssn_report_spec.rb diff --git a/app/jobs/reports/duplicate_ssn_report.rb b/app/jobs/reports/duplicate_ssn_report.rb new file mode 100644 index 00000000000..e689e8800a4 --- /dev/null +++ b/app/jobs/reports/duplicate_ssn_report.rb @@ -0,0 +1,75 @@ +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) + + CSV.generate do |csv| + csv << %w[ + new_account + uuid + account_created_at + identity_verified_at + ssn_fingerprint + count_ssn_fingerprint + ] + + profiles_connected_by_ssn.each do |profile| + ssn_count = count_by_ssn[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.ssn_signature, + ssn_count, + ] + end + end + end + end +end diff --git a/config/initializers/job_configurations.rb b/config/initializers/job_configurations.rb index 932ca006262..2702485bb7a 100644 --- a/config/initializers/job_configurations.rb +++ b/config/initializers/job_configurations.rb @@ -139,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/spec/jobs/reports/duplicate_ssn_report_spec.rb b/spec/jobs/reports/duplicate_ssn_report_spec.rb new file mode 100644 index 00000000000..ed1885a0072 --- /dev/null +++ b/spec/jobs/reports/duplicate_ssn_report_spec.rb @@ -0,0 +1,92 @@ +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 + 2.times.map do + create( + :profile, + :active, + ssn_signature: ssn_fingerprint2, + activated_at: report_date - 10.days, + ).tap(&:reload) + end + 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) + expect(Time.zone.parse(row['identity_verified_at']).to_i).to eq(profile.activated_at.to_i) + expect(row['ssn_fingerprint']).to eq(ssn_fingerprint2) + expect(row['count_ssn_fingerprint']).to eq('3') + end + end + end +end From 67e40bca1c7c818d28f1161578340918f02cd710 Mon Sep 17 00:00:00 2001 From: Osman Latif <109746710+olatifflexion@users.noreply.github.com> Date: Wed, 15 Mar 2023 11:33:26 -0500 Subject: [PATCH 16/18] LG-9067: Account reset account deleted event fixes (#7982) * LG-9067: Account reset account deleted event fixes changelog: Internal, Attempts API, Update & fixes Account reset account deleted event --- .../delete_account_controller.rb | 8 +- .../account_reset/request_controller.rb | 2 +- app/models/account_reset_request.rb | 6 ++ app/services/account_reset/create_request.rb | 6 +- app/services/account_reset/delete_account.rb | 8 +- app/services/account_reset/track_irs_event.rb | 36 +++++++ .../account_reset/validate_granted_token.rb | 9 +- .../account_reset/granted_token_validator.rb | 5 + ...p_issuer_field_to_account_reset_request.rb | 5 + db/schema.rb | 3 +- .../delete_account_controller_spec.rb | 32 ------ .../account_reset/delete_account_spec.rb | 100 ++++++++++++++++++ .../account_reset/create_request_spec.rb | 14 ++- .../account_reset/delete_account_spec.rb | 59 ++++++++++- .../validate_granted_token_spec.rb | 49 +++++++++ spec/support/account_reset_helper.rb | 3 +- 16 files changed, 294 insertions(+), 51 deletions(-) create mode 100644 app/services/account_reset/track_irs_event.rb create mode 100644 db/primary_migrate/20230309201053_add_sp_issuer_field_to_account_reset_request.rb create mode 100644 spec/services/account_reset/validate_granted_token_spec.rb 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/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/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/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/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/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/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/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/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 From 6d2a4527b37cf69ecc52ad42acc86ab3f8aeffa3 Mon Sep 17 00:00:00 2001 From: Matt Hinz Date: Wed, 15 Mar 2023 09:58:06 -0700 Subject: [PATCH 17/18] Update eslint-plugin-import (#7987) * Update eslint-plugin-import Fix warning in `yarn audit` [skip changelog] * Update version specifier for eslint-plugin-import * Run yarn-deduplicate to reduce duplicate dependencies --- package.json | 2 +- yarn.lock | 278 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 175 insertions(+), 105 deletions(-) diff --git a/package.json b/package.json index 8056e7fc06c..b20906934eb 100644 --- a/package.json +++ b/package.json @@ -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/yarn.lock b/yarn.lock index d526bdb79fa..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" @@ -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== From 6f92cfbaf2f04dfce4465f6133dc00837a646aaa Mon Sep 17 00:00:00 2001 From: Zach Margolis Date: Wed, 15 Mar 2023 12:19:42 -0700 Subject: [PATCH 18/18] Add columns to duplicate SSN reporting (LG-9191) (#7995) * Add profile_active column * Add count_active_ssn_fingerprint column * Handle nil activated_at values changelog: Internal, Reporting, Add report for duplicate SSNs (cherry picked from commit f8d0bb5b389388c40dea6c9c368fa64943d6e39b) --- app/jobs/reports/duplicate_ssn_report.rb | 15 ++++++++++++-- .../jobs/reports/duplicate_ssn_report_spec.rb | 20 ++++++++++++++----- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/app/jobs/reports/duplicate_ssn_report.rb b/app/jobs/reports/duplicate_ssn_report.rb index e689e8800a4..42e3ef7a629 100644 --- a/app/jobs/reports/duplicate_ssn_report.rb +++ b/app/jobs/reports/duplicate_ssn_report.rb @@ -44,7 +44,13 @@ def report_body 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 = 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[ @@ -52,21 +58,26 @@ def report_body 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.activated_at&.in_time_zone('UTC')&.iso8601, + profile.active, profile.ssn_signature, ssn_count, + ssn_count_active, ] end end diff --git a/spec/jobs/reports/duplicate_ssn_report_spec.rb b/spec/jobs/reports/duplicate_ssn_report_spec.rb index ed1885a0072..afe816a4442 100644 --- a/spec/jobs/reports/duplicate_ssn_report_spec.rb +++ b/spec/jobs/reports/duplicate_ssn_report_spec.rb @@ -48,14 +48,20 @@ end let!(:fingerprint2_previous_profiles) do - 2.times.map do + [ create( :profile, - :active, + active: false, ssn_signature: ssn_fingerprint2, activated_at: report_date - 10.days, - ).tap(&:reload) - end + ), + create( + :profile, + active: false, + ssn_signature: ssn_fingerprint2, + activated_at: nil, + ), + ].map(&:reload) end it 'creates csv with corresponding data', aggregate_failures: true do @@ -83,9 +89,13 @@ 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) - expect(Time.zone.parse(row['identity_verified_at']).to_i).to eq(profile.activated_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