diff --git a/app/assets/stylesheets/_uswds.scss b/app/assets/stylesheets/_uswds.scss index 94e4c5ca8be..2742d09a988 100644 --- a/app/assets/stylesheets/_uswds.scss +++ b/app/assets/stylesheets/_uswds.scss @@ -14,4 +14,3 @@ @forward 'usa-skipnav'; @forward 'usa-tag'; @forward 'uswds-form-controls'; -@forward 'uswds-utilities'; diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index f77d6eccc9f..6015355f074 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -3,4 +3,5 @@ @forward 'uswds'; @forward 'design-system-waiting-room'; @forward 'components'; +@forward 'uswds-utilities'; @forward 'utilities'; diff --git a/app/assets/stylesheets/components/_profile-section.scss b/app/assets/stylesheets/components/_profile-section.scss index 497c10edf9e..9604d5bedb7 100644 --- a/app/assets/stylesheets/components/_profile-section.scss +++ b/app/assets/stylesheets/components/_profile-section.scss @@ -1,17 +1,5 @@ @use 'uswds-core' as *; -@use '../variables/app' as *; .profile-info-box { - border: 0; - border-radius: 0; - margin-bottom: 0; - overflow: hidden; padding: units(4); } - -@include at-media('mobile') { - .profile-info-box { - border-radius: $border-radius-md; - margin-bottom: units(4); - } -} diff --git a/app/assets/stylesheets/variables/_app.scss b/app/assets/stylesheets/variables/_app.scss index 37d4c0909b5..3249beb3be3 100644 --- a/app/assets/stylesheets/variables/_app.scss +++ b/app/assets/stylesheets/variables/_app.scss @@ -14,7 +14,6 @@ $sm-h4: 1rem !default; $sm-h5: 0.875rem !default; $sm-h6: 0.75rem !default; -$border-radius-md: 6px !default; $border-radius-xl: 16px !default; $container-skinny-width: 620px !default; diff --git a/app/components/countdown_alert_component.rb b/app/components/countdown_alert_component.rb index 2b2fcb57652..8e70d508d73 100644 --- a/app/components/countdown_alert_component.rb +++ b/app/components/countdown_alert_component.rb @@ -1,20 +1,18 @@ # frozen_string_literal: true class CountdownAlertComponent < BaseComponent - attr_reader :show_at_remaining, :alert_options, :countdown_options, :redirect_url, :tag_options + attr_reader :show_at_remaining, :alert_options, :countdown_options, :tag_options def initialize( show_at_remaining: nil, alert_options: {}, countdown_options: {}, - redirect_url: nil, **tag_options ) @show_at_remaining = show_at_remaining @alert_options = alert_options @countdown_options = countdown_options @tag_options = tag_options - @redirect_url = redirect_url end def call @@ -24,7 +22,6 @@ def call **tag_options, class: css_class, 'show-at-remaining': show_at_remaining&.in_milliseconds, - 'redirect-url': redirect_url, ) end diff --git a/app/controllers/idv/how_to_verify_controller.rb b/app/controllers/idv/how_to_verify_controller.rb index a41f98da563..2c05bdef304 100644 --- a/app/controllers/idv/how_to_verify_controller.rb +++ b/app/controllers/idv/how_to_verify_controller.rb @@ -12,19 +12,15 @@ class HowToVerifyController < ApplicationController check_or_render_not_found -> { self.class.enabled? } def show - @selection = if idv_session.skip_doc_auth == false - Idv::HowToVerifyForm::REMOTE - elsif idv_session.skip_doc_auth == true - Idv::HowToVerifyForm::IPP - end - analytics.idv_doc_auth_how_to_verify_visited(**analytics_arguments) - @idv_how_to_verify_form = Idv::HowToVerifyForm.new(selection: @selection) + @idv_how_to_verify_form = Idv::HowToVerifyForm.new end def update clear_future_steps! - result = Idv::HowToVerifyForm.new.submit(how_to_verify_form_params) + @idv_how_to_verify_form = Idv::HowToVerifyForm.new + result = @idv_how_to_verify_form.submit(how_to_verify_form_params) + if how_to_verify_form_params[:selection] == [] sendable_form_params = {} else @@ -48,10 +44,8 @@ def update idv_session.skip_doc_auth_from_how_to_verify = true redirect_to idv_document_capture_url end - else - flash[:error] = result.first_error_message - redirect_to idv_how_to_verify_url + render :show, locals: { error: result.first_error_message } end end diff --git a/app/controllers/two_factor_authentication/backup_code_verification_controller.rb b/app/controllers/two_factor_authentication/backup_code_verification_controller.rb index 10bcc18e140..7c5831edf57 100644 --- a/app/controllers/two_factor_authentication/backup_code_verification_controller.rb +++ b/app/controllers/two_factor_authentication/backup_code_verification_controller.rb @@ -22,9 +22,7 @@ def show def create @backup_code_form = BackupCodeVerificationForm.new(current_user) result = @backup_code_form.submit(backup_code_params) - analytics.track_mfa_submit_event( - result.to_h.merge(new_device: new_device?), - ) + analytics.multi_factor_auth(**result.to_h.merge(new_device: new_device?)) irs_attempts_api_tracker.mfa_login_backup_code(success: result.success?) handle_result(result) end diff --git a/app/controllers/two_factor_authentication/otp_verification_controller.rb b/app/controllers/two_factor_authentication/otp_verification_controller.rb index 3bf40093f41..f3ca89a228f 100644 --- a/app/controllers/two_factor_authentication/otp_verification_controller.rb +++ b/app/controllers/two_factor_authentication/otp_verification_controller.rb @@ -136,7 +136,7 @@ def post_analytics(result) properties = result.to_h.merge(analytics_properties, new_device: new_device?) analytics.multi_factor_auth_setup(**properties) if context == 'confirmation' - analytics.track_mfa_submit_event(properties) + analytics.multi_factor_auth(**properties) if UserSessionContext.reauthentication_context?(context) irs_attempts_api_tracker.mfa_login_phone_otp_submitted( diff --git a/app/controllers/two_factor_authentication/personal_key_verification_controller.rb b/app/controllers/two_factor_authentication/personal_key_verification_controller.rb index 85302e11991..af95dd27af2 100644 --- a/app/controllers/two_factor_authentication/personal_key_verification_controller.rb +++ b/app/controllers/two_factor_authentication/personal_key_verification_controller.rb @@ -32,7 +32,10 @@ def track_analytics(result) new_device: new_device?, ) - analytics.track_mfa_submit_event(analytics_hash) + analytics.multi_factor_auth( + **analytics_hash, + pii_like_keypaths: [[:errors, :personal_key], [:error_details, :personal_key]], + ) end def check_personal_key_enabled 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 56151472bfa..ccd783d158e 100644 --- a/app/controllers/two_factor_authentication/piv_cac_verification_controller.rb +++ b/app/controllers/two_factor_authentication/piv_cac_verification_controller.rb @@ -30,9 +30,7 @@ def redirect_to_piv_cac_service def process_token result = piv_cac_verification_form.submit - analytics.track_mfa_submit_event( - result.to_h.merge(analytics_properties), - ) + analytics.multi_factor_auth(**result.to_h.merge(analytics_properties)) irs_attempts_api_tracker.mfa_login_piv_cac( success: result.success?, subject_dn: piv_cac_verification_form.x509_dn, diff --git a/app/controllers/two_factor_authentication/totp_verification_controller.rb b/app/controllers/two_factor_authentication/totp_verification_controller.rb index 111c8a52c1b..28c50c48444 100644 --- a/app/controllers/two_factor_authentication/totp_verification_controller.rb +++ b/app/controllers/two_factor_authentication/totp_verification_controller.rb @@ -21,7 +21,7 @@ def show def create result = TotpVerificationForm.new(current_user, params.require(:code).strip).submit - analytics.track_mfa_submit_event(result.to_h.merge(new_device: new_device?)) + analytics.multi_factor_auth(**result.to_h.merge(new_device: new_device?)) irs_attempts_api_tracker.mfa_login_totp(success: result.success?) if result.success? diff --git a/app/controllers/two_factor_authentication/webauthn_verification_controller.rb b/app/controllers/two_factor_authentication/webauthn_verification_controller.rb index 458ebf650a8..75f8e6255a3 100644 --- a/app/controllers/two_factor_authentication/webauthn_verification_controller.rb +++ b/app/controllers/two_factor_authentication/webauthn_verification_controller.rb @@ -18,7 +18,7 @@ def show def confirm result = form.submit - analytics.track_mfa_submit_event( + analytics.multi_factor_auth( **result.to_h, **analytics_properties, multi_factor_auth_method_created_at: diff --git a/app/controllers/users/backup_code_setup_controller.rb b/app/controllers/users/backup_code_setup_controller.rb index 03580ac54cc..0de1097304b 100644 --- a/app/controllers/users/backup_code_setup_controller.rb +++ b/app/controllers/users/backup_code_setup_controller.rb @@ -80,16 +80,7 @@ def confirm_backup_codes; end private def validate_multi_mfa_selection - if IdentityConfig.store.backup_code_confirm_setup_screen_enabled - redirect_to backup_code_confirm_setup_url unless in_multi_mfa_selection_flow? - else - redirect_to root_url unless internal_referrer? - end - end - - def internal_referrer? - UserSessionContext.reauthentication_context?(context) || - session[:account_redirect_path] || in_multi_mfa_selection_flow? + redirect_to backup_code_confirm_setup_url unless in_multi_mfa_selection_flow? end def analytics_properties_for_visit diff --git a/app/controllers/users/two_factor_authentication_controller.rb b/app/controllers/users/two_factor_authentication_controller.rb index 09d9c825126..79bcb6135ac 100644 --- a/app/controllers/users/two_factor_authentication_controller.rb +++ b/app/controllers/users/two_factor_authentication_controller.rb @@ -321,9 +321,14 @@ def send_user_otp(method) end def otp_length - bucket = AbTests::IDV_TEN_DIGIT_OTP.bucket(current_user.uuid) - length = bucket == :ten_digit_otp ? 'ten' : 'six' - I18n.t("telephony.format_length.#{length}") + configured_length = TwoFactorAuthenticatable::DIRECT_OTP_LENGTH + if configured_length == 6 + I18n.t('telephony.format_length.six') + elsif configured_length == 10 + I18n.t('telephony.format_length.ten') + else + raise "Missing translation for OTP length: #{configured_length}" + end end def user_selected_default_number diff --git a/app/forms/idv/api_image_upload_form.rb b/app/forms/idv/api_image_upload_form.rb index 9dbf7778a09..cb97b7314da 100644 --- a/app/forms/idv/api_image_upload_form.rb +++ b/app/forms/idv/api_image_upload_form.rb @@ -342,6 +342,7 @@ def update_analytics(client_response:, vendor_request_time_in_ms:) add_costs(client_response) update_funnel(client_response) birth_year = client_response.pii_from_doc&.dob&.to_date&.year + zip_code = client_response.pii_from_doc&.zipcode&.to_s&.strip&.slice(0, 5) analytics.idv_doc_auth_submitted_image_upload_vendor( **client_response.to_h.merge( birth_year: birth_year, @@ -349,6 +350,7 @@ def update_analytics(client_response:, vendor_request_time_in_ms:) async: false, flow_path: params[:flow_path], vendor_request_time_in_ms: vendor_request_time_in_ms, + zip_code: zip_code, ).except(:classification_info). merge(acuant_sdk_upgrade_ab_test_data), ) diff --git a/app/forms/idv/how_to_verify_form.rb b/app/forms/idv/how_to_verify_form.rb index bb4eed1adb8..7178c44338e 100644 --- a/app/forms/idv/how_to_verify_form.rb +++ b/app/forms/idv/how_to_verify_form.rb @@ -9,8 +9,13 @@ class HowToVerifyForm attr_reader :selection - validates :selection, - presence: { message: proc { I18n.t('errors.doc_auth.how_to_verify_form') } } + validates :selection, presence: { + message: proc { I18n.t('errors.doc_auth.how_to_verify_form') }, + } + validates :selection, inclusion: { + in: [REMOTE, IPP], + message: proc { I18n.t('errors.doc_auth.how_to_verify_form') }, + } def initialize(selection: nil) @selection = selection diff --git a/app/javascript/packages/countdown/countdown-alert-element.spec.ts b/app/javascript/packages/countdown/countdown-alert-element.spec.ts index 0c252153f39..28a3dfd39e2 100644 --- a/app/javascript/packages/countdown/countdown-alert-element.spec.ts +++ b/app/javascript/packages/countdown/countdown-alert-element.spec.ts @@ -5,14 +5,9 @@ import './countdown-element'; describe('CountdownAlertElement', () => { const sandbox = useSandbox({ useFakeTimers: true }); - function createElement({ - showAtRemaining, - redirectURL, - }: { showAtRemaining?: number; redirectURL?: string } = {}) { + function createElement({ showAtRemaining }: { showAtRemaining?: number } = {}) { document.body.innerHTML = ` - +

@@ -46,13 +41,4 @@ describe('CountdownAlertElement', () => { expect(element.show).to.have.been.called(); }); }); - - it('redirects when time has expired', () => { - createElement({ redirectURL: '#teapot' }); - - sandbox.clock.tick(91000); - expect(window.location.hash).to.equal(''); - sandbox.clock.tick(1000); - expect(window.location.hash).to.equal('#teapot'); - }); }); diff --git a/app/javascript/packages/countdown/countdown-alert-element.ts b/app/javascript/packages/countdown/countdown-alert-element.ts index 7e72f151ea9..dcdf6479b96 100644 --- a/app/javascript/packages/countdown/countdown-alert-element.ts +++ b/app/javascript/packages/countdown/countdown-alert-element.ts @@ -1,4 +1,3 @@ -import { trackEvent } from '@18f/identity-analytics'; import type { CountdownElement } from './countdown-element'; export class CountdownAlertElement extends HTMLElement { @@ -6,20 +5,12 @@ export class CountdownAlertElement extends HTMLElement { if (this.showAtRemaining) { this.addEventListener('lg:countdown:tick', this.handleShowAtRemainingTick); } - - if (this.redirectURL) { - this.addEventListener('lg:countdown:tick', this.handleRedirectTick); - } } get showAtRemaining(): number | null { return Number(this.getAttribute('show-at-remaining')) || null; } - get redirectURL(): string | null { - return this.getAttribute('redirect-url') || null; - } - get countdown(): CountdownElement { return this.querySelector('lg-countdown')!; } @@ -31,18 +22,6 @@ export class CountdownAlertElement extends HTMLElement { } }; - handleRedirectTick = () => { - if (this.countdown.timeRemaining <= 0) { - trackEvent('Countdown timeout redirect', { - path: this.redirectURL, - expiration: this.countdown.expiration, - timeRemaining: this.countdown.timeRemaining, - }); - window.location.href = this.redirectURL!; - this.removeEventListener('lg:countdown:tick', this.handleRedirectTick); - } - }; - show() { this.classList.remove('display-none'); } diff --git a/app/javascript/packages/phone-input/package.json b/app/javascript/packages/phone-input/package.json index 530f2283179..e3c49b40102 100644 --- a/app/javascript/packages/phone-input/package.json +++ b/app/javascript/packages/phone-input/package.json @@ -4,7 +4,7 @@ "version": "1.0.0", "dependencies": { "intl-tel-input": "^17.0.19", - "libphonenumber-js": "^1.11.1" + "libphonenumber-js": "^1.11.2" }, "sideEffects": [ "./index.ts" diff --git a/app/models/user.rb b/app/models/user.rb index 2274e8dd6d9..843d990fc2b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -428,10 +428,6 @@ def has_devices? !recent_devices.empty? end - def new_device?(cookie_uuid:) - !cookie_uuid || !devices.exists?(cookie_uuid:) - end - def authenticated_device?(cookie_uuid:) return false if cookie_uuid.blank? devices.joins(:events).exists?(cookie_uuid:, events: { event_type: :sign_in_after_2fa }) diff --git a/app/services/analytics.rb b/app/services/analytics.rb index f8f415cbe2a..0f1ee156c45 100644 --- a/app/services/analytics.rb +++ b/app/services/analytics.rb @@ -52,13 +52,6 @@ def first_event_this_session? session[:first_event] end - def track_mfa_submit_event(attributes) - multi_factor_auth( - **attributes, - pii_like_keypaths: [[:errors, :personal_key], [:error_details, :personal_key]], - ) - end - def request_attributes attributes = { user_ip: request.remote_ip, diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 2317b33810f..a2e67ab2aef 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -1127,6 +1127,7 @@ def idv_doc_auth_submitted_image_upload_form( # @param [Hash] portrait_match_results # @param [Hash] image_metrics # @param [Boolean] address_line2_present + # @param [String] zip_code # @option extra [String] 'DocumentName' # @option extra [String] 'DocAuthResult' # @option extra [String] 'DocIssuerCode' @@ -1177,6 +1178,7 @@ def idv_doc_auth_submitted_image_upload_vendor( portrait_match_results: nil, image_metrics: nil, address_line2_present: nil, + zip_code: nil, **extra ) track_event( @@ -1215,6 +1217,7 @@ def idv_doc_auth_submitted_image_upload_vendor( image_metrics:, address_line2_present:, liveness_checking_required:, + zip_code:, **extra, ) end @@ -3730,6 +3733,7 @@ def logout_initiated( # @param [Boolean] success Whether authentication was successful # @param [Hash] errors Authentication error reasons, if unsuccessful + # @param [Hash] error_details Details for error that occurred in unsuccessful submission # @param [String] context # @param [Boolean] new_device # @param [String] multi_factor_auth_method @@ -3748,6 +3752,7 @@ def logout_initiated( def multi_factor_auth( success:, errors: nil, + error_details: nil, context: nil, new_device: nil, multi_factor_auth_method: nil, @@ -3767,24 +3772,27 @@ def multi_factor_auth( ) track_event( 'Multi-Factor Authentication', - success: success, - errors: errors, - context: context, - new_device: new_device, - multi_factor_auth_method: multi_factor_auth_method, - multi_factor_auth_method_created_at: multi_factor_auth_method_created_at, - auth_app_configuration_id: auth_app_configuration_id, - piv_cac_configuration_id: piv_cac_configuration_id, - key_id: key_id, - webauthn_configuration_id: webauthn_configuration_id, - confirmation_for_add_phone: confirmation_for_add_phone, - phone_configuration_id: phone_configuration_id, - pii_like_keypaths: pii_like_keypaths, - area_code: area_code, - country_code: country_code, - phone_fingerprint: phone_fingerprint, - frontend_error:, - **extra, + { + success: success, + errors: errors, + error_details: error_details, + context: context, + new_device: new_device, + multi_factor_auth_method: multi_factor_auth_method, + multi_factor_auth_method_created_at: multi_factor_auth_method_created_at, + auth_app_configuration_id: auth_app_configuration_id, + piv_cac_configuration_id: piv_cac_configuration_id, + key_id: key_id, + webauthn_configuration_id: webauthn_configuration_id, + confirmation_for_add_phone: confirmation_for_add_phone, + phone_configuration_id: phone_configuration_id, + pii_like_keypaths: pii_like_keypaths, + area_code: area_code, + country_code: country_code, + phone_fingerprint: phone_fingerprint, + frontend_error:, + **extra, + }.compact, ) end diff --git a/app/services/reporting/total_user_count_report.rb b/app/services/reporting/total_user_count_report.rb index d7a875ad60c..7d372e282c1 100644 --- a/app/services/reporting/total_user_count_report.rb +++ b/app/services/reporting/total_user_count_report.rb @@ -60,13 +60,13 @@ def total_user_count def verified_user_count Reports::BaseReport.transaction_with_timeout do - Profile.where(active: true).where('activated_at <= ?', end_date).count + Profile.where(active: true).where('verified_at <= ?', end_date).count end end def new_verified_user_count Reports::BaseReport.transaction_with_timeout do - Profile.where(active: true).where(activated_at: current_month).count + Profile.where(active: true).where(verified_at: current_month).count end end @@ -79,7 +79,7 @@ def annual_total_user_count def annual_verified_user_count Reports::BaseReport.transaction_with_timeout do Profile.where(active: true). - where(activated_at: annual_start_date..annual_end_date). + where(verified_at: annual_start_date..annual_end_date). count end end diff --git a/app/services/saml_request_validator.rb b/app/services/saml_request_validator.rb index 2b21aadc0bc..4eee8790492 100644 --- a/app/services/saml_request_validator.rb +++ b/app/services/saml_request_validator.rb @@ -43,7 +43,7 @@ def parsed_vectors_of_trust return @parsed_vectors_of_trust if defined?(@parsed_vectors_of_trust) @parsed_vectors_of_trust = begin - if vtr.is_a?(Array) && !vtr.empty? + if vtr.present? vtr.map { |vot| Vot::Parser.new(vector_of_trust: vot).parse } end rescue Vot::Parser::ParseException diff --git a/app/services/vot/component_expander.rb b/app/services/vot/component_expander.rb new file mode 100644 index 00000000000..12420b94d09 --- /dev/null +++ b/app/services/vot/component_expander.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Vot + class ComponentExpander + attr_reader :initial_components, :component_map, :expanded_components + + def initialize(initial_components:, component_map:) + @initial_components = initial_components + @component_map = component_map + @expanded_components = [] + end + + def expand + initial_components.each do |component| + expand_and_add_component(component) + end + expanded_components.sort_by(&:name) + end + + private + + def expand_and_add_component(component) + # Do not add components if we have alread expanded and added them. + # This prevents infinite recursion. + return if expanded_components.include?(component) + + expanded_components.push(component) + component.implied_component_values.each do |implied_component_name| + implied_component = component_map[implied_component_name] + expand_and_add_component(implied_component) + end + end + end +end diff --git a/app/services/vot/parser.rb b/app/services/vot/parser.rb index 004d77c3a6f..08586cb43f3 100644 --- a/app/services/vot/parser.rb +++ b/app/services/vot/parser.rb @@ -44,44 +44,16 @@ def initialize(vector_of_trust: nil, acr_values: nil) end def parse - initial_components = - if vector_of_trust.present? - map_initial_vector_of_trust_components_to_component_values - elsif acr_values.present? - map_initial_acr_values_to_component_values - end - - if !initial_components + if initial_components.blank? raise ParseException.new('VoT parser called without VoT or ACR values') end + validate_component_uniqueness!(initial_components) - expand_components_with_initial_components(initial_components) - end - - private - - def map_initial_vector_of_trust_components_to_component_values - vector_of_trust.split('.').map do |component_value_name| - SupportedComponentValues.by_name.fetch(component_value_name) - rescue KeyError - raise_unsupported_component_exception(component_value_name) - end - end - - def map_initial_acr_values_to_component_values - acr_values.split(' ').map do |component_value_name| - LegacyComponentValues.by_name.fetch(component_value_name) - rescue KeyError - raise_unsupported_component_exception(component_value_name) - end - end + expanded_components = Vot::ComponentExpander.new(initial_components:, component_map:).expand - def expand_components_with_initial_components(initial_components) - validate_component_uniqueness!(initial_components) - resulting_components = add_implied_components(initial_components).sort_by(&:name) - requirement_list = resulting_components.flat_map(&:requirements) + requirement_list = expanded_components.flat_map(&:requirements) Result.new( - component_values: resulting_components, + component_values: expanded_components, aal2?: requirement_list.include?(:aal2), phishing_resistant?: requirement_list.include?(:phishing_resistant), hspd12?: requirement_list.include?(:hspd12), @@ -92,25 +64,39 @@ def expand_components_with_initial_components(initial_components) ) end - def validate_component_uniqueness!(component_values) - if component_values.length != component_values.uniq.length - raise_duplicate_component_exception + private + + def initial_components + return @initial_components if defined?(@initial_components) + + component_string = vector_of_trust.presence || acr_values || '' + @initial_components ||= component_string.split(component_separator).map do |component_name| + component_map.fetch(component_name) + rescue KeyError + raise_unsupported_component_exception(component_name) + end + end + + def component_separator + if vector_of_trust.present? + '.' + else + ' ' end end - def add_implied_components(component_values) - component_values.flat_map do |component_value| - component_with_implied_components(component_value) - end.uniq + def component_map + if vector_of_trust.present? + SupportedComponentValues.by_name + else + LegacyComponentValues.by_name + end end - def component_with_implied_components(component_value) - [ - component_value, - *component_value.implied_component_values.map do |implied_component_value| - component_with_implied_components(implied_component_value) - end, - ].flatten + def validate_component_uniqueness!(component_values) + if component_values.length != component_values.uniq.length + raise_duplicate_component_exception + end end def raise_unsupported_component_exception(component_value_name) diff --git a/app/services/vot/supported_component_values.rb b/app/services/vot/supported_component_values.rb index a49f23a7d45..af7392684b2 100644 --- a/app/services/vot/supported_component_values.rb +++ b/app/services/vot/supported_component_values.rb @@ -11,37 +11,37 @@ module SupportedComponentValues C2 = ComponentValue.new( name: 'C2', description: 'AAL2 conformant features are engaged', - implied_component_values: [C1], + implied_component_values: ['C1'], requirements: [:aal2], ).freeze Ca = ComponentValue.new( name: 'Ca', description: 'A phishing resistant authenticator is required', - implied_component_values: [C1], + implied_component_values: ['C1'], requirements: [:phishing_resistant], ).freeze Cb = ComponentValue.new( name: 'Cb', description: 'A PIV/CAC card is required', - implied_component_values: [C1], + implied_component_values: ['C1'], requirements: [:hspd12], ).freeze P1 = ComponentValue.new( name: 'P1', description: 'Identity proofing is performed', - implied_component_values: [C2], + implied_component_values: ['C2'], requirements: [:identity_proofing], ).freeze Pb = ComponentValue.new( name: 'Pb', description: 'A biometric comparison is required as part of identity proofing', - implied_component_values: [P1], + implied_component_values: ['P1'], requirements: [:biometric_comparison], ).freeze Pe = ComponentValue.new( name: 'Pe', description: 'Enhanced In Person Proofing is required', - implied_component_values: [P1], + implied_component_values: ['P1'], requirements: [:enhanced_ipp], ).freeze diff --git a/app/views/accounts/history/show.html.erb b/app/views/accounts/history/show.html.erb index 943233bf3dc..ff8a227df2c 100644 --- a/app/views/accounts/history/show.html.erb +++ b/app/views/accounts/history/show.html.erb @@ -13,7 +13,7 @@

-
+

<%= t('headings.account.activity') %> diff --git a/app/views/idv/how_to_verify/show.html.erb b/app/views/idv/how_to_verify/show.html.erb index ebda9307adf..3b8b75993d5 100644 --- a/app/views/idv/how_to_verify/show.html.erb +++ b/app/views/idv/how_to_verify/show.html.erb @@ -1,5 +1,16 @@ <% self.title = t('doc_auth.headings.how_to_verify') %> <%= render PageHeadingComponent.new.with_content(t('doc_auth.headings.how_to_verify')) %> + +<% if defined?(error) %> + <%= render AlertComponent.new( + type: :error, + class: 'margin-bottom-4', + text_tag: 'div', + ) do %> + <%= error %> + <% end %> +<% end %> +

<%= @presenter.how_to_verify_info %>

diff --git a/app/views/users/backup_code_setup/edit.html.erb b/app/views/users/backup_code_setup/edit.html.erb index 580c811f447..043c45357b2 100644 --- a/app/views/users/backup_code_setup/edit.html.erb +++ b/app/views/users/backup_code_setup/edit.html.erb @@ -7,7 +7,7 @@ <%= render ButtonComponent.new( url: backup_code_setup_path, - method: IdentityConfig.store.backup_code_confirm_setup_screen_enabled ? :post : :get, + method: :post, big: true, wide: true, class: 'margin-top-3 margin-bottom-2', diff --git a/config/application.yml.default b/config/application.yml.default index bbed146510e..772c6068aee 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -58,7 +58,6 @@ aws_kms_multi_region_key_id: alias/login-dot-gov-keymaker-multi-region aws_kms_session_key_id: alias/login-dot-gov-test-keymaker aws_logo_bucket: '' aws_region: 'us-west-2' -backup_code_confirm_setup_screen_enabled: true backup_code_cost: '2000$8$1$' broken_personal_key_window_start: '2021-07-29T00:00:00Z' broken_personal_key_window_finish: '2021-09-22T00:00:00Z' @@ -455,7 +454,6 @@ production: attribute_encryption_key_queue: '[]' available_locales: 'en,es,fr' aws_logo_bucket: '' - backup_code_confirm_setup_screen_enabled: false dashboard_api_token: '' dashboard_url: https://dashboard.demo.login.gov database_host: '' diff --git a/lib/identity_config.rb b/lib/identity_config.rb index f2f2462fde3..87e472b033b 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -74,7 +74,6 @@ def self.store config.add(:aws_kms_session_key_id, type: :string) config.add(:aws_logo_bucket, type: :string) config.add(:aws_region, type: :string) - config.add(:backup_code_confirm_setup_screen_enabled, type: :boolean) config.add(:backup_code_cost, type: :string) config.add(:broken_personal_key_window_finish, type: :timestamp) config.add(:broken_personal_key_window_start, type: :timestamp) diff --git a/lib/idp/constants.rb b/lib/idp/constants.rb index bd772c5fd60..5a3b32bce42 100644 --- a/lib/idp/constants.rb +++ b/lib/idp/constants.rb @@ -105,7 +105,7 @@ module Vendors state_id_jurisdiction: MOCK_IDV_APPLICANT_STATE_ID_JURISDICTION, state_id_number: '1111111111111', state_id_type: 'drivers_license', - zipcode: '59010', + zipcode: '59010-1234', issuing_country_code: 'US', }.freeze @@ -116,7 +116,7 @@ module Vendors identity_doc_address1: '123 Way St', identity_doc_address2: '2nd Address Line', identity_doc_city: 'Best City', - identity_doc_zipcode: '12345', + identity_doc_zipcode: '12345-4321', state_id_jurisdiction: 'Virginia', identity_doc_address_state: 'VA', state_id_number: '1111111111111', @@ -133,7 +133,7 @@ module Vendors identity_doc_address1: '123 Way St', identity_doc_address2: '2nd Address Line', identity_doc_city: 'Best City', - identity_doc_zipcode: '12345', + identity_doc_zipcode: '12345-4321', identity_doc_address_state: 'VA', same_address_as_id: 'false', ).freeze diff --git a/lib/reporting/proofing_rate_report.rb b/lib/reporting/proofing_rate_report.rb index 634cf11eb68..da635c9dd52 100644 --- a/lib/reporting/proofing_rate_report.rb +++ b/lib/reporting/proofing_rate_report.rb @@ -87,12 +87,20 @@ def to_csv def reports @reports ||= begin sub_reports = [0, *DATE_INTERVALS].each_cons(2).map do |slice_end, slice_start| + time_range = if slice_end.zero? + Range.new( + (end_date - slice_start.days).beginning_of_day, + (end_date - slice_end.days).end_of_day, + ) + else + Range.new( + (end_date - slice_start.days).beginning_of_day, + (end_date - slice_end.days).end_of_day - 1.day, + ) + end Reporting::IdentityVerificationReport.new( issuers: nil, # all issuers - time_range: Range.new( - (end_date - slice_start.days).beginning_of_day, - (end_date - slice_end.days).beginning_of_day, - ), + time_range: time_range, cloudwatch_client: cloudwatch_client, ) end diff --git a/spec/controllers/idv/how_to_verify_controller_spec.rb b/spec/controllers/idv/how_to_verify_controller_spec.rb index 3b20836174d..4fe227bb666 100644 --- a/spec/controllers/idv/how_to_verify_controller_spec.rb +++ b/spec/controllers/idv/how_to_verify_controller_spec.rb @@ -155,6 +155,29 @@ end let(:analytics_name) { :idv_doc_auth_how_to_verify_submitted } + shared_examples_for 'invalid form submissions' do + it 'invalidates future steps' do + expect(subject).to receive(:clear_future_steps!) + + put :update + end + + it 'logs the invalid value and re-renders the page' do + put :update, params: params + + expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + expect(response).to render_template :show + end + + it 'redirects to how_to_verify' do + put :update, params: params + + expect(flash[:error]).not_to be_present + expect(subject.idv_session.skip_doc_auth).to be_nil + expect(subject.idv_session.opted_in_to_in_person_proofing).to be_nil + end + end + context 'no selection made' do let(:analytics_args) do { @@ -168,17 +191,29 @@ }.merge(ab_test_args) end - it 'invalidates future steps' do - expect(subject).to receive(:clear_future_steps!) + let(:params) { nil } - put :update - end + it_behaves_like 'invalid form submissions' + end - it 'sends analytics_submitted event when nothing is selected' do - put :update + context 'an invalid selection is submitted' do + # (This should only be possible if someone alters the form) + let(:selection) { 'carrier_pigeon' } - expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + let(:analytics_args) do + { + step: 'how_to_verify', + analytics_id: 'Doc Auth', + skip_hybrid_handoff: nil, + 'selection' => selection, + irs_reproofing: false, + error_details: { selection: { inclusion: true } }, + errors: { selection: ['Select a way to verify your identity.'] }, + success: false, + }.merge(ab_test_args) end + + it_behaves_like 'invalid form submissions' end context 'remote' do @@ -236,34 +271,6 @@ expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) end end - - context 'undo/back' do - it 'sets skip_doc_auth to nil and does not redirect' do - put :update, params: { undo_step: true } - - expect(subject.idv_session.skip_doc_auth).to be_nil - expect(subject.idv_session.skip_doc_auth_from_how_to_verify).to be_nil - expect(subject.idv_session.opted_in_to_in_person_proofing).to be_nil - expect(response).to redirect_to(idv_how_to_verify_url) - end - end - end - - context 'form submission error' do - let(:invalid_params) do - { - idv_how_to_verify_form: { selection: '' }, - } - end - - it 'redirects to how to verify when a form submission error is encountered' do - put :update, params: invalid_params - - expect(flash[:error]).to be_present - expect(subject.idv_session.skip_doc_auth).to be_nil - expect(subject.idv_session.opted_in_to_in_person_proofing).to be_nil - expect(response).to redirect_to(idv_how_to_verify_url) - end end describe '#step_info' do diff --git a/spec/controllers/idv/image_uploads_controller_spec.rb b/spec/controllers/idv/image_uploads_controller_spec.rb index b458af87f1a..b004ad3f587 100644 --- a/spec/controllers/idv/image_uploads_controller_spec.rb +++ b/spec/controllers/idv/image_uploads_controller_spec.rb @@ -462,6 +462,7 @@ vendor: nil, workflow: an_instance_of(String), birth_year: 1938, + zip_code: '59010', ) expect(@analytics).to have_logged_event( @@ -647,6 +648,7 @@ transaction_status: nil, vendor: nil, birth_year: 1938, + zip_code: '12345', ) expect(@analytics).to have_logged_event( @@ -760,6 +762,7 @@ transaction_status: nil, vendor: nil, birth_year: 1938, + zip_code: '12345', ) expect(@analytics).to have_logged_event( @@ -873,6 +876,7 @@ transaction_status: nil, vendor: nil, birth_year: 1938, + zip_code: '12345', ) expect(@analytics).to have_logged_event( @@ -983,6 +987,7 @@ transaction_status: nil, vendor: nil, birth_year: nil, + zip_code: '12345', ) expect(@analytics).to have_logged_event( @@ -1102,6 +1107,7 @@ transaction_status: nil, vendor: nil, birth_year: nil, + zip_code: nil, ) expect_funnel_update_counts(user, 1) @@ -1194,6 +1200,7 @@ vendor: nil, workflow: an_instance_of(String), birth_year: nil, + zip_code: nil, ) expect_funnel_update_counts(user, 1) diff --git a/spec/controllers/two_factor_authentication/backup_code_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/backup_code_verification_controller_spec.rb index 8884908d96a..65df3035474 100644 --- a/spec/controllers/two_factor_authentication/backup_code_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/backup_code_verification_controller_spec.rb @@ -27,16 +27,6 @@ sign_in_before_2fa(user) stub_analytics stub_attempts_tracker - analytics_hash = { - success: true, - errors: {}, - multi_factor_auth_method: 'backup_code', - multi_factor_auth_method_created_at: Time.zone.now.strftime('%s%L'), - new_device: true, - } - - expect(@analytics).to receive(:track_mfa_submit_event). - with(analytics_hash) expect(@irs_attempts_api_tracker).to receive(:track_event). with(:mfa_login_backup_code, success: true) @@ -47,6 +37,15 @@ post :create, params: payload + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + success: true, + errors: {}, + multi_factor_auth_method: 'backup_code', + multi_factor_auth_method_created_at: Time.zone.now.strftime('%s%L'), + new_device: true, + ) + expect(subject.user_session[:auth_events]).to eq( [ auth_method: TwoFactorAuthenticatable::AuthMethod::BACKUP_CODE, @@ -93,22 +92,23 @@ stub_analytics stub_attempts_tracker - expect(@analytics).to receive(:track_mfa_submit_event). - with({ - success: true, - errors: {}, - multi_factor_auth_method: 'backup_code', - multi_factor_auth_method_created_at: Time.zone.now.strftime('%s%L'), - new_device: true, - }) - expect(@irs_attempts_api_tracker).to receive(:track_event). with(:mfa_login_backup_code, success: true) - expect(@analytics).to receive(:track_event). - with('User marked authenticated', authentication_type: :valid_2fa) - post :create, params: payload + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + success: true, + errors: {}, + multi_factor_auth_method: 'backup_code', + multi_factor_auth_method_created_at: Time.zone.now.strftime('%s%L'), + new_device: true, + ) + expect(@analytics).to have_logged_event( + 'User marked authenticated', + authentication_type: :valid_2fa, + ) end end end @@ -122,10 +122,12 @@ stub_analytics stub_sign_in_before_2fa(user) - expect(@analytics).to receive(:track_mfa_submit_event). - with(hash_including(new_device: false)) - post :create, params: payload + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + hash_including(new_device: false), + ) end end @@ -171,26 +173,13 @@ user.second_factor_attempts_count = IdentityConfig.store.login_otp_confirmation_max_attempts - 1 user.save - properties = { - success: false, - errors: {}, - multi_factor_auth_method: 'backup_code', - multi_factor_auth_method_created_at: nil, - new_device: true, - } stub_analytics stub_attempts_tracker - expect(@analytics).to receive(:track_mfa_submit_event). - with(properties) - expect(@irs_attempts_api_tracker).to receive(:track_event). with(:mfa_login_backup_code, success: false) - expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: max attempts reached') - expect(@irs_attempts_api_tracker).to receive(:mfa_login_rate_limited). with(mfa_device_type: 'backup_code') @@ -198,6 +187,15 @@ with(PushNotification::MfaLimitAccountLockedEvent.new(user: subject.current_user)) post :create, params: payload + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + success: false, + errors: {}, + multi_factor_auth_method: 'backup_code', + new_device: true, + ) + expect(@analytics).to have_logged_event('Multi-Factor Authentication: max attempts reached') end it 'records unsuccessful 2fa event' do diff --git a/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb index 2cc1a037dfa..0b2e8d4a6ab 100644 --- a/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb @@ -122,41 +122,43 @@ end describe '#create' do - let(:parsed_phone) { Phonelib.parse(controller.current_user.default_phone_configuration.phone) } + let(:user) { create(:user, :with_phone) } + let(:parsed_phone) { Phonelib.parse(user.default_phone_configuration.phone) } context 'when the user enters an invalid OTP during authentication context' do subject(:response) { post :create, params: { code: '12345', otp_delivery_preference: 'sms' } } before do - sign_in_before_2fa + sign_in_before_2fa(user) controller.user_session[:mfa_selections] = ['sms'] expect(controller.current_user.reload.second_factor_attempts_count).to eq 0 - phone_configuration_created_at = controller.current_user. - default_phone_configuration.created_at - properties = { + stub_analytics + stub_attempts_tracker + + expect(@irs_attempts_api_tracker).to receive(:mfa_login_phone_otp_submitted). + with({ reauthentication: false, success: false }) + end + + it 'logs analytics' do + response + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', success: false, error_details: { code: { wrong_length: true, incorrect: true } }, confirmation_for_add_phone: false, context: 'authentication', multi_factor_auth_method: 'sms', - multi_factor_auth_method_created_at: phone_configuration_created_at.strftime('%s%L'), + multi_factor_auth_method_created_at: user.default_phone_configuration.created_at. + strftime('%s%L'), new_device: true, - phone_configuration_id: controller.current_user.default_phone_configuration.id, + phone_configuration_id: user.default_phone_configuration.id, area_code: parsed_phone.area_code, country_code: parsed_phone.country, phone_fingerprint: Pii::Fingerprinter.fingerprint(parsed_phone.e164), enabled_mfa_methods_count: 1, in_account_creation_flow: false, - } - - stub_analytics - stub_attempts_tracker - - expect(@analytics).to receive(:track_mfa_submit_event). - with(properties) - - expect(@irs_attempts_api_tracker).to receive(:mfa_login_phone_otp_submitted). - with({ reauthentication: false, success: false }) + ) end it 'increments second_factor_attempts_count' do @@ -191,7 +193,7 @@ context 'when the user enters an invalid OTP during reauthentication context' do it 'increments second_factor_attempts_count' do - sign_in_before_2fa + sign_in_before_2fa(user) controller.user_session[:context] = 'reauthentication' post :create, params: { code: '12345', otp_delivery_preference: 'sms' } @@ -201,42 +203,23 @@ end context 'when the user has reached the max number of OTP attempts' do - it 'tracks the event' do - user = create( + let(:user) do + create( :user, :fully_registered, + :with_phone, second_factor_attempts_count: IdentityConfig.store.login_otp_confirmation_max_attempts - 1, ) + end + + it 'tracks the event' do sign_in_before_2fa(user) controller.user_session[:mfa_selections] = ['sms'] - phone_configuration_created_at = controller.current_user. - default_phone_configuration.created_at - - properties = { - success: false, - error_details: { code: { wrong_length: true, incorrect: true } }, - confirmation_for_add_phone: false, - context: 'authentication', - multi_factor_auth_method: 'sms', - multi_factor_auth_method_created_at: phone_configuration_created_at.strftime('%s%L'), - new_device: true, - phone_configuration_id: controller.current_user.default_phone_configuration.id, - area_code: parsed_phone.area_code, - country_code: parsed_phone.country, - phone_fingerprint: Pii::Fingerprinter.fingerprint(parsed_phone.e164), - enabled_mfa_methods_count: 1, - in_account_creation_flow: false, - } stub_analytics stub_attempts_tracker - expect(@analytics).to receive(:track_mfa_submit_event). - with(properties) - - expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: max attempts reached') expect(PushNotification::HttpPush).to receive(:deliver). with(PushNotification::MfaLimitAccountLockedEvent.new(user: controller.current_user)) @@ -246,15 +229,32 @@ expect(@irs_attempts_api_tracker).to receive(:mfa_login_rate_limited). with(mfa_device_type: 'otp') - post :create, params: - { code: '12345', - otp_delivery_preference: 'sms' } + post :create, params: { code: '12345', otp_delivery_preference: 'sms' } + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + success: false, + error_details: { code: { wrong_length: true, incorrect: true } }, + confirmation_for_add_phone: false, + context: 'authentication', + multi_factor_auth_method: 'sms', + multi_factor_auth_method_created_at: user.default_phone_configuration.created_at. + strftime('%s%L'), + new_device: true, + phone_configuration_id: user.default_phone_configuration.id, + area_code: parsed_phone.area_code, + country_code: parsed_phone.country, + phone_fingerprint: Pii::Fingerprinter.fingerprint(parsed_phone.e164), + enabled_mfa_methods_count: 1, + in_account_creation_flow: false, + ) + expect(@analytics).to have_logged_event('Multi-Factor Authentication: max attempts reached') end end context 'when the user enters a valid OTP' do before do - sign_in_before_2fa + sign_in_before_2fa(user) subject.user_session[:mfa_selections] = ['sms'] expect(subject.current_user.reload.second_factor_attempts_count).to eq 0 end @@ -279,31 +279,9 @@ end it 'tracks the valid authentication event' do - phone_configuration_created_at = controller.current_user. - default_phone_configuration.created_at - properties = { - success: true, - confirmation_for_add_phone: false, - context: 'authentication', - multi_factor_auth_method: 'sms', - multi_factor_auth_method_created_at: phone_configuration_created_at.strftime('%s%L'), - new_device: true, - phone_configuration_id: controller.current_user.default_phone_configuration.id, - area_code: parsed_phone.area_code, - country_code: parsed_phone.country, - phone_fingerprint: Pii::Fingerprinter.fingerprint(parsed_phone.e164), - enabled_mfa_methods_count: 1, - in_account_creation_flow: false, - } - stub_analytics stub_attempts_tracker - expect(@analytics).to receive(:track_mfa_submit_event). - with(properties) - expect(@analytics).to receive(:track_event). - with('User marked authenticated', authentication_type: :valid_2fa) - expect(@irs_attempts_api_tracker).to receive(:mfa_login_phone_otp_submitted). with(reauthentication: false, success: true) @@ -327,6 +305,27 @@ ) expect(subject.user_session[TwoFactorAuthenticatable::NEED_AUTHENTICATION]).to eq false end + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + success: true, + confirmation_for_add_phone: false, + context: 'authentication', + multi_factor_auth_method: 'sms', + multi_factor_auth_method_created_at: user.default_phone_configuration.created_at. + strftime('%s%L'), + new_device: true, + phone_configuration_id: user.default_phone_configuration.id, + area_code: parsed_phone.area_code, + country_code: parsed_phone.country, + phone_fingerprint: Pii::Fingerprinter.fingerprint(parsed_phone.e164), + enabled_mfa_methods_count: 1, + in_account_creation_flow: false, + ) + expect(@analytics).to have_logged_event( + 'User marked authenticated', + authentication_type: :valid_2fa, + ) end context 'with existing device' do @@ -337,13 +336,15 @@ it 'tracks new device value' do stub_analytics - expect(@analytics).to receive(:track_mfa_submit_event). - with(hash_including(new_device: false)) - post :create, params: { code: subject.current_user.reload.direct_otp, otp_delivery_preference: 'sms', } + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + hash_including(new_device: false), + ) end end @@ -402,7 +403,7 @@ context 'when the user lockout period expires' do before do - sign_in_before_2fa + sign_in_before_2fa(user) lockout_period = IdentityConfig.store.lockout_period_in_minutes.minutes subject.current_user.update( second_factor_locked_at: Time.zone.now - lockout_period - 1.second, diff --git a/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb index 2baef2c7369..a244e28ee70 100644 --- a/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb @@ -47,29 +47,11 @@ let(:payload) { { personal_key_form: personal_key } } it 'tracks the valid authentication event' do personal_key + multi_factor_auth_method_created_at = user.reload. + encrypted_recovery_code_digest_generated_at.strftime('%s%L') sign_in_before_2fa(user) stub_analytics - analytics_hash = { - success: true, - errors: {}, - multi_factor_auth_method: 'personal-key', - multi_factor_auth_method_created_at: user.reload. - encrypted_recovery_code_digest_generated_at.strftime('%s%L'), - new_device: true, - } - - expect(@analytics).to receive(:track_mfa_submit_event). - with(analytics_hash) - - expect(@analytics).to receive(:track_event).with( - 'Personal key: Alert user about sign in', - hash_including(emails: 1, sms_message_ids: ['fake-message-id']), - ) - - expect(@analytics).to receive(:track_event). - with('User marked authenticated', authentication_type: :valid_2fa) - expect(controller).to receive(:handle_valid_verification_for_authentication_context). with(auth_method: TwoFactorAuthenticatable::AuthMethod::PERSONAL_KEY). and_call_original @@ -85,6 +67,23 @@ ) expect(subject.user_session[TwoFactorAuthenticatable::NEED_AUTHENTICATION]).to eq false end + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + success: true, + errors: {}, + multi_factor_auth_method: 'personal-key', + multi_factor_auth_method_created_at:, + new_device: true, + ) + expect(@analytics).to have_logged_event( + 'Personal key: Alert user about sign in', + hash_including(emails: 1, sms_message_ids: ['fake-message-id']), + ) + expect(@analytics).to have_logged_event( + 'User marked authenticated', + authentication_type: :valid_2fa, + ) end context 'with enable_additional_mfa_redirect_for_personal_key_mfa? set to true' do @@ -122,10 +121,12 @@ stub_analytics stub_sign_in_before_2fa(user) - expect(@analytics).to receive(:track_mfa_submit_event). - with(hash_including(new_device: false)) - post :create, params: payload + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + hash_including(new_device: false), + ) end end end @@ -203,20 +204,6 @@ stub_analytics stub_attempts_tracker - properties = { - success: false, - errors: { personal_key: [t('errors.messages.personal_key_incorrect')] }, - error_details: { personal_key: { personal_key_incorrect: true } }, - multi_factor_auth_method: 'personal-key', - multi_factor_auth_method_created_at: personal_key_generated_at.strftime('%s%L'), - new_device: true, - } - - expect(@analytics).to receive(:track_mfa_submit_event). - with(properties) - expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: max attempts reached') - expect(@irs_attempts_api_tracker).to receive(:mfa_login_rate_limited). with(mfa_device_type: 'personal_key') @@ -224,6 +211,17 @@ with(PushNotification::MfaLimitAccountLockedEvent.new(user: subject.current_user)) post :create, params: payload + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + success: false, + errors: { personal_key: [t('errors.messages.personal_key_incorrect')] }, + error_details: { personal_key: { personal_key_incorrect: true } }, + multi_factor_auth_method: 'personal-key', + multi_factor_auth_method_created_at: personal_key_generated_at.strftime('%s%L'), + new_device: true, + ) + expect(@analytics).to have_logged_event('Multi-Factor Authentication: max attempts reached') end it 'records unsuccessful 2fa event' do diff --git a/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb index e62ea8697cd..4e0a56e92a2 100644 --- a/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb @@ -105,17 +105,26 @@ stub_attempts_tracker cfg = controller.current_user.piv_cac_configurations.first - attributes = { + expect(@irs_attempts_api_tracker).to receive(:mfa_login_piv_cac).with( + success: true, + subject_dn: x509_subject, + ) + + expect(controller).to receive(:handle_valid_verification_for_authentication_context). + with(auth_method: TwoFactorAuthenticatable::AuthMethod::PIV_CAC). + and_call_original + + get :show, params: { token: 'good-token' } + + expect(@analytics).to have_logged_event( + :multi_factor_auth_enter_piv_cac, context: 'authentication', multi_factor_auth_method: 'piv_cac', new_device: true, piv_cac_configuration_id: nil, - } - - expect(@analytics).to receive(:track_event). - with(:multi_factor_auth_enter_piv_cac, attributes) - - submit_attributes = { + ) + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', success: true, errors: {}, context: 'authentication', @@ -125,23 +134,11 @@ piv_cac_configuration_id: cfg.id, piv_cac_configuration_dn_uuid: cfg.x509_dn_uuid, key_id: 'foo', - } - expect(@analytics).to receive(:track_mfa_submit_event). - with(submit_attributes) - - expect(@irs_attempts_api_tracker).to receive(:mfa_login_piv_cac).with( - success: true, - subject_dn: x509_subject, ) - - expect(@analytics).to receive(:track_event). - with('User marked authenticated', authentication_type: :valid_2fa) - - expect(controller).to receive(:handle_valid_verification_for_authentication_context). - with(auth_method: TwoFactorAuthenticatable::AuthMethod::PIV_CAC). - and_call_original - - get :show, params: { token: 'good-token' } + expect(@analytics).to have_logged_event( + 'User marked authenticated', + authentication_type: :valid_2fa, + ) end context 'with existing device' do @@ -153,10 +150,12 @@ stub_analytics stub_sign_in_before_2fa(user) - expect(@analytics).to receive(:track_mfa_submit_event). - with(hash_including(new_device: false)) - get :show, params: { token: 'good-token' } + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + hash_including(new_device: false), + ) end end end @@ -240,46 +239,39 @@ stub_analytics stub_attempts_tracker - attributes = { - context: 'authentication', - multi_factor_auth_method: 'piv_cac', - new_device: true, - piv_cac_configuration_id: nil, - } - - expect(@analytics).to receive(:track_event). - with(:multi_factor_auth_enter_piv_cac, attributes) - expect(@irs_attempts_api_tracker).to receive(:mfa_login_rate_limited). with(mfa_device_type: 'piv_cac') piv_cac_mismatch = { type: 'user.piv_cac_mismatch' } - submit_attributes = { - success: false, - errors: piv_cac_mismatch, - context: 'authentication', - multi_factor_auth_method: 'piv_cac', - multi_factor_auth_method_created_at: nil, - new_device: true, - key_id: 'foo', - piv_cac_configuration_dn_uuid: 'bad-uuid', - piv_cac_configuration_id: nil, - } - expect(@analytics).to receive(:track_mfa_submit_event). - with(submit_attributes) - expect(@irs_attempts_api_tracker).to receive(:mfa_login_piv_cac).with( success: false, subject_dn: bad_dn, ) - expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: max attempts reached') expect(PushNotification::HttpPush).to receive(:deliver). with(PushNotification::MfaLimitAccountLockedEvent.new(user: subject.current_user)) get :show, params: { token: 'bad-token' } + + expect(@analytics).to have_logged_event( + :multi_factor_auth_enter_piv_cac, + context: 'authentication', + multi_factor_auth_method: 'piv_cac', + new_device: true, + piv_cac_configuration_id: nil, + ) + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + success: false, + errors: piv_cac_mismatch, + context: 'authentication', + multi_factor_auth_method: 'piv_cac', + new_device: true, + key_id: 'foo', + piv_cac_configuration_dn_uuid: 'bad-uuid', + ) + expect(@analytics).to have_logged_event('Multi-Factor Authentication: max attempts reached') end end diff --git a/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb index 5cc253b1c51..e2ec77add5c 100644 --- a/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb @@ -45,18 +45,6 @@ it 'tracks the valid authentication event' do cfg = controller.current_user.auth_app_configurations.first - attributes = { - success: true, - errors: {}, - multi_factor_auth_method: 'totp', - multi_factor_auth_method_created_at: cfg.created_at.strftime('%s%L'), - new_device: true, - auth_app_configuration_id: controller.current_user.auth_app_configurations.first.id, - } - expect(@analytics).to receive(:track_mfa_submit_event). - with(attributes) - expect(@analytics).to receive(:track_event). - with('User marked authenticated', authentication_type: :valid_2fa) expect(@irs_attempts_api_tracker).to receive(:track_event). with(:mfa_login_totp, success: true) expect(controller).to receive(:handle_valid_verification_for_authentication_context). @@ -64,6 +52,20 @@ and_call_original post :create, params: { code: generate_totp_code(@secret) } + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + success: true, + errors: {}, + multi_factor_auth_method: 'totp', + multi_factor_auth_method_created_at: cfg.created_at.strftime('%s%L'), + new_device: true, + auth_app_configuration_id: controller.current_user.auth_app_configurations.first.id, + ) + expect(@analytics).to have_logged_event( + 'User marked authenticated', + authentication_type: :valid_2fa, + ) end context 'with existing device' do @@ -74,10 +76,12 @@ it 'tracks new device value' do stub_analytics - expect(@analytics).to receive(:track_mfa_submit_event). - with(hash_including(new_device: false)) - post :create, params: { code: generate_totp_code(@secret) } + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + hash_including(new_device: false), + ) end end end @@ -88,18 +92,26 @@ app1 = Db::AuthAppConfiguration.create(user, user.generate_totp_secret, nil, 'foo') app2 = Db::AuthAppConfiguration.create(user, user.generate_totp_secret, nil, 'bar') - expect(@analytics).to receive(:track_event). - with('User marked authenticated', authentication_type: :valid_2fa).twice - sign_in_as_user(user) post :create, params: { code: generate_totp_code(app1.otp_secret_key) } expect(response).to redirect_to account_url + expect(@analytics).to have_logged_event( + 'User marked authenticated', + authentication_type: :valid_2fa, + ) + @analytics.events.clear + sign_out(user) sign_in_as_user(user) post :create, params: { code: generate_totp_code(app2.otp_secret_key) } expect(response).to redirect_to account_url + + expect(@analytics).to have_logged_event( + 'User marked authenticated', + authentication_type: :valid_2fa, + ) end end @@ -155,19 +167,6 @@ @secret = user.generate_totp_secret Db::AuthAppConfiguration.create(user, @secret, nil, 'foo') - attributes = { - success: false, - errors: {}, - multi_factor_auth_method: 'totp', - multi_factor_auth_method_created_at: nil, - new_device: true, - auth_app_configuration_id: nil, - } - - expect(@analytics).to receive(:track_mfa_submit_event). - with(attributes) - expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: max attempts reached') expect(@irs_attempts_api_tracker).to receive(:track_event). with(:mfa_login_totp, success: false) @@ -178,6 +177,15 @@ with(PushNotification::MfaLimitAccountLockedEvent.new(user: subject.current_user)) post :create, params: { code: '12345' } + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + success: false, + errors: {}, + multi_factor_auth_method: 'totp', + new_device: true, + ) + expect(@analytics).to have_logged_event('Multi-Factor Authentication: max attempts reached') end end diff --git a/spec/controllers/two_factor_authentication/webauthn_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/webauthn_verification_controller_spec.rb index 802b619982b..7d1ecf24292 100644 --- a/spec/controllers/two_factor_authentication/webauthn_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/webauthn_verification_controller_spec.rb @@ -133,27 +133,12 @@ ) controller.current_user.webauthn_configurations.first end - let(:result) do - { - context: 'authentication', - multi_factor_auth_method: 'webauthn', - success: true, - webauthn_configuration_id: webauthn_configuration.id, - multi_factor_auth_method_created_at: webauthn_configuration.created_at.strftime('%s%L'), - new_device: true, - } - end before do allow(WebauthnVerificationForm).to receive(:domain_name).and_return('localhost:3000') end it 'tracks a valid submission' do - expect(@analytics).to receive(:track_mfa_submit_event). - with(result) - expect(@analytics).to receive(:track_event). - with('User marked authenticated', authentication_type: :valid_2fa) - expect(@irs_attempts_api_tracker).to receive(:track_event).with( :mfa_login_webauthn_roaming, success: true, @@ -173,6 +158,20 @@ ) expect(subject.user_session[TwoFactorAuthenticatable::NEED_AUTHENTICATION]).to eq false end + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + context: 'authentication', + multi_factor_auth_method: 'webauthn', + success: true, + webauthn_configuration_id: webauthn_configuration.id, + multi_factor_auth_method_created_at: webauthn_configuration.created_at.strftime('%s%L'), + new_device: true, + ) + expect(@analytics).to have_logged_event( + 'User marked authenticated', + authentication_type: :valid_2fa, + ) end context 'with existing device' do @@ -183,10 +182,12 @@ it 'tracks new device value' do stub_analytics - expect(@analytics).to receive(:track_mfa_submit_event). - with(hash_including(new_device: false)) - patch :confirm, params: params + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + hash_including(new_device: false), + ) end end @@ -201,14 +202,8 @@ ) controller.current_user.webauthn_configurations.first end - let(:result) { super().merge(multi_factor_auth_method: 'webauthn_platform') } it 'tracks a valid submission' do - expect(@analytics).to receive(:track_mfa_submit_event). - with(result) - expect(@analytics).to receive(:track_event). - with('User marked authenticated', authentication_type: :valid_2fa) - expect(@irs_attempts_api_tracker).to receive(:track_event).with( :mfa_login_webauthn_platform, success: true, @@ -226,6 +221,21 @@ false, ) end + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + context: 'authentication', + multi_factor_auth_method: 'webauthn_platform', + success: true, + webauthn_configuration_id: webauthn_configuration.id, + multi_factor_auth_method_created_at: webauthn_configuration.created_at. + strftime('%s%L'), + new_device: true, + ) + expect(@analytics).to have_logged_event( + 'User marked authenticated', + authentication_type: :valid_2fa, + ) end end end @@ -238,19 +248,20 @@ credential_public_key: credential_public_key, ) webauthn_configuration = controller.current_user.webauthn_configurations.first - result = { context: 'authentication', - multi_factor_auth_method: 'webauthn', - success: false, - error_details: { authenticator_data: { invalid_authenticator_data: true } }, - webauthn_configuration_id: webauthn_configuration.id, - multi_factor_auth_method_created_at: webauthn_configuration.created_at. - strftime('%s%L'), - new_device: true } - expect(@analytics).to receive(:track_mfa_submit_event). - with(result) expect(controller).to receive(:create_user_event).with(:sign_in_unsuccessful_2fa) patch :confirm, params: params + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', + context: 'authentication', + multi_factor_auth_method: 'webauthn', + success: false, + error_details: { authenticator_data: { invalid_authenticator_data: true } }, + webauthn_configuration_id: webauthn_configuration.id, + multi_factor_auth_method_created_at: webauthn_configuration.created_at.strftime('%s%L'), + new_device: true, + ) end context 'webauthn_platform returns an error from frontend API' do @@ -297,7 +308,10 @@ end it 'logs an event with error details' do - expect(@analytics).to receive(:track_mfa_submit_event).with( + patch :confirm, params: params + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication', success: false, error_details: { authenticator_data: { blank: true }, @@ -311,11 +325,8 @@ multi_factor_auth_method_created_at: second_webauthn_platform_configuration.created_at.strftime('%s%L'), new_device: true, - webauthn_configuration_id: nil, frontend_error: webauthn_error, ) - - patch :confirm, params: params end end end diff --git a/spec/controllers/users/backup_code_setup_controller_spec.rb b/spec/controllers/users/backup_code_setup_controller_spec.rb index f0c2e12ed12..20934de2c76 100644 --- a/spec/controllers/users/backup_code_setup_controller_spec.rb +++ b/spec/controllers/users/backup_code_setup_controller_spec.rb @@ -70,33 +70,6 @@ it_behaves_like 'valid backup codes creation' end - - context 'backup code confirm setup feature disabled' do - before do - allow(IdentityConfig.store).to receive(:backup_code_confirm_setup_screen_enabled). - and_return(false) - end - - it 'redirects to root url' do - expect(response).to redirect_to(root_url) - end - - context 'in multi mfa setup flow' do - before do - allow(controller).to receive(:in_multi_mfa_selection_flow?).and_return(true) - end - - it_behaves_like 'valid backup codes creation' - end - - context 'adding backup codes from account dashboard' do - before do - controller.user_session[:account_redirect_path] = account_path - end - - it_behaves_like 'valid backup codes creation' - end - end end describe '#create' do diff --git a/spec/controllers/users/two_factor_authentication_controller_spec.rb b/spec/controllers/users/two_factor_authentication_controller_spec.rb index 5fc618b23fd..067fcd2d695 100644 --- a/spec/controllers/users/two_factor_authentication_controller_spec.rb +++ b/spec/controllers/users/two_factor_authentication_controller_spec.rb @@ -595,6 +595,38 @@ def index ) end + it 'sends a 6-digit OTP when the idv_ten_digit_otp A/B test is in progress' do + stub_const( + 'AbTests::IDV_TEN_DIGIT_OTP', + FakeAbTestBucket.new.tap { |ab| ab.assign(@user.uuid => :ten_digit_otp) }, + ) + + sign_in_before_2fa(@user) + subject.user_session[:context] = 'confirmation' + subject.user_session[:unconfirmed_phone] = @unconfirmed_phone + parsed_phone = Phonelib.parse(@unconfirmed_phone) + + allow(Telephony).to receive(:send_confirmation_otp).and_call_original + + get :send_code, params: otp_delivery_form_sms + + expect(Telephony).to have_received(:send_confirmation_otp).with( + otp: subject.current_user.direct_otp, + to: @unconfirmed_phone, + expiration: 10, + channel: :sms, + otp_format: 'digit', + otp_length: '6', + domain: IdentityConfig.store.domain_name, + country_code: 'US', + extra_metadata: { + area_code: parsed_phone.area_code, + phone_fingerprint: Pii::Fingerprinter.fingerprint(parsed_phone.e164), + resend: nil, + }, + ) + end + it 'tracks the enrollment attempt event' do sign_in_before_2fa(@user) subject.user_session[:context] = 'confirmation' diff --git a/spec/features/account/backup_codes_spec.rb b/spec/features/account/backup_codes_spec.rb index b36e47f4c97..478645b6c66 100644 --- a/spec/features/account/backup_codes_spec.rb +++ b/spec/features/account/backup_codes_spec.rb @@ -36,29 +36,6 @@ expect(page).to have_content(t('notices.backup_codes_deleted')) expect(page).to have_current_path(account_two_factor_authentication_path) end - - context 'backup code confirm setup feature disabled' do - before do - allow(IdentityConfig.store).to receive(:backup_code_confirm_setup_screen_enabled). - and_return(false) - end - - it 'allows user to regenerate backup codes' do - expect(page).to have_content(t('account.index.backup_codes_exist')) - old_backup_code = user.backup_code_configurations.sample - click_link t('forms.backup_code.regenerate'), href: backup_code_regenerate_path - click_on t('account.index.backup_code_confirm_regenerate') - - expect(page).to have_current_path(backup_code_setup_path) - expect(page).to have_content(t('forms.backup_code.title')) - expect(BackupCodeConfiguration.where(id: old_backup_code.id).any?).to eq(false) - - click_continue - - expect(page).to have_content(t('notices.backup_codes_configured')) - expect(page).to have_current_path(account_two_factor_authentication_path) - end - end end context 'without backup codes and having another mfa method' do @@ -105,34 +82,6 @@ expect(page).to have_current_path(account_two_factor_authentication_path) expect(page).to have_content(expected_message) end - - context 'backup code confirm setup feature disabled' do - before do - allow(IdentityConfig.store).to receive(:backup_code_confirm_setup_screen_enabled). - and_return(false) - end - - it 'allows user to create backup codes' do - click_on t('forms.backup_code.generate') - - expect(page).to have_current_path(backup_code_setup_path) - - generated_at = user.backup_code_configurations. - order(created_at: :asc).first.created_at. - in_time_zone('UTC') - formatted_generated_at = l(generated_at, format: t('time.formats.event_timestamp')) - - expected_message = "#{t('account.index.backup_codes_exist')} #{formatted_generated_at}" - - expect(page).to have_current_path(backup_code_setup_path) - expect(page).to have_content(t('forms.backup_code.title')) - click_continue - - expect(page).to have_content(t('notices.backup_codes_configured')) - expect(page).to have_current_path(account_two_factor_authentication_path) - expect(page).to have_content(expected_message) - end - end end context 'with only backup codes' do diff --git a/spec/fixtures/ial2_test_portrait_match_success.yml b/spec/fixtures/ial2_test_portrait_match_success.yml index decfc65999d..5b63725e6b1 100644 --- a/spec/fixtures/ial2_test_portrait_match_success.yml +++ b/spec/fixtures/ial2_test_portrait_match_success.yml @@ -10,7 +10,7 @@ document: state_id_jurisdiction: ND state_id_number: '1111111111111' state_id_type: 'drivers_license' - zipcode: '59010' + zipcode: '59010-1234' doc_auth_result: Passed failed_alerts: [] portrait_match_results: diff --git a/spec/forms/idv/api_image_upload_form_spec.rb b/spec/forms/idv/api_image_upload_form_spec.rb index 87b2be2f466..fa657bebb9a 100644 --- a/spec/forms/idv/api_image_upload_form_spec.rb +++ b/spec/forms/idv/api_image_upload_form_spec.rb @@ -257,6 +257,7 @@ transaction_reason_code: nil, workflow: 'test_non_liveness_workflow', birth_year: 1938, + zip_code: '59010', ) end @@ -377,6 +378,7 @@ transaction_reason_code: nil, workflow: 'test_liveness_workflow', birth_year: 1938, + zip_code: '59010', ) end diff --git a/spec/lib/reporting/proofing_rate_report_spec.rb b/spec/lib/reporting/proofing_rate_report_spec.rb index 0cdf1ed0aa5..63e3521c800 100644 --- a/spec/lib/reporting/proofing_rate_report_spec.rb +++ b/spec/lib/reporting/proofing_rate_report_spec.rb @@ -2,7 +2,7 @@ require 'reporting/proofing_rate_report' RSpec.describe Reporting::ProofingRateReport do - let(:end_date) { Date.new(2022, 1, 1).in_time_zone('UTC').beginning_of_day } + let(:end_date) { Date.new(2022, 1, 1).in_time_zone('UTC').end_of_day } let(:parallel) { true } subject(:report) do @@ -25,7 +25,7 @@ successfully_verified_users: 1, idv_doc_auth_rejected: 1, idv_fraud_rejected: 0, - time_range: (end_date - 30.days)..end_date, + time_range: (end_date - 30.days).beginning_of_day..end_date, ), instance_double( 'Reporting::IdentityVerificationReport', @@ -39,7 +39,7 @@ successfully_verified_users: 2, idv_doc_auth_rejected: 1, idv_fraud_rejected: 1, - time_range: (end_date - 60.days)..end_date, + time_range: (end_date - 60.days).beginning_of_day..end_date, ), instance_double( 'Reporting::IdentityVerificationReport', @@ -53,7 +53,7 @@ successfully_verified_users: 3, idv_doc_auth_rejected: 1, idv_fraud_rejected: 2, - time_range: (end_date - 90.days)..end_date, + time_range: (end_date - 90.days).beginning_of_day..end_date, ), ], ) @@ -136,26 +136,26 @@ expect(report.reports.map(&:time_range)).to eq( [ - (end_date - 30.days)..end_date, - (end_date - 60.days)..end_date, - (end_date - 90.days)..end_date, + (end_date - 30.days).beginning_of_day..end_date, + (end_date - 60.days).beginning_of_day..end_date, + (end_date - 90.days).beginning_of_day..end_date, ], ) expect(Reporting::IdentityVerificationReport).to have_received(:new).with( - time_range: (end_date - 30.days)..end_date, + time_range: (end_date - 30.days).beginning_of_day..end_date, issuers: nil, cloudwatch_client: report.cloudwatch_client, ).once expect(Reporting::IdentityVerificationReport).to have_received(:new).with( - time_range: (end_date - 60.days)..(end_date - 30.days), + time_range: (end_date - 60.days).beginning_of_day..(end_date - 30.days).end_of_day, issuers: nil, cloudwatch_client: report.cloudwatch_client, ).once expect(Reporting::IdentityVerificationReport).to have_received(:new).with( - time_range: (end_date - 90.days)..(end_date - 60.days), + time_range: (end_date - 90.days).beginning_of_day..(end_date - 60.days).end_of_day, issuers: nil, cloudwatch_client: report.cloudwatch_client, ).once diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 67798ff36d4..88ddf55bdb2 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1540,55 +1540,6 @@ def it_should_not_send_survey end end - describe '#new_device?' do - let(:user_agent) { 'A computer on the internet' } - let(:ip_address) { '4.4.4.4' } - let(:existing_device_cookie) { 'existing_device_cookie' } - let(:cookie_jar) do - { - device: existing_device_cookie, - }.with_indifferent_access.tap do |cookie_jar| - allow(cookie_jar).to receive(:permanent).and_return({}) - end - end - let(:request) do - double( - remote_ip: ip_address, - user_agent: user_agent, - cookie_jar: cookie_jar, - ) - end - let(:user) { create(:user, :fully_registered) } - let(:device) { create(:device, user: user, cookie_uuid: existing_device_cookie) } - - context 'with existing device' do - before do - # Memoize user and device before specs run - user - device - end - it 'does not expect a device to be new' do - cookies = request.cookie_jar - device_present = user.new_device?(cookie_uuid: cookies[:device]) - expect(device_present).to eq(false) - end - end - - context 'with new device' do - let(:device) { create(:device, user: user, cookie_uuid: 'non_existing_device_cookie') } - before do - # Memoize user and device before specs run - user - device - end - it 'expects a new device' do - cookies = request.cookie_jar - device_present = user.new_device?(cookie_uuid: cookies[:device]) - expect(device_present).to eq(true) - end - end - end - describe '#authenticated_device?' do let(:user) { create(:user, :fully_registered) } let(:device) { create(:device, user:) } diff --git a/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb b/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb index e4ec9c1487c..821fefd18f1 100644 --- a/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb +++ b/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb @@ -32,7 +32,7 @@ address2: nil, city: 'GREAT FALLS', state: 'MT', - zipcode: '59010', + zipcode: '59010-1234', dob: '1938-10-06', state_id_number: '1111111111111', state_id_jurisdiction: 'ND', @@ -131,7 +131,7 @@ address2: nil, city: 'GREAT FALLS', state: 'MT', - zipcode: '59010', + zipcode: '59010-1234', dob: '1938-10-06', state_id_number: '1111111111111', state_id_jurisdiction: 'ND', diff --git a/spec/services/doc_auth/mock/result_response_spec.rb b/spec/services/doc_auth/mock/result_response_spec.rb index 0e247fd97b0..8398f56c89d 100644 --- a/spec/services/doc_auth/mock/result_response_spec.rb +++ b/spec/services/doc_auth/mock/result_response_spec.rb @@ -359,7 +359,7 @@ address2: nil, city: 'GREAT FALLS', state: 'MT', - zipcode: '59010', + zipcode: '59010-1234', dob: '1938-10-06', state_id_number: '1111111111111', state_id_jurisdiction: 'ND', diff --git a/spec/services/reporting/total_user_count_report_spec.rb b/spec/services/reporting/total_user_count_report_spec.rb index b67f7111bb6..9135cd25e67 100644 --- a/spec/services/reporting/total_user_count_report_spec.rb +++ b/spec/services/reporting/total_user_count_report_spec.rb @@ -77,10 +77,13 @@ context 'with one verified and one non-verified user' do before do - create(:user) + user1 = create(:user) user2 = create(:user) + create(:profile, :active, :verified, user: user1) # MW: The :verified trait doesn't set active: true. This feels confusing. - create(:profile, :active, user: user2) + # user2 active profile but unverified + create(:profile, :active, :verified, user: user2) + user2.profiles.first.deactivate(:password_reset) end let(:expected_total_count) { 2 } let(:expected_verified_count) { 1 } diff --git a/spec/services/vot/component_expander_spec.rb b/spec/services/vot/component_expander_spec.rb new file mode 100644 index 00000000000..f66cae695b4 --- /dev/null +++ b/spec/services/vot/component_expander_spec.rb @@ -0,0 +1,68 @@ +require 'rails_helper' + +RSpec.describe Vot::ComponentExpander do + context 'with a component with no implied components' do + it 'returns the single component' do + component_a = Vot::ComponentValue.new( + name: 'A1', description: 'Test component', requirements: [], + implied_component_values: [] + ) + component_b = Vot::ComponentValue.new( + name: 'B1', description: 'Test component', requirements: [], + implied_component_values: [] + ) + component_map = { 'A1' => component_a, 'B1' => component_b } + initial_components = [component_a] + + result = described_class.new(component_map:, initial_components:).expand + + expect(result).to eq([component_a]) + end + end + + context 'with a component with several layers of implied components' do + it 'returns the components expanded into an array' do + component_a = Vot::ComponentValue.new( + name: 'A1', description: 'Test component', requirements: [], + implied_component_values: [] + ) + component_b = Vot::ComponentValue.new( + name: 'B1', description: 'Test component', requirements: [], + implied_component_values: ['A1'] + ) + component_c = Vot::ComponentValue.new( + name: 'C1', description: 'Test component', requirements: [], + implied_component_values: ['B1'] + ) + component_map = { 'A1' => component_a, 'B1' => component_b, 'C1' => component_c } + initial_components = [component_c] + + result = described_class.new(component_map:, initial_components:).expand + + expect(result).to eq([component_a, component_b, component_c]) + end + end + + context 'with a component with cyclic implied component relationships' do + it 'returns the components expanded into an array' do + component_a = Vot::ComponentValue.new( + name: 'A1', description: 'Test component', requirements: [], + implied_component_values: ['C1'] + ) + component_b = Vot::ComponentValue.new( + name: 'B1', description: 'Test component', requirements: [], + implied_component_values: ['A1'] + ) + component_c = Vot::ComponentValue.new( + name: 'C1', description: 'Test component', requirements: [], + implied_component_values: ['B1'] + ) + component_map = { 'A1' => component_a, 'B1' => component_b, 'C1' => component_c } + initial_components = [component_c] + + result = described_class.new(component_map:, initial_components:).expand + + expect(result).to eq([component_a, component_b, component_c]) + end + end +end diff --git a/spec/support/fake_analytics.rb b/spec/support/fake_analytics.rb index efb6adc9e05..7862b5feb3a 100644 --- a/spec/support/fake_analytics.rb +++ b/spec/support/fake_analytics.rb @@ -25,7 +25,6 @@ def track_event(event, original_attributes = {}) :first_name, :last_name, :address1, - :zipcode, :dob, :state_id_number, ).each do |key, default_pii_value| @@ -168,10 +167,6 @@ def track_event(event, attributes = {}) nil end - def track_mfa_submit_event(_attributes) - # no-op - end - def browser_attributes {} end diff --git a/spec/views/users/backup_code_setup/edit.html.erb_spec.rb b/spec/views/users/backup_code_setup/edit.html.erb_spec.rb index cd2c972ec50..7db44566d88 100644 --- a/spec/views/users/backup_code_setup/edit.html.erb_spec.rb +++ b/spec/views/users/backup_code_setup/edit.html.erb_spec.rb @@ -13,18 +13,4 @@ it 'has a link to cancel and return to account page' do expect(rendered).to have_link(t('links.cancel'), href: account_path) end - - context 'backup code confirm setup feature disabled' do - before do - allow(IdentityConfig.store).to receive(:backup_code_confirm_setup_screen_enabled). - and_return(false) - end - - it 'has a link to confirm and proceed to setup' do - expect(rendered).to have_link( - t('account.index.backup_code_confirm_regenerate'), - href: backup_code_setup_path, - ) - end - end end diff --git a/yarn.lock b/yarn.lock index a7b39fd89e8..70ab8487714 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4665,10 +4665,10 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -libphonenumber-js@^1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.1.tgz#2596683e1876bfee74082bb49339fe0a85ae34f9" - integrity sha512-Wze1LPwcnzvcKGcRHFGFECTaLzxOtujwpf924difr5zniyYv1C2PiW0419qDR7m8lKDxsImu5mwxFuXhXpjmvw== +libphonenumber-js@^1.11.2: + version "1.11.2" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.2.tgz#9ddd7d1a1e1be0e7c596c7e09487c362b4f1210c" + integrity sha512-V9mGLlaXN1WETzqQvSu6qf6XVAr3nFuJvWsHcuzCCCo6xUKawwSxOPTpan5CGOSKTn5w/bQuCZcLPJkyysgC3w== lightningcss-darwin-arm64@1.23.0: version "1.23.0"