diff --git a/.rubocop.yml b/.rubocop.yml index 3b952f5e1c4..c882e675be8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -28,7 +28,7 @@ AllCops: - 'vendor/**/*' - 'public/**/*' TargetRubyVersion: 3.2.0 - TargetRailsVersion: 7.1 + TargetRailsVersion: 7.2 UseCache: true DisabledByDefault: true SuggestExtensions: false diff --git a/Gemfile b/Gemfile index e96adfe135d..70131c68b55 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" } ruby "~> #{File.read(File.join(__dir__, '.ruby-version')).strip}" -gem 'rails', '~> 7.1.4' +gem 'rails', '~> 7.2.1' gem 'ahoy_matey', '~> 3.0' # pod identity requires 3.188.0 @@ -117,7 +117,7 @@ group :development, :test do gem 'pry-rails' gem 'psych' gem 'rspec', '~> 3.13.0' - gem 'rspec-rails', '~> 6.0' + gem 'rspec-rails', '~> 7.0' gem 'rubocop', '~> 1.62.0', require: false gem 'rubocop-performance', '~> 1.20.2', require: false gem 'rubocop-rails', '>= 2.26.2', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 9dbc4929498..149c90bb090 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -79,80 +79,76 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.1.4.1) - actionpack (= 7.1.4.1) - activesupport (= 7.1.4.1) + actioncable (7.2.1.1) + actionpack (= 7.2.1.1) + activesupport (= 7.2.1.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.1.4.1) - actionpack (= 7.1.4.1) - activejob (= 7.1.4.1) - activerecord (= 7.1.4.1) - activestorage (= 7.1.4.1) - activesupport (= 7.1.4.1) - mail (>= 2.7.1) - net-imap - net-pop - net-smtp - actionmailer (7.1.4.1) - actionpack (= 7.1.4.1) - actionview (= 7.1.4.1) - activejob (= 7.1.4.1) - activesupport (= 7.1.4.1) - mail (~> 2.5, >= 2.5.4) - net-imap - net-pop - net-smtp + actionmailbox (7.2.1.1) + actionpack (= 7.2.1.1) + activejob (= 7.2.1.1) + activerecord (= 7.2.1.1) + activestorage (= 7.2.1.1) + activesupport (= 7.2.1.1) + mail (>= 2.8.0) + actionmailer (7.2.1.1) + actionpack (= 7.2.1.1) + actionview (= 7.2.1.1) + activejob (= 7.2.1.1) + activesupport (= 7.2.1.1) + mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (7.1.4.1) - actionview (= 7.1.4.1) - activesupport (= 7.1.4.1) + actionpack (7.2.1.1) + actionview (= 7.2.1.1) + activesupport (= 7.2.1.1) nokogiri (>= 1.8.5) racc - rack (>= 2.2.4) + rack (>= 2.2.4, < 3.2) rack-session (>= 1.0.1) rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - actiontext (7.1.4.1) - actionpack (= 7.1.4.1) - activerecord (= 7.1.4.1) - activestorage (= 7.1.4.1) - activesupport (= 7.1.4.1) + useragent (~> 0.16) + actiontext (7.2.1.1) + actionpack (= 7.2.1.1) + activerecord (= 7.2.1.1) + activestorage (= 7.2.1.1) + activesupport (= 7.2.1.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.1.4.1) - activesupport (= 7.1.4.1) + actionview (7.2.1.1) + activesupport (= 7.2.1.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (7.1.4.1) - activesupport (= 7.1.4.1) + activejob (7.2.1.1) + activesupport (= 7.2.1.1) globalid (>= 0.3.6) - activemodel (7.1.4.1) - activesupport (= 7.1.4.1) - activerecord (7.1.4.1) - activemodel (= 7.1.4.1) - activesupport (= 7.1.4.1) + activemodel (7.2.1.1) + activesupport (= 7.2.1.1) + activerecord (7.2.1.1) + activemodel (= 7.2.1.1) + activesupport (= 7.2.1.1) timeout (>= 0.4.0) - activestorage (7.1.4.1) - actionpack (= 7.1.4.1) - activejob (= 7.1.4.1) - activerecord (= 7.1.4.1) - activesupport (= 7.1.4.1) + activestorage (7.2.1.1) + actionpack (= 7.2.1.1) + activejob (= 7.2.1.1) + activerecord (= 7.2.1.1) + activesupport (= 7.2.1.1) marcel (~> 1.0) - activesupport (7.1.4.1) + activesupport (7.2.1.1) base64 bigdecimal - concurrent-ruby (~> 1.0, >= 1.0.2) + concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) - mutex_m - tzinfo (~> 2.0) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) ahoy_matey (3.3.0) @@ -395,7 +391,7 @@ GEM listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - logger (1.6.0) + logger (1.6.1) lograge (0.11.2) actionpack (>= 4) activesupport (>= 4) @@ -433,7 +429,6 @@ GEM minitest (5.24.1) msgpack (1.7.2) multiset (0.5.3) - mutex_m (0.2.0) net-http (0.4.1) uri net-http-persistent (4.0.2) @@ -523,20 +518,20 @@ GEM rackup (2.1.0) rack (>= 3) webrick (~> 1.8) - rails (7.1.4.1) - actioncable (= 7.1.4.1) - actionmailbox (= 7.1.4.1) - actionmailer (= 7.1.4.1) - actionpack (= 7.1.4.1) - actiontext (= 7.1.4.1) - actionview (= 7.1.4.1) - activejob (= 7.1.4.1) - activemodel (= 7.1.4.1) - activerecord (= 7.1.4.1) - activestorage (= 7.1.4.1) - activesupport (= 7.1.4.1) + rails (7.2.1.1) + actioncable (= 7.2.1.1) + actionmailbox (= 7.2.1.1) + actionmailer (= 7.2.1.1) + actionpack (= 7.2.1.1) + actiontext (= 7.2.1.1) + actionview (= 7.2.1.1) + activejob (= 7.2.1.1) + activemodel (= 7.2.1.1) + activerecord (= 7.2.1.1) + activestorage (= 7.2.1.1) + activesupport (= 7.2.1.1) bundler (>= 1.15.0) - railties (= 7.1.4.1) + railties (= 7.2.1.1) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -551,10 +546,10 @@ GEM rails-i18n (7.0.6) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) - railties (7.1.4.1) - actionpack (= 7.1.4.1) - activesupport (= 7.1.4.1) - irb + railties (7.2.1.1) + actionpack (= 7.2.1.1) + activesupport (= 7.2.1.1) + irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) thor (~> 1.0, >= 1.2.2) @@ -592,22 +587,22 @@ GEM rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) - rspec-core (3.13.0) + rspec-core (3.13.1) rspec-support (~> 3.13.0) - rspec-expectations (3.13.0) + rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.0) + rspec-mocks (3.13.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (6.0.3) - actionpack (>= 6.1) - activesupport (>= 6.1) - railties (>= 6.1) - rspec-core (~> 3.12) - rspec-expectations (~> 3.12) - rspec-mocks (~> 3.12) - rspec-support (~> 3.12) + rspec-rails (7.0.1) + actionpack (>= 7.0) + activesupport (>= 7.0) + railties (>= 7.0) + rspec-core (~> 3.13) + rspec-expectations (~> 3.13) + rspec-mocks (~> 3.13) + rspec-support (~> 3.13) rspec-retry (0.6.2) rspec-core (> 3.3) rspec-support (3.13.1) @@ -656,6 +651,7 @@ GEM jwt (~> 2.0) scrypt (3.0.7) ffi-compiler (>= 1.0, < 2.0) + securerandom (0.3.1) selenium-webdriver (4.22.0) base64 (~> 0.2) logger (~> 1.4) @@ -706,6 +702,7 @@ GEM unicode-display_width (2.5.0) uniform_notifier (1.16.0) uri (0.13.0) + useragent (0.16.10) view_component (3.9.0) activesupport (>= 5.2.0, < 8.0) concurrent-ruby (~> 1.0) @@ -837,7 +834,7 @@ DEPENDENCIES rack-test (>= 1.1.0) rack-timeout rack_session_access (>= 0.2.0) - rails (~> 7.1.4) + rails (~> 7.2.1) rails-controller-testing (>= 1.0.4) redacted_struct redis (>= 3.2.0) @@ -847,7 +844,7 @@ DEPENDENCIES rotp (~> 6.3, >= 6.3.0) rqrcode rspec (~> 3.13.0) - rspec-rails (~> 6.0) + rspec-rails (~> 7.0) rspec-retry rspec_junit_formatter rubocop (~> 1.62.0) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0933f040a98..bf70f35a09b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -230,6 +230,7 @@ def after_sign_in_path_for(_user) return authentication_methods_setup_url if user_needs_sp_auth_method_setup? return fix_broken_personal_key_url if current_user.broken_personal_key? return user_session.delete(:stored_location) if user_session.key?(:stored_location) + return setup_piv_cac_url if user_session[:add_piv_cac_after_2fa] return login_add_piv_cac_prompt_url if session[:needs_to_setup_piv_cac_after_sign_in].present? return reactivate_account_url if user_needs_to_reactivate_account? return login_piv_cac_recommended_path if user_recommended_for_piv_cac? diff --git a/app/controllers/concerns/two_factor_authenticatable_methods.rb b/app/controllers/concerns/two_factor_authenticatable_methods.rb index a69ecdbfe9f..9a3502ae4ef 100644 --- a/app/controllers/concerns/two_factor_authenticatable_methods.rb +++ b/app/controllers/concerns/two_factor_authenticatable_methods.rb @@ -12,16 +12,19 @@ def auth_methods_session end def handle_verification_for_authentication_context(result:, auth_method:, extra_analytics: nil) + increment_mfa_selection_attempt_count(auth_method) analytics.multi_factor_auth( **result.to_h, multi_factor_auth_method: auth_method, enabled_mfa_methods_count: mfa_context.enabled_mfa_methods_count, new_device: new_device?, **extra_analytics.to_h, + attempts: mfa_attempts_count, ) if result.success? handle_valid_verification_for_authentication_context(auth_method:) + user_session.delete(:mfa_attempts) else handle_invalid_verification_for_authentication_context end @@ -113,6 +116,20 @@ def handle_remember_device_preference(remember_device_preference) save_remember_device_preference(remember_device_preference) end + def increment_mfa_selection_attempt_count(auth_method) + user_session[:mfa_attempts] ||= {} + user_session[:mfa_attempts][:attempts] ||= 0 + if user_session[:mfa_attempts][:auth_method] != auth_method + user_session[:mfa_attempts][:attempts] = 0 + end + user_session[:mfa_attempts][:attempts] += 1 + user_session[:mfa_attempts][:auth_method] = auth_method + end + + def mfa_attempts_count + user_session.dig(:mfa_attempts, :attempts) + end + # Method will be renamed in the next refactor. # You can pass in any "type" with a corresponding I18n key in # two_factor_authentication.invalid_#{type} @@ -137,8 +154,6 @@ def invalid_otp_error(type) t('two_factor_authentication.invalid_otp') when 'personal_key' t('two_factor_authentication.invalid_personal_key') - when 'piv_cac' - t('two_factor_authentication.invalid_piv_cac') else raise "Unsupported otp method: #{type}" end diff --git a/app/controllers/idv/in_person/address_controller.rb b/app/controllers/idv/in_person/address_controller.rb index 36512fdc59c..a1ca3afd542 100644 --- a/app/controllers/idv/in_person/address_controller.rb +++ b/app/controllers/idv/in_person/address_controller.rb @@ -109,11 +109,7 @@ def redirect_to_next_page def confirm_in_person_state_id_step_complete return if pii_from_user&.has_key?(:identity_doc_address1) - if IdentityConfig.store.in_person_state_id_controller_enabled - redirect_to idv_in_person_proofing_state_id_url - else - redirect_to idv_in_person_step_url(step: :state_id) - end + redirect_to idv_in_person_proofing_state_id_url end def confirm_in_person_address_step_needed diff --git a/app/controllers/idv/in_person/public/usps_locations_controller.rb b/app/controllers/idv/in_person/public/usps_locations_controller.rb index eafddf1924a..b8f20a7f96d 100644 --- a/app/controllers/idv/in_person/public/usps_locations_controller.rb +++ b/app/controllers/idv/in_person/public/usps_locations_controller.rb @@ -4,10 +4,6 @@ module Idv module InPerson module Public class UspsLocationsController < ApplicationController - include RenderConditionConcern - - check_or_render_not_found -> { enabled? } - skip_forgery_protection def index @@ -38,10 +34,6 @@ def localized_locations(locations) end end - def enabled? - IdentityConfig.store.in_person_public_address_search_enabled - end - def search_params params.require(:address).permit( :street_address, diff --git a/app/controllers/idv/in_person_controller.rb b/app/controllers/idv/in_person_controller.rb index 49071561e0c..0da218e48f7 100644 --- a/app/controllers/idv/in_person_controller.rb +++ b/app/controllers/idv/in_person_controller.rb @@ -20,7 +20,7 @@ class InPersonController < ApplicationController FLOW_STATE_MACHINE_SETTINGS = { step_url: :idv_in_person_step_url, - final_url: :idv_in_person_address_url, + final_url: :idv_in_person_proofing_state_id_url, flow: Idv::Flows::InPersonFlow, analytics_id: 'In Person Proofing', }.freeze diff --git a/app/controllers/two_factor_authentication/options_controller.rb b/app/controllers/two_factor_authentication/options_controller.rb index fec6acb3a8d..9d74854fad7 100644 --- a/app/controllers/two_factor_authentication/options_controller.rb +++ b/app/controllers/two_factor_authentication/options_controller.rb @@ -54,6 +54,7 @@ def two_factor_options_presenter service_provider: current_sp, phishing_resistant_required: service_provider_mfa_policy.phishing_resistant_required?, piv_cac_required: service_provider_mfa_policy.piv_cac_required?, + add_piv_cac_after_2fa: user_session[:add_piv_cac_after_2fa].present?, ) end diff --git a/app/controllers/two_factor_authentication/otp_verification_controller.rb b/app/controllers/two_factor_authentication/otp_verification_controller.rb index 728d8620ce6..dee0a141549 100644 --- a/app/controllers/two_factor_authentication/otp_verification_controller.rb +++ b/app/controllers/two_factor_authentication/otp_verification_controller.rb @@ -20,6 +20,9 @@ def show end def create + if UserSessionContext.confirmation_context?(context) + increment_mfa_selection_attempt_count(otp_auth_method) + end result = otp_verification_form.submit post_analytics(result) @@ -41,6 +44,7 @@ def create end reset_otp_session_data + user_session.delete(:mfa_attempts) else handle_invalid_otp(type: 'otp') end @@ -48,6 +52,14 @@ def create private + def otp_auth_method + if params[:otp_delivery_preference] == 'sms' + TwoFactorAuthenticatable::AuthMethod::SMS + else + TwoFactorAuthenticatable::AuthMethod::VOICE + end + end + def handle_valid_confirmation_otp assign_phone track_mfa_added @@ -155,6 +167,7 @@ def analytics_properties phone_configuration_id: phone_configuration&.id, in_account_creation_flow: user_session[:in_account_creation_flow] || false, enabled_mfa_methods_count: mfa_context.enabled_mfa_methods_count, + attempts: mfa_attempts_count, } end diff --git a/app/controllers/two_factor_authentication/piv_cac_mismatch_controller.rb b/app/controllers/two_factor_authentication/piv_cac_mismatch_controller.rb new file mode 100644 index 00000000000..a6fe0a3bae8 --- /dev/null +++ b/app/controllers/two_factor_authentication/piv_cac_mismatch_controller.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module TwoFactorAuthentication + class PivCacMismatchController < ApplicationController + include TwoFactorAuthenticatable + + def show + analytics.piv_cac_mismatch_visited( + piv_cac_required: piv_cac_required?, + has_other_authentication_methods: has_other_authentication_methods?, + ) + + @piv_cac_required = piv_cac_required? + @has_other_authentication_methods = has_other_authentication_methods? + end + + def create + analytics.piv_cac_mismatch_submitted(add_piv_cac_after_2fa: add_piv_cac_after_2fa?) + user_session[:add_piv_cac_after_2fa] = add_piv_cac_after_2fa? + redirect_to login_two_factor_options_url + end + + private + + def add_piv_cac_after_2fa? + params[:add_piv_cac_after_2fa] == 'true' + end + + def piv_cac_required? + service_provider_mfa_policy.piv_cac_required? + end + + def has_other_authentication_methods? + return @has_other_authentication_methods if defined?(@has_other_authentication_methods) + @has_other_authentication_methods = mfa_context.two_factor_configurations.any? do |config| + config.mfa_enabled? && !config.is_a?(PivCacConfiguration) + end + end + + def mfa_context + @mfa_context ||= MfaContext.new(current_user) + end + end +end diff --git a/app/controllers/two_factor_authentication/piv_cac_verification_controller.rb b/app/controllers/two_factor_authentication/piv_cac_verification_controller.rb index 27ec7494354..a1fb1a0f5ed 100644 --- a/app/controllers/two_factor_authentication/piv_cac_verification_controller.rb +++ b/app/controllers/two_factor_authentication/piv_cac_verification_controller.rb @@ -58,16 +58,22 @@ def handle_valid_piv_cac def handle_invalid_piv_cac clear_piv_cac_information - handle_invalid_otp(type: 'piv_cac') + update_invalid_user + + if current_user.locked_out? + handle_second_factor_locked_user(type: 'piv_cac') + elsif redirect_for_piv_cac_mismatch_replacement? + redirect_to login_two_factor_piv_cac_mismatch_url + else + flash[:error] = t('two_factor_authentication.invalid_piv_cac') + redirect_to login_two_factor_piv_cac_url + end end - # This overrides the method in TwoFactorAuthenticatable so that we - # redirect back to ourselves rather than rendering the :show template. - # This removes the token from the address bar and preserves the error - # in the flash. - def render_show_after_invalid - flash[:error] = flash.now[:error] - redirect_to login_two_factor_piv_cac_url + def redirect_for_piv_cac_mismatch_replacement? + piv_cac_verification_form.error_type == 'user.piv_cac_mismatch' && + UserSessionContext.authentication_context?(context) && + current_user.piv_cac_configurations.count < IdentityConfig.store.max_piv_cac_per_account end def piv_cac_view_data diff --git a/app/controllers/two_factor_authentication/totp_verification_controller.rb b/app/controllers/two_factor_authentication/totp_verification_controller.rb index 5577e37a5ed..7eff4f6ac53 100644 --- a/app/controllers/two_factor_authentication/totp_verification_controller.rb +++ b/app/controllers/two_factor_authentication/totp_verification_controller.rb @@ -25,7 +25,6 @@ def create result:, auth_method: TwoFactorAuthenticatable::AuthMethod::TOTP, ) - if result.success? handle_remember_device_preference(params[:remember_device]) redirect_to after_sign_in_path_for(current_user) diff --git a/app/controllers/users/piv_cac_authentication_setup_controller.rb b/app/controllers/users/piv_cac_authentication_setup_controller.rb index 9b32434f741..b52d1e3bad9 100644 --- a/app/controllers/users/piv_cac_authentication_setup_controller.rb +++ b/app/controllers/users/piv_cac_authentication_setup_controller.rb @@ -18,6 +18,8 @@ class PivCacAuthenticationSetupController < ApplicationController helper_method :in_multi_mfa_selection_flow? def new + @piv_cac_required = service_provider_mfa_policy.piv_cac_required? + if params.key?(:token) process_piv_cac_setup else @@ -35,7 +37,10 @@ def error end def submit_new_piv_cac - if good_nickname + if skip? + user_session.delete(:add_piv_cac_after_2fa) + redirect_to after_sign_in_path_for(current_user) + elsif good_nickname? user_session[:piv_cac_nickname] = params[:name] create_piv_cac_nonce redirect_to piv_cac_service_url_with_redirect, allow_other_host: true @@ -66,11 +71,13 @@ def piv_cac_service_url_with_redirect end def process_piv_cac_setup + increment_mfa_selection_attempt_count(TwoFactorAuthenticatable::AuthMethod::PIV_CAC) result = user_piv_cac_form.submit properties = result.to_h.merge(analytics_properties) analytics.multi_factor_auth_setup(**properties) if result.success? process_valid_submission + user_session.delete(:mfa_attempts) else process_invalid_submission end @@ -98,6 +105,7 @@ def process_valid_submission ) create_user_event(:piv_cac_enabled) track_mfa_method_added + user_session.delete(:add_piv_cac_after_2fa) session[:needs_to_setup_piv_cac_after_sign_in] = false redirect_to next_setup_path || after_sign_in_path_for(current_user) end @@ -117,7 +125,11 @@ def process_invalid_submission end end - def good_nickname + def skip? + params[:skip] == 'true' + end + + def good_nickname? name = params[:name] name.present? && !PivCacConfiguration.exists?(user_id: current_user.id, name: name) end @@ -126,6 +138,7 @@ def analytics_properties { in_account_creation_flow: user_session[:in_account_creation_flow] || false, enabled_mfa_methods_count: mfa_context.enabled_mfa_methods_count, + attempts: mfa_attempts_count, } end diff --git a/app/controllers/users/totp_setup_controller.rb b/app/controllers/users/totp_setup_controller.rb index fb9524a8be1..af09ac734fe 100644 --- a/app/controllers/users/totp_setup_controller.rb +++ b/app/controllers/users/totp_setup_controller.rb @@ -26,12 +26,13 @@ def new def confirm result = totp_setup_form.submit - + increment_mfa_selection_attempt_count(TwoFactorAuthenticatable::AuthMethod::TOTP) properties = result.to_h.merge(analytics_properties) analytics.multi_factor_auth_setup(**properties) if result.success? process_valid_code + user_session.delete(:mfa_attempts) else process_invalid_code end @@ -118,6 +119,7 @@ def analytics_properties { in_account_creation_flow: in_account_creation_flow?, pii_like_keypaths: [[:mfa_method_counts, :phone]], + attempts: mfa_attempts_count, } end end diff --git a/app/controllers/users/webauthn_setup_controller.rb b/app/controllers/users/webauthn_setup_controller.rb index 6a80cfda56b..60bb0fc4952 100644 --- a/app/controllers/users/webauthn_setup_controller.rb +++ b/app/controllers/users/webauthn_setup_controller.rb @@ -43,6 +43,7 @@ def new @need_to_set_up_additional_mfa = need_to_set_up_additional_mfa? if result.errors.present? + increment_mfa_selection_attempt_count(webauthn_auth_method) analytics.webauthn_setup_submitted( platform_authenticator: form.platform_authenticator?, errors: result.errors, @@ -54,6 +55,7 @@ def new end def confirm + increment_mfa_selection_attempt_count(webauthn_auth_method) form = WebauthnSetupForm.new( user: current_user, user_session: user_session, @@ -71,9 +73,9 @@ def confirm ) properties = result.to_h.merge(analytics_properties) analytics.multi_factor_auth_setup(**properties) - if result.success? process_valid_webauthn(form) + user_session.delete(:mfa_attempts) else flash.now[:error] = result.first_error_message render :new @@ -89,6 +91,14 @@ def validate_existing_platform_authenticator end end + def webauthn_auth_method + if @platform_authenticator + TwoFactorAuthenticatable::AuthMethod::WEBAUTHN_PLATFORM + else + TwoFactorAuthenticatable::AuthMethod::WEBAUTHN + end + end + def platform_authenticator? params[:platform] == 'true' end @@ -141,6 +151,7 @@ def process_valid_webauthn(form) def analytics_properties { in_account_creation_flow: user_session[:in_account_creation_flow] || false, + attempts: mfa_attempts_count, } end diff --git a/app/jobs/fraud_rejection_daily_job.rb b/app/jobs/fraud_rejection_daily_job.rb index 86635c3c419..b99aa9e0258 100644 --- a/app/jobs/fraud_rejection_daily_job.rb +++ b/app/jobs/fraud_rejection_daily_job.rb @@ -6,7 +6,7 @@ class FraudRejectionDailyJob < ApplicationJob def perform(_date) profiles_eligible_for_fraud_rejection.find_each do |profile| profile.reject_for_fraud(notify_user: false) - analytics.automatic_fraud_rejection( + analytics(user: profile.user).automatic_fraud_rejection( fraud_rejection_at: profile.fraud_rejection_at, ) end @@ -14,11 +14,11 @@ def perform(_date) private - def analytics(user: AnonymousUser.new) + def analytics(user:) Analytics.new(user: user, request: nil, session: {}, sp: nil) end def profiles_eligible_for_fraud_rejection - Profile.where(fraud_review_pending_at: ..30.days.ago) + Profile.includes(:user).where(fraud_review_pending_at: ..30.days.ago) end end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 7fe5e30640d..c69079ba73a 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -273,7 +273,7 @@ def in_person_completion_survey end end - def in_person_deadline_passed(enrollment:, visited_location_name:) + def in_person_deadline_passed(enrollment:, visited_location_name: nil) with_user_locale(user) do @header = t('user_mailer.in_person_deadline_passed.header') @presenter = Idv::InPerson::VerificationResultsEmailPresenter.new( @@ -338,7 +338,7 @@ def in_person_ready_to_verify_reminder(enrollment:) end end - def in_person_verified(enrollment:, visited_location_name:) + def in_person_verified(enrollment:, visited_location_name: nil) with_user_locale(user) do @hide_title = true @presenter = Idv::InPerson::VerificationResultsEmailPresenter.new( @@ -353,7 +353,7 @@ def in_person_verified(enrollment:, visited_location_name:) end end - def in_person_failed(enrollment:, visited_location_name:) + def in_person_failed(enrollment:, visited_location_name: nil) with_user_locale(user) do @presenter = Idv::InPerson::VerificationResultsEmailPresenter.new( enrollment: enrollment, @@ -367,7 +367,7 @@ def in_person_failed(enrollment:, visited_location_name:) end end - def in_person_failed_fraud(enrollment:, visited_location_name:) + def in_person_failed_fraud(enrollment:, visited_location_name: nil) with_user_locale(user) do @presenter = Idv::InPerson::VerificationResultsEmailPresenter.new( enrollment: enrollment, @@ -381,7 +381,7 @@ def in_person_failed_fraud(enrollment:, visited_location_name:) end end - def in_person_please_call(enrollment:, visited_location_name:) + def in_person_please_call(enrollment:, visited_location_name: nil) with_user_locale(user) do @presenter = Idv::InPerson::VerificationResultsEmailPresenter.new( enrollment: enrollment, diff --git a/app/models/user.rb b/app/models/user.rb index b5eb603eddd..5d53cc38dac 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -98,7 +98,7 @@ def last_identity end def active_identities - identities.where('session_uuid IS NOT ?', nil).order(last_authenticated_at: :asc) || [] + identities.where.not(session_uuid: nil).order(last_authenticated_at: :asc) || [] end def active_profile? diff --git a/app/presenters/two_factor_authentication/sign_in_piv_cac_selection_presenter.rb b/app/presenters/two_factor_authentication/sign_in_piv_cac_selection_presenter.rb index cb66efbcdff..981415b8e80 100644 --- a/app/presenters/two_factor_authentication/sign_in_piv_cac_selection_presenter.rb +++ b/app/presenters/two_factor_authentication/sign_in_piv_cac_selection_presenter.rb @@ -6,6 +6,11 @@ def type :piv_cac end + def render_in(view_context, &block) + @disabled = view_context.user_session.key?(:add_piv_cac_after_2fa) + view_context.capture(&block) + end + def label t('two_factor_authentication.login_options.piv_cac') end @@ -13,5 +18,9 @@ def label def info t('two_factor_authentication.login_options.piv_cac_info') end + + def disabled? + @disabled.present? + end end end diff --git a/app/presenters/two_factor_login_options_presenter.rb b/app/presenters/two_factor_login_options_presenter.rb index 3a9488f22dc..bcc13ba0747 100644 --- a/app/presenters/two_factor_login_options_presenter.rb +++ b/app/presenters/two_factor_login_options_presenter.rb @@ -4,11 +4,16 @@ class TwoFactorLoginOptionsPresenter < TwoFactorAuthCode::GenericDeliveryPresent include AccountResetConcern include ActionView::Helpers::TranslationHelper - attr_reader :user, :reauthentication_context, :phishing_resistant_required, :piv_cac_required + attr_reader :user, + :reauthentication_context, + :phishing_resistant_required, + :piv_cac_required, + :add_piv_cac_after_2fa alias_method :reauthentication_context?, :reauthentication_context alias_method :phishing_resistant_required?, :phishing_resistant_required alias_method :piv_cac_required?, :piv_cac_required + alias_method :add_piv_cac_after_2fa?, :add_piv_cac_after_2fa def initialize( user:, @@ -16,7 +21,8 @@ def initialize( reauthentication_context:, service_provider:, phishing_resistant_required:, - piv_cac_required: + piv_cac_required:, + add_piv_cac_after_2fa: ) @user = user @view = view @@ -24,6 +30,7 @@ def initialize( @service_provider = service_provider @phishing_resistant_required = phishing_resistant_required @piv_cac_required = piv_cac_required + @add_piv_cac_after_2fa = add_piv_cac_after_2fa end def title @@ -47,7 +54,7 @@ def info end def restricted_options_warning_text - return if reauthentication_context? + return if show_all_options? if piv_cac_required? t('two_factor_authentication.aal2_request.piv_cac_only_html', sp_name:) @@ -60,9 +67,9 @@ def options return @options if defined?(@options) mfa = MfaContext.new(user) - if piv_cac_required? && !reauthentication_context? + if piv_cac_required? && !show_all_options? configurations = mfa.piv_cac_configurations - elsif phishing_resistant_required? && !reauthentication_context? + elsif phishing_resistant_required? && !show_all_options? configurations = mfa.phishing_resistant_configurations else configurations = mfa.two_factor_configurations @@ -101,6 +108,10 @@ def first_enabled_option_index private + def show_all_options? + reauthentication_context? || add_piv_cac_after_2fa? + end + def account_reset_link t( 'two_factor_authentication.account_reset.text_html', diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 2b5f851659e..d7175ac29ce 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -2785,37 +2785,6 @@ def idv_in_person_proofing_address_visited( ) end - # @param ["hybrid","standard"] flow_path Document capture user flow - # @param [String] step - # @param [String] analytics_id - # @param [Boolean] success Whether form validation was successful - # @param [Hash] errors Errors resulting from form validation - # @param [Hash] error_details Details for errors that occurred in unsuccessful submission - # @param [Boolean] same_address_as_id - # User clicked cancel on update state id page - def idv_in_person_proofing_cancel_update_state_id( - success:, - errors:, - flow_path: nil, - step: nil, - analytics_id: nil, - error_details: nil, - same_address_as_id: nil, - **extra - ) - track_event( - 'IdV: in person proofing cancel_update_state_id submitted', - flow_path:, - step:, - analytics_id:, - success:, - errors:, - error_details:, - same_address_as_id:, - **extra, - ) - end - # A job to check USPS notifications about in-person enrollment status updates has completed # @param [Integer] fetched_items items fetched # @param [Integer] processed_items items fetched and processed @@ -2885,40 +2854,6 @@ def idv_in_person_proofing_nontransliterable_characters_submitted( ) end - # @param ["hybrid","standard"] flow_path Document capture user flow - # @param [String] step - # @param [String] analytics_id - # @param [Boolean] success Whether form validation was successful - # @param [Hash] errors Errors resulting from form validation - # @param [Hash] error_details Details for errors that occurred in unsuccessful submission - # @param [Boolean] same_address_as_id - # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing - # User submitted state id on redo state id page - def idv_in_person_proofing_redo_state_id_submitted( - success:, - errors:, - error_details: nil, - flow_path: nil, - step: nil, - analytics_id: nil, - same_address_as_id: nil, - opted_in_to_in_person_proofing: nil, - **extra - ) - track_event( - 'IdV: in person proofing redo_state_id submitted', - flow_path:, - step:, - analytics_id:, - success:, - errors:, - error_details:, - same_address_as_id:, - opted_in_to_in_person_proofing:, - **extra, - ) - end - # @param [Boolean] success Whether form validation was successful # @param [Hash] errors Errors resulting from form validation # @param [Hash] error_details Details for errors that occurred in unsuccessful submission @@ -4970,6 +4905,7 @@ def logout_initiated( # @param [Boolean] new_device Whether the user is authenticating from a new device # @param [String] multi_factor_auth_method Authentication method used # @param [String] multi_factor_auth_method_created_at When the authentication method was created + # @param [Integer] attempts number of MFA setup attempts # @param [Integer] auth_app_configuration_id Database ID of authentication app configuration # @param [Integer] piv_cac_configuration_id Database ID of PIV/CAC configuration # @param [String] piv_cac_configuration_dn_uuid PIV/CAC X509 distinguished name UUID @@ -4993,6 +4929,7 @@ def multi_factor_auth( errors: nil, error_details: nil, context: nil, + attempts: nil, multi_factor_auth_method_created_at: nil, auth_app_configuration_id: nil, piv_cac_configuration_id: nil, @@ -5016,6 +4953,7 @@ def multi_factor_auth( error_details:, context:, new_device:, + attempts:, multi_factor_auth_method:, multi_factor_auth_method_created_at:, auth_app_configuration_id:, @@ -5063,10 +5001,12 @@ def multi_factor_auth_added_phone( # @param [Integer] enabled_mfa_methods_count Number of enabled MFA methods on the account # @param [Boolean] in_account_creation_flow whether user is going through creation flow # @param ['piv_cac'] method_name Authentication method added + # @param [Integer] attempts number of MFA setup attempts def multi_factor_auth_added_piv_cac( enabled_mfa_methods_count:, in_account_creation_flow:, method_name: :piv_cac, + attempts: nil, **extra ) track_event( @@ -5074,6 +5014,7 @@ def multi_factor_auth_added_piv_cac( method_name:, enabled_mfa_methods_count:, in_account_creation_flow:, + attempts:, **extra, ) end @@ -5113,6 +5054,7 @@ def multi_factor_auth_enter_backup_code_visit(context:, **extra) end # @param ["authentication", "reauthentication", "confirmation"] context User session context + # @param [Integer] attempts number of MFA setup attempts # @param [String] multi_factor_auth_method # @param [Boolean] confirmation_for_add_phone # @param [Integer] phone_configuration_id @@ -5132,11 +5074,13 @@ def multi_factor_auth_enter_otp_visit( phone_fingerprint:, in_account_creation_flow:, enabled_mfa_methods_count:, + attempts: nil, **extra ) track_event( 'Multi-Factor Authentication: enter OTP visited', context:, + attempts:, multi_factor_auth_method:, confirmation_for_add_phone:, phone_configuration_id:, @@ -5312,6 +5256,7 @@ def multi_factor_auth_phone_setup( # @param [String, nil] key_id PIV/CAC key_id from PKI service # @param [Hash] mfa_method_counts Hash of MFA method with the number of that method on the account # @param [Hash] authenticator_data_flags WebAuthn authenticator data flags + # @param [Integer] attempts number of MFA setup attempts # @param [String, nil] aaguid AAGUID value of WebAuthn device # @param [String[], nil] unknown_transports Array of unrecognized WebAuthn transports, intended to # be used in case of future specification changes. @@ -5335,6 +5280,7 @@ def multi_factor_auth_setup( key_id: nil, mfa_method_counts: nil, authenticator_data_flags: nil, + attempts: nil, aaguid: nil, unknown_transports: nil, **extra @@ -5360,6 +5306,7 @@ def multi_factor_auth_setup( key_id:, mfa_method_counts:, authenticator_data_flags:, + attempts:, aaguid:, unknown_transports:, **extra, @@ -5989,6 +5936,24 @@ def piv_cac_login_visited track_event(:piv_cac_login_visited) end + # User submits prompt to replace PIV/CAC after failing to authenticate due to mismatched subject + # @param [Boolean] add_piv_cac_after_2fa User chooses to replace PIV/CAC authenticator + def piv_cac_mismatch_submitted(add_piv_cac_after_2fa:, **extra) + track_event(:piv_cac_mismatch_submitted, add_piv_cac_after_2fa:, **extra) + end + + # User visits prompt to replace PIV/CAC after failing to authenticate due to mismatched subject + # @param [Boolean] piv_cac_required Partner requires HSPD12 authentication + # @param [Boolean] has_other_authentication_methods User has non-PIV authentication methods + def piv_cac_mismatch_visited(piv_cac_required:, has_other_authentication_methods:, **extra) + track_event( + :piv_cac_mismatch_visited, + piv_cac_required:, + has_other_authentication_methods:, + **extra, + ) + end + # @param [String] action what action user made # Tracks when user submits an action on Piv Cac recommended page def piv_cac_recommended(action: nil, **extra) @@ -6009,11 +5974,18 @@ def piv_cac_recommended_visited # Tracks when user's piv cac setup # @param [Boolean] in_account_creation_flow Whether user is going through account creation # @param [Integer] enabled_mfa_methods_count Number of enabled MFA methods on the account - def piv_cac_setup_visited(in_account_creation_flow:, enabled_mfa_methods_count: nil, **extra) + # @param [Integer] attempts number of MFA setup attempts + def piv_cac_setup_visited( + in_account_creation_flow:, + enabled_mfa_methods_count: nil, + attempts: nil, + **extra + ) track_event( :piv_cac_setup_visited, in_account_creation_flow:, enabled_mfa_methods_count:, + attempts:, **extra, ) end diff --git a/app/services/flow/base_flow.rb b/app/services/flow/base_flow.rb index 8bd5a08e8df..18e201ed721 100644 --- a/app/services/flow/base_flow.rb +++ b/app/services/flow/base_flow.rb @@ -5,13 +5,11 @@ class BaseFlow include Failure attr_accessor :flow_session - attr_reader :steps, :actions, :current_user, :current_sp, :params, :request, :json, + attr_reader :current_user, :current_sp, :params, :request, :json, :http_status, :controller - def initialize(controller, steps, actions, session) + def initialize(controller, session) @controller = controller - @steps = steps.with_indifferent_access - @actions = actions.with_indifferent_access @redirect = nil @json = nil @flow_session = session diff --git a/app/services/flow/flow_state_machine.rb b/app/services/flow/flow_state_machine.rb index 02ca54fdcc3..338e86de78e 100644 --- a/app/services/flow/flow_state_machine.rb +++ b/app/services/flow/flow_state_machine.rb @@ -13,7 +13,7 @@ module FlowStateMachine attr_accessor :flow def index - redirect_to_step(next_step) + redirect_to idv_in_person_proofing_state_id_url end def show @@ -169,17 +169,13 @@ def flow_finish redirect_to send(@final_url) end - def redirect_to_step(step) + def redirect_to_step(_step) flow_finish and return unless next_step - redirect_url(step) + redirect_url end - def redirect_url(step) - if IdentityConfig.store.in_person_state_id_controller_enabled - redirect_to idv_in_person_proofing_state_id_url - else - redirect_to send(@step_url, step: step) - end + def redirect_url + redirect_to idv_in_person_proofing_state_id_url end def analytics_properties diff --git a/app/services/idv/actions/in_person/cancel_update_state_id_action.rb b/app/services/idv/actions/in_person/cancel_update_state_id_action.rb deleted file mode 100644 index 3dd0e1516d6..00000000000 --- a/app/services/idv/actions/in_person/cancel_update_state_id_action.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module Idv - module Actions - module InPerson - class CancelUpdateStateIdAction < Idv::Steps::DocAuthBaseStep - def self.analytics_submitted_event - :idv_in_person_proofing_cancel_update_state_id - end - - def call - mark_step_complete(:state_id) if flow_session.dig(:pii_from_user, :first_name) - redirect_to idv_in_person_verify_info_url - end - end - end - end -end diff --git a/app/services/idv/actions/in_person/redo_state_id_action.rb b/app/services/idv/actions/in_person/redo_state_id_action.rb deleted file mode 100644 index 59d4337e197..00000000000 --- a/app/services/idv/actions/in_person/redo_state_id_action.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module Idv - module Actions - module InPerson - class RedoStateIdAction < Idv::Steps::DocAuthBaseStep - def self.analytics_submitted_event - :idv_in_person_proofing_redo_state_id_submitted - end - - def call - mark_step_incomplete(:state_id) - end - end - end - end -end diff --git a/app/services/idv/analytics_events_enhancer.rb b/app/services/idv/analytics_events_enhancer.rb index 1dff6c81e89..6f67339f580 100644 --- a/app/services/idv/analytics_events_enhancer.rb +++ b/app/services/idv/analytics_events_enhancer.rb @@ -54,12 +54,10 @@ module AnalyticsEventsEnhancer idv_in_person_prepare_submitted idv_in_person_prepare_visited idv_in_person_proofing_address_visited - idv_in_person_proofing_cancel_update_state_id idv_in_person_proofing_enrollments_ready_for_status_check_job_completed idv_in_person_proofing_enrollments_ready_for_status_check_job_ingestion_error idv_in_person_proofing_enrollments_ready_for_status_check_job_started idv_in_person_proofing_nontransliterable_characters_submitted - idv_in_person_proofing_redo_state_id_submitted idv_in_person_proofing_residential_address_submitted idv_in_person_proofing_state_id_submitted idv_in_person_proofing_state_id_visited diff --git a/app/services/idv/flows/in_person_flow.rb b/app/services/idv/flows/in_person_flow.rb index 823dfc7c891..1c393f4465f 100644 --- a/app/services/idv/flows/in_person_flow.rb +++ b/app/services/idv/flows/in_person_flow.rb @@ -5,15 +5,6 @@ module Flows class InPersonFlow < Flow::BaseFlow attr_reader :idv_session # this is used by DocAuthBaseStep - STEPS = { - state_id: Idv::Steps::InPerson::StateIdStep, # info from state id - }.freeze - - ACTIONS = { - cancel_update_state_id: Idv::Actions::InPerson::CancelUpdateStateIdAction, - redo_state_id: Idv::Actions::InPerson::RedoStateIdAction, - }.freeze - STEP_INDICATOR_STEPS = [ { name: :find_a_post_office }, { name: :verify_info }, @@ -32,7 +23,7 @@ class InPersonFlow < Flow::BaseFlow def initialize(controller, session, name) @idv_session = self.class.session_idv(session) - super(controller, STEPS, ACTIONS, session[name]) + super(controller, session[name]) @flow_session ||= {} @flow_session[:pii_from_user] ||= { uuid: current_user.uuid } # there may be data in @idv_session to copy to @flow_session diff --git a/app/services/idv/session.rb b/app/services/idv/session.rb index bf52fb4b991..ea2db47c62c 100644 --- a/app/services/idv/session.rb +++ b/app/services/idv/session.rb @@ -222,8 +222,6 @@ def has_pii_from_user_in_flow_session? def invalidate_in_person_pii_from_user! if has_pii_from_user_in_flow_session? user_session['idv/in_person'][:pii_from_user] = nil - # Mark the FSM step as incomplete so that it can be re-entered. - user_session['idv/in_person'].delete('Idv::Steps::InPerson::StateIdStep') end end diff --git a/app/services/idv/steps/in_person/state_id_step.rb b/app/services/idv/steps/in_person/state_id_step.rb deleted file mode 100644 index 23a2f15fbf8..00000000000 --- a/app/services/idv/steps/in_person/state_id_step.rb +++ /dev/null @@ -1,127 +0,0 @@ -# frozen_string_literal: true - -module Idv - module Steps - module InPerson - class StateIdStep < DocAuthBaseStep - STEP_INDICATOR_STEP = :verify_info - - def self.analytics_visited_event - :idv_in_person_proofing_state_id_visited - end - - def self.analytics_submitted_event - :idv_in_person_proofing_state_id_submitted - end - - def call - pii_from_user = flow_session[:pii_from_user] - initial_state_of_same_address_as_id = flow_session[:pii_from_user][:same_address_as_id] - Idv::StateIdForm::ATTRIBUTES.each do |attr| - flow_session[:pii_from_user][attr] = flow_params[attr] - end - # Accept Date of Birth from both memorable date and input date components - formatted_dob = MemorableDateComponent.extract_date_param flow_params&.[](:dob) - pii_from_user[:dob] = formatted_dob if formatted_dob - - if pii_from_user[:same_address_as_id] == 'true' - copy_state_id_address_to_residential_address(pii_from_user) - redirect_to idv_in_person_ssn_url - end - - if initial_state_of_same_address_as_id == 'true' && - pii_from_user[:same_address_as_id] == 'false' - clear_residential_address(pii_from_user) - end - - redirect_to idv_in_person_verify_info_url if updating_state_id? - - if pii_from_user[:same_address_as_id] == 'false' - redirect_to idv_in_person_address_url - end - end - - def extra_view_variables - { - form:, - pii:, - parsed_dob:, - updating_state_id: updating_state_id?, - } - end - - private - - def clear_residential_address(pii_from_user) - pii_from_user.delete(:address1) - pii_from_user.delete(:address2) - pii_from_user.delete(:city) - pii_from_user.delete(:state) - pii_from_user.delete(:zipcode) - end - - def copy_state_id_address_to_residential_address(pii_from_user) - pii_from_user[:address1] = flow_params[:identity_doc_address1] - pii_from_user[:address2] = flow_params[:identity_doc_address2] - pii_from_user[:city] = flow_params[:identity_doc_city] - pii_from_user[:state] = flow_params[:identity_doc_address_state] - pii_from_user[:zipcode] = flow_params[:identity_doc_zipcode] - end - - def updating_state_id? - flow_session[:pii_from_user].has_key?(:first_name) - end - - def parsed_dob - form_dob = pii[:dob] - if form_dob.instance_of?(String) - dob_str = form_dob - elsif form_dob.instance_of?(Hash) - dob_str = MemorableDateComponent.extract_date_param(form_dob) - end - Date.parse(dob_str) unless dob_str.nil? - rescue StandardError - # Catch date parsing errors - end - - def pii - data = flow_session[:pii_from_user] - if params.has_key?(:identity_doc) || params.has_key?(:state_id) - data = data.merge(flow_params) - end - data.deep_symbolize_keys - end - - def flow_params - if params.dig(:identity_doc).present? - # Transform the top-level params key to accept the renamed form - # for autofill handling workaround - params[:state_id] = params.delete(:identity_doc) - - # Rename nested id_number to state_id_number - if params[:state_id][:id_number].present? - params[:state_id][:state_id_number] = params[:state_id].delete(:id_number) - end - end - - params.require(:state_id).permit( - *Idv::StateIdForm::ATTRIBUTES, - dob: [ - :month, - :day, - :year, - ], - ) - end - - def form - @form ||= Idv::StateIdForm.new(current_user) - end - - def form_submit - form.submit(flow_params) - end - end - end - end -end diff --git a/app/views/idv/in_person/state_id.html.erb b/app/views/idv/in_person/state_id.html.erb deleted file mode 100644 index 5c414de16bd..00000000000 --- a/app/views/idv/in_person/state_id.html.erb +++ /dev/null @@ -1,244 +0,0 @@ -<% self.title = t('titles.doc_auth.verify') %> - -<% 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_milestone_2')) %> -<% end %> - -

- <%= t('in_person_proofing.body.state_id.info_html') %> -

- -<%= render AlertComponent.new( - type: :info, - class: 'margin-bottom-4', - text_tag: 'div', - ) do %> - <%= t('in_person_proofing.body.state_id.alert_message') %> - -

- <%= t('in_person_proofing.body.state_id.questions') %> - <%= link_to( - help_center_redirect_url( - category: 'verify-your-identity', - article: 'accepted-identification-documents', - ), - class: 'display-inline', - ) do %> - <%= t('in_person_proofing.body.state_id.learn_more_link') %> - <% end %> -

-<% end %> - -<%= simple_form_for form, - as: 'identity_doc', # Renaming form as a workaround for aggressive browser autofill assumptions - url: url_for, - method: 'put', - html: { class: 'margin-y-5' } do |f| %> - -
- <%= render ValidatedFieldComponent.new( - name: :first_name, - form: f, - input_html: { value: pii[:first_name] }, - label: t('in_person_proofing.form.state_id.first_name'), - label_html: { class: 'usa-label' }, - maxlength: 255, - required: true, - ) %> -
- -
- <%= render ValidatedFieldComponent.new( - name: :last_name, - form: f, - input_html: { value: pii[:last_name] }, - label: t('in_person_proofing.form.state_id.last_name'), - label_html: { class: 'usa-label' }, - maxlength: 255, - required: true, - ) %> -
- -
- <%= render MemorableDateComponent.new( - content_tag: 'memorable-date', - name: :dob, - day: parsed_dob&.day, - month: parsed_dob&.month, - year: parsed_dob&.year, - required: true, - min: '1900-01-01', - max: Time.zone.today, - hint: t('in_person_proofing.form.state_id.dob_hint'), - label: t('in_person_proofing.form.state_id.dob'), - form: f, - error_messages: { - missing_month_day_year: t('in_person_proofing.form.state_id.memorable_date.errors.date_of_birth.missing_month_day_year'), - range_overflow: t('in_person_proofing.form.state_id.memorable_date.errors.date_of_birth.range_overflow'), - }, - range_errors: [ - { - max: Time.zone.today - 13.years, - message: t( - 'in_person_proofing.form.state_id.memorable_date.errors.date_of_birth.range_min_age', - app_name: APP_NAME, - ), - }, - ], - ) - %> -
- -
- <%= render ValidatedFieldComponent.new( - name: :state_id_jurisdiction, - collection: @presenter.usps_states_territories, - form: f, - hint: t('in_person_proofing.form.state_id.state_id_jurisdiction_hint'), - input_html: { class: 'jurisdiction-state-selector' }, - label: t('in_person_proofing.form.state_id.state_id_jurisdiction'), - label_html: { class: 'usa-label' }, - prompt: t('in_person_proofing.form.state_id.state_id_jurisdiction_prompt'), - required: true, - selected: pii[:state_id_jurisdiction], - ) %> -
-
- <% state_id_number_hint_default = capture do %> - <%= t('in_person_proofing.form.state_id.state_id_number_hint') %> - <% [ - [t('in_person_proofing.form.state_id.state_id_number_hint_spaces'), ' '], - [t('in_person_proofing.form.state_id.state_id_number_hint_forward_slashes'), '/'], - [t('in_person_proofing.form.state_id.state_id_number_hint_asterisks'), '*'], - [t('in_person_proofing.form.state_id.state_id_number_hint_dashes'), '-', true], - ].each do |text, symbol, last| %> - <%= text %><%= ',' if !last %> - - <% end %> - <% end %> - - <% state_id_number_hint = capture do %> - <% [ - [:default, state_id_number_hint_default], - ['FL', t('in_person_proofing.form.state_id.state_id_number_florida_hint_html')], - ['TX', t('in_person_proofing.form.state_id.state_id_number_texas_hint')], - ].each do |state, hint| %> - <%= content_tag( - :span, - hint, - class: state == :default ? nil : 'display-none', - data: { state: }, - ) %> - <% end %> - <% end %> - - <%= render ValidatedFieldComponent.new( - name: :id_number, # Renaming field as a workaround for aggressive browser autofill assumptions - form: f, - hint: state_id_number_hint, - hint_html: { class: ['jurisdiction-extras'] }, - input_html: { value: pii[:state_id_number] }, - label: t('in_person_proofing.form.state_id.state_id_number'), - label_html: { class: 'usa-label' }, - maxlength: 255, - required: true, - ) %> -
- -

<%= t('in_person_proofing.headings.id_address') %>

- <%= render ValidatedFieldComponent.new( - name: :identity_doc_address_state, - collection: @presenter.usps_states_territories, - form: f, - input_html: { class: 'address-state-selector' }, - label: t('in_person_proofing.form.state_id.identity_doc_address_state'), - label_html: { class: 'usa-label' }, - prompt: t('in_person_proofing.form.state_id.identity_doc_address_state_prompt'), - required: true, - selected: pii[:identity_doc_address_state], - ) %> - <%= render ValidatedFieldComponent.new( - name: :identity_doc_address1, - form: f, - hint_html: { class: ['display-none', 'puerto-rico-extras'] }, - hint: t('in_person_proofing.form.state_id.address1_hint'), - input_html: { value: pii[:identity_doc_address1] }, - label: t('in_person_proofing.form.state_id.address1'), - label_html: { class: 'usa-label' }, - maxlength: 255, - required: true, - ) %> - - <%= render ValidatedFieldComponent.new( - name: :identity_doc_address2, - form: f, - hint: t('in_person_proofing.form.state_id.address2_hint'), - hint_html: { class: ['display-none', 'puerto-rico-extras'] }, - input_html: { value: pii[:identity_doc_address2] }, - label: t('in_person_proofing.form.state_id.address2'), - label_html: { class: 'usa-label' }, - maxlength: 255, - required: false, - ) %> - - <%= render ValidatedFieldComponent.new( - name: :identity_doc_city, - form: f, - input_html: { value: pii[:identity_doc_city] }, - label: t('in_person_proofing.form.state_id.city'), - label_html: { class: 'usa-label' }, - maxlength: 255, - required: true, - ) %> -
- <%# using :tel for mobile numeric keypad %> - <%= render ValidatedFieldComponent.new( - as: :tel, - error_messages: { patternMismatch: t('idv.errors.pattern_mismatch.zipcode') }, - form: f, - input_html: { value: pii[:identity_doc_zipcode], class: 'zipcode' }, - label: t('in_person_proofing.form.state_id.zipcode'), - label_html: { class: 'usa-label' }, - name: :identity_doc_zipcode, - pattern: '\d{5}([\-]\d{4})?', - 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'), - legend_html: { class: 'h2' }, - name: :same_address_as_id, - required: true, - wrapper: :uswds_radio_buttons, - ) %> - - <%= f.submit do %> - <% if updating_state_id %> - <%= t('forms.buttons.submit.update') %> - <% else %> - <%= t('forms.buttons.continue') %> - <% end %> - <% end %> -<% end %> - -<% if updating_state_id %> - <%= render 'idv/shared/back', action: 'cancel_update_state_id' %> -<% else %> - <%= render 'idv/doc_auth/cancel', step: 'state_id' %> -<% end %> -<%= javascript_packs_tag_once('formatted-fields', 'state-guidance') %> diff --git a/app/views/idv/in_person/verify_info/show.html.erb b/app/views/idv/in_person/verify_info/show.html.erb index 5ce62008459..baf8157e7d9 100644 --- a/app/views/idv/in_person/verify_info/show.html.erb +++ b/app/views/idv/in_person/verify_info/show.html.erb @@ -72,9 +72,8 @@ locals:
- <%= button_to( - idv_in_person_step_url(step: :redo_state_id), - method: :put, + <%= link_to( + idv_in_person_proofing_state_id_url, class: 'usa-button usa-button--unstyled padding-y-1', 'aria-label': t('idv.buttons.change_state_id_label'), ) { t('idv.buttons.change_label') } %> diff --git a/app/views/two_factor_authentication/options/index.html.erb b/app/views/two_factor_authentication/options/index.html.erb index d17c9914614..9a55980a0ee 100644 --- a/app/views/two_factor_authentication/options/index.html.erb +++ b/app/views/two_factor_authentication/options/index.html.erb @@ -2,6 +2,13 @@ <%= render(VendorOutageAlertComponent.new(vendors: [:sms, :voice])) %> +<% if @presenter.add_piv_cac_after_2fa? %> + <%= render AlertComponent.new( + type: :info, + class: 'margin-bottom-4', + ).with_content(t('two_factor_authentication.piv_cac_mismatch.2fa_before_add')) %> +<% end %> + <%= render PageHeadingComponent.new.with_content(@presenter.heading) %>

diff --git a/app/views/two_factor_authentication/piv_cac_mismatch/show.html.erb b/app/views/two_factor_authentication/piv_cac_mismatch/show.html.erb new file mode 100644 index 00000000000..eee6ddd11f6 --- /dev/null +++ b/app/views/two_factor_authentication/piv_cac_mismatch/show.html.erb @@ -0,0 +1,38 @@ +<% self.title = t('two_factor_authentication.piv_cac_mismatch.title') %> + +<%= render PageHeadingComponent.new.with_content(t('two_factor_authentication.piv_cac_mismatch.title')) %> + +<% if @has_other_authentication_methods %> +

<%= t('two_factor_authentication.piv_cac_mismatch.instructions') %>

+ + <%= render ButtonComponent.new( + url: login_two_factor_piv_cac_mismatch_url, + method: :post, + params: { add_piv_cac_after_2fa: 'true' }, + big: true, + wide: true, + class: 'display-block margin-top-5', + ).with_content(t('two_factor_authentication.piv_cac_mismatch.cta')) %> + + <% if !@piv_cac_required %> + <%= render ButtonComponent.new( + url: login_two_factor_piv_cac_mismatch_url, + method: :post, + unstyled: true, + class: 'display-block margin-top-2', + ).with_content(t('two_factor_authentication.piv_cac_mismatch.skip')) %> + <% end %> +<% else %> +

<%= t('two_factor_authentication.piv_cac_mismatch.instructions_no_other_method', app_name: APP_NAME) %>

+ + <%= render ButtonComponent.new( + url: account_reset_recovery_options_url, + big: true, + wide: true, + class: 'display-inline-block margin-top-3', + ).with_content(t('two_factor_authentication.piv_cac_mismatch.delete_account')) %> +<% end %> + +<%= render PageFooterComponent.new do %> + <%= link_to t('links.cancel'), sign_out_url %> +<% end %> diff --git a/app/views/users/piv_cac_authentication_setup/new.html.erb b/app/views/users/piv_cac_authentication_setup/new.html.erb index 172c77960dd..1807862381e 100644 --- a/app/views/users/piv_cac_authentication_setup/new.html.erb +++ b/app/views/users/piv_cac_authentication_setup/new.html.erb @@ -31,8 +31,22 @@ <% end %> <% end %> - <%= f.submit t('forms.piv_cac_setup.submit'), class: 'display-block margin-y-5' %> + <%= f.submit t('forms.piv_cac_setup.submit'), class: 'display-block margin-top-5 margin-bottom-2' %> <% end %> +<% if user_session[:add_piv_cac_after_2fa] && !@piv_cac_required %> + <%= render ButtonComponent.new( + url: submit_new_piv_cac_url, + method: :post, + params: { skip: 'true' }, + unstyled: true, + ).with_content(t('mfa.skip')) %> +<% end %> -<%= render 'shared/cancel_or_back_to_options' %> +<% if user_session[:add_piv_cac_after_2fa] %> + <%= render PageFooterComponent.new do %> + <%= link_to t('links.cancel'), sign_out_path %> + <% end %> +<% else %> + <%= render 'shared/cancel_or_back_to_options' %> +<% end %> diff --git a/config/application.rb b/config/application.rb index 5b544fd382d..64f212ce46f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -59,7 +59,7 @@ class Application < Rails::Application end end - config.load_defaults '7.1' + config.load_defaults '7.2' config.active_record.belongs_to_required_by_default = false config.active_job.queue_adapter = :good_job @@ -157,9 +157,7 @@ class Application < Rails::Application allow do origins IdentityCors.allowed_origins_static_sites resource '/api/country-support', headers: :any, methods: [:get] - if Identity::Hostdata.config.in_person_public_address_search_enabled - resource '/api/usps_locations', headers: :any, methods: %i[post options] - end + resource '/api/usps_locations', headers: :any, methods: %i[post options] end end diff --git a/config/application.yml.default b/config/application.yml.default index 5ef389edbf4..ada79aca234 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -184,10 +184,8 @@ in_person_outage_message_enabled: false in_person_proofing_enabled: false in_person_proofing_enforce_tmx: false in_person_proofing_opt_in_enabled: false -in_person_public_address_search_enabled: false in_person_results_delay_in_hours: 1 in_person_send_proofing_notifications_enabled: false -in_person_state_id_controller_enabled: false in_person_stop_expiring_enrollments: false invalid_gpo_confirmation_zipcode: '00001' # LexisNexis ##################################################### @@ -543,7 +541,6 @@ test: hmac_fingerprinter_key: a2c813d4dca919340866ba58063e4072adc459b767a74cf2666d5c1eef3861db26708e7437abde1755eb24f4034386b0fea1850a1cb7e56bff8fae3cc6ade96c hmac_fingerprinter_key_queue: '["old-key-one", "old-key-two"]' identity_pki_disabled: true - in_person_state_id_controller_enabled: true lexisnexis_trueid_account_id: 'test_account' lockout_period_in_minutes: 5 logins_per_email_and_ip_limit: 2 diff --git a/config/environments/production.rb b/config/environments/production.rb index 5097c9f195f..35df3e3e9d2 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -27,7 +27,7 @@ if IdentityConfig.store.rails_mailer_previews_enabled config.action_mailer.show_previews = true - config.action_mailer.preview_path = Rails.root.join('spec/mailers/previews') + config.action_mailer.preview_paths = [Rails.root.join('spec/mailers/previews')] end routes.default_url_options[:protocol] = :https diff --git a/config/locales/en.yml b/config/locales/en.yml index ebd7685fc38..c4e56d5d3c2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1716,6 +1716,13 @@ two_factor_authentication.phone_verification.troubleshooting.code_not_received: two_factor_authentication.phone.delete.failure: Unable to remove your phone. two_factor_authentication.phone.delete.success: Your phone has been removed. two_factor_authentication.piv_cac_header_text: Insert your government employee ID +two_factor_authentication.piv_cac_mismatch.2fa_before_add: You need to authenticate with another method before adding your PIV/CAC. +two_factor_authentication.piv_cac_mismatch.cta: Authenticate and add PIV/CAC +two_factor_authentication.piv_cac_mismatch.delete_account: Delete your account +two_factor_authentication.piv_cac_mismatch.instructions: Click “Authenticate and add PIV/CAC” below to authenticate with another method before adding this PIV/CAC to your account. +two_factor_authentication.piv_cac_mismatch.instructions_no_other_method: If you were reissued your PIV/CAC, you will need to delete your %{app_name} account and create a new account to use your reissued PIV/CAC. +two_factor_authentication.piv_cac_mismatch.skip: Skip adding PIV/CAC +two_factor_authentication.piv_cac_mismatch.title: This government employee ID is not connected to your account two_factor_authentication.piv_cac_upsell.add_piv: Add PIV/CAC card two_factor_authentication.piv_cac_upsell.choose_other_method: Choose other methods instead two_factor_authentication.piv_cac_upsell.explain: This will improve your account security and let you skip entering your email and password when signing in. diff --git a/config/locales/es.yml b/config/locales/es.yml index 3497461ffe1..5ab154e9ebd 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1728,6 +1728,13 @@ two_factor_authentication.phone_verification.troubleshooting.code_not_received: two_factor_authentication.phone.delete.failure: No se puede eliminar su teléfono. two_factor_authentication.phone.delete.success: Su teléfono fue eliminado. two_factor_authentication.piv_cac_header_text: Inserte su identificación de empleado del gobierno +two_factor_authentication.piv_cac_mismatch.2fa_before_add: Debe realizar la autenticación con otro método antes de añadir su tarjeta PIV o CAC. +two_factor_authentication.piv_cac_mismatch.cta: Autenticar y añadir tarjeta PIV o CAC +two_factor_authentication.piv_cac_mismatch.delete_account: Eliminar su cuenta +two_factor_authentication.piv_cac_mismatch.instructions: Haga clic en “Autenticar y añadir tarjeta PIV o CAC” más abajo para autenticar con otro método antes de añadir esta tarjeta PIV o CAC a su cuenta. +two_factor_authentication.piv_cac_mismatch.instructions_no_other_method: Si se le emitió una nueva tarjeta PIV o CAC, para poder usarla, deberá eliminar su cuenta de %{app_name} y crear una cuenta nueva. +two_factor_authentication.piv_cac_mismatch.skip: Saltar añadir tarjeta PIV o CAC +two_factor_authentication.piv_cac_mismatch.title: Esta tarjeta de identificación de empleado del gobierno no está conectada a su cuenta two_factor_authentication.piv_cac_upsell.add_piv: Agregar tarjeta PIV/CAC two_factor_authentication.piv_cac_upsell.choose_other_method: Elegir otros métodos two_factor_authentication.piv_cac_upsell.explain: Esto hará que su cuenta sea más segura y no tendrá que ingresar su correo electrónico ni su contraseña cuando inicie sesión. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index e49b209a072..1c7fd9cb6e1 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1716,6 +1716,13 @@ two_factor_authentication.phone_verification.troubleshooting.code_not_received: two_factor_authentication.phone.delete.failure: Impossible de supprimer votre téléphone. two_factor_authentication.phone.delete.success: Votre téléphone a été supprimé. two_factor_authentication.piv_cac_header_text: Insérer votre carte d’employé fédéral +two_factor_authentication.piv_cac_mismatch.2fa_before_add: Vous devez vous authentifier à l’aide d’une autre méthode avant d’ajouter votre carte PIV/CAC. +two_factor_authentication.piv_cac_mismatch.cta: S’authentifier et ajouter une carte PIV/CAC +two_factor_authentication.piv_cac_mismatch.delete_account: Supprimer votre compte +two_factor_authentication.piv_cac_mismatch.instructions: Cliquez sur « S’authentifier et ajouter une carte PIV/CAC » ci-dessous pour vous authentifier au moyen d’une autre méthode avant d’ajouter cette carte PIV/CAC à votre compte. +two_factor_authentication.piv_cac_mismatch.instructions_no_other_method: Si l’on vous a à nouveau délivré une carte PIV/CAC, vous devez supprimer votre compte %{app_name} et en créer un nouveau pour l’utiliser. +two_factor_authentication.piv_cac_mismatch.skip: Sauter l’ajout de carte PIV/CAC +two_factor_authentication.piv_cac_mismatch.title: Cette carte d’employé fédéral n’est pas associée à votre compte. two_factor_authentication.piv_cac_upsell.add_piv: Ajouter une carte PIV/CAC two_factor_authentication.piv_cac_upsell.choose_other_method: Choisir plutôt d’autres méthodes two_factor_authentication.piv_cac_upsell.explain: Ceci permettra de renforcer la sécurité de votre compte et de sauter l’étape de saisie de votre e-mail et mot de passe quand vous vous connecterez. diff --git a/config/locales/zh.yml b/config/locales/zh.yml index bcc3a9451e0..d7e911f2dc5 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -1729,6 +1729,13 @@ two_factor_authentication.phone_verification.troubleshooting.code_not_received: two_factor_authentication.phone.delete.failure: 无法去掉你的电话。 two_factor_authentication.phone.delete.success: 你的电话已被去掉。 two_factor_authentication.piv_cac_header_text: 插入您的政府雇员ID +two_factor_authentication.piv_cac_mismatch.2fa_before_add: 添加你的PIV/CAC之前,你需要使用另外一种方法进行身份证实。 +two_factor_authentication.piv_cac_mismatch.cta: 进行身份证实并添加PIV/CAC +two_factor_authentication.piv_cac_mismatch.delete_account: 删除你的帐户 +two_factor_authentication.piv_cac_mismatch.instructions: 点击下边的“进行身份证实并添加PIV/CAC”,以在将该PIV/CAC添加到你账户之前使用另外一种方法进行身份证实。 +two_factor_authentication.piv_cac_mismatch.instructions_no_other_method: 如果你的PIV/CAV是重新颁发的,你需要删除自己的%{app_name}帐户,并使用重新颁发的PIV/CAC 设立一个新账户。 +two_factor_authentication.piv_cac_mismatch.skip: 跳过添加 PIV/CAC +two_factor_authentication.piv_cac_mismatch.title: 该政府雇员身份证件与你的账户没有连接起来 two_factor_authentication.piv_cac_upsell.add_piv: 添加 PIV/CAC 卡 two_factor_authentication.piv_cac_upsell.choose_other_method: 选择其他方法 two_factor_authentication.piv_cac_upsell.explain: 这将改善你账户安全,而且你在登录时无需再输入电邮和密码。 diff --git a/config/routes.rb b/config/routes.rb index 6952004af90..0a903eab6c9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -119,6 +119,9 @@ get '/login/two_factor/options' => 'two_factor_authentication/options#index' post '/login/two_factor/options' => 'two_factor_authentication/options#create' + get '/login/two_factor/piv_cac_mismatch' => 'two_factor_authentication/piv_cac_mismatch#show' + post '/login/two_factor/piv_cac_mismatch' => 'two_factor_authentication/piv_cac_mismatch#create' + get '/login/two_factor/authenticator' => 'two_factor_authentication/totp_verification#show' post '/login/two_factor/authenticator' => 'two_factor_authentication/totp_verification#create' get '/login/two_factor/personal_key' => 'two_factor_authentication/personal_key_verification#show' diff --git a/db/primary_migrate/20241023191918_add_socure_docv_capture_app_url_to_document_capture_sessions_w_comment.rb b/db/primary_migrate/20241023191918_add_socure_docv_capture_app_url_to_document_capture_sessions_w_comment.rb new file mode 100644 index 00000000000..6fe9739d4af --- /dev/null +++ b/db/primary_migrate/20241023191918_add_socure_docv_capture_app_url_to_document_capture_sessions_w_comment.rb @@ -0,0 +1,5 @@ +class AddSocureDocvCaptureAppUrlToDocumentCaptureSessionsWComment < ActiveRecord::Migration[7.2] + def change + add_column :document_capture_sessions, :socure_docv_capture_app_url, :string, comment: 'sensitive=false' + end +end diff --git a/db/schema.rb b/db/schema.rb index a20b704ba51..cbb37d407b2 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.1].define(version: 2024_10_17_153042) do +ActiveRecord::Schema[7.2].define(version: 2024_10_23_191918) do # These are extensions that must be enabled in order to support this database enable_extension "citext" enable_extension "pg_stat_statements" @@ -192,6 +192,7 @@ t.boolean "ocr_confirmation_pending", default: false, comment: "sensitive=false" t.string "last_doc_auth_result", comment: "sensitive=false" t.string "socure_docv_transaction_token", comment: "sensitive=false" + t.string "socure_docv_capture_app_url", comment: "sensitive=false" t.index ["result_id"], name: "index_document_capture_sessions_on_result_id" t.index ["user_id"], name: "index_document_capture_sessions_on_user_id" t.index ["uuid"], name: "index_document_capture_sessions_on_uuid" diff --git a/db/worker_jobs_schema.rb b/db/worker_jobs_schema.rb index 3398c6bca59..3e6fa136f59 100644 --- a/db/worker_jobs_schema.rb +++ b/db/worker_jobs_schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_10_21_192437) do +ActiveRecord::Schema[7.2].define(version: 2024_10_21_192437) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -102,5 +102,4 @@ t.index ["queue_name", "scheduled_at"], name: "index_good_jobs_on_queue_name_and_scheduled_at", where: "(finished_at IS NULL)" t.index ["scheduled_at"], name: "index_good_jobs_on_scheduled_at", where: "(finished_at IS NULL)" end - end diff --git a/docs/frontend.md b/docs/frontend.md index 48dd9a01142..80babf8ff59 100644 --- a/docs/frontend.md +++ b/docs/frontend.md @@ -233,6 +233,10 @@ For example, consider a **Password Input** component: - A web component would be named `PasswordInputElement` - A web components file would be named `app/javascript/packages/password-input/password-input-element.ts` +#### Graphical Assets + +Web graphic assets like images, GIFs, and videos are artifacts authored in other tools. As such, there is no need to keep multiple variants of an asset (e.g., SVG and PNG) in the repository if they are not in use. + ## Testing ### Stylelint diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 6e0b04b2a07..b64e7e5fa77 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -199,10 +199,8 @@ def self.store config.add(:in_person_proofing_enabled, type: :boolean) config.add(:in_person_proofing_enforce_tmx, type: :boolean) config.add(:in_person_proofing_opt_in_enabled, type: :boolean) - config.add(:in_person_public_address_search_enabled, type: :boolean) config.add(:in_person_results_delay_in_hours, type: :integer) config.add(:in_person_send_proofing_notifications_enabled, type: :boolean) - config.add(:in_person_state_id_controller_enabled, type: :boolean) config.add(:in_person_stop_expiring_enrollments, type: :boolean) config.add(:invalid_gpo_confirmation_zipcode, type: :string) config.add(:lexisnexis_account_id, type: :string) diff --git a/lib/reporting/identity_verification_report.rb b/lib/reporting/identity_verification_report.rb index 8ee8fbb3e77..089a9d8f27a 100644 --- a/lib/reporting/identity_verification_report.rb +++ b/lib/reporting/identity_verification_report.rb @@ -53,6 +53,17 @@ module Results # rubocop:enable Layout/LineLength end + # Because historically fraud-related events were not tagged with SP data, + # we need pull these out-of-band events *even if* the are marked as + # pending fraud review. This allows us to attribute untagged fraud-related + # events (by matching on user_id). We filter these events for counting + # purposes, though. + EVENTS_TO_IGNORE_IF_FRAUD_REVIEW_PENDING = [ + Events::GPO_VERIFICATION_SUBMITTED, + Events::GPO_VERIFICATION_SUBMITTED_OLD, + Events::USPS_ENROLLMENT_STATUS_UPDATED, + ].to_set.freeze + # @param [Array] issuers # @param [Range