diff --git a/app/controllers/concerns/idv/phone_otp_rate_limitable.rb b/app/controllers/concerns/idv/phone_otp_rate_limitable.rb index 9ee9a529eb8..f0e443849ac 100644 --- a/app/controllers/concerns/idv/phone_otp_rate_limitable.rb +++ b/app/controllers/concerns/idv/phone_otp_rate_limitable.rb @@ -27,11 +27,19 @@ def reset_attempt_count_if_user_no_longer_locked_out def handle_too_many_otp_sends analytics.idv_phone_confirmation_otp_rate_limit_sends + # TODO: Attempts API PII phone_number: current_user.phone + attempts_api_tracker.idv_rate_limited( + limiter_type: :phone_otp, + ) handle_max_attempts('otp_requests') end def handle_too_many_otp_attempts analytics.idv_phone_confirmation_otp_rate_limit_attempts + # TODO: Attempts API PII phone_number: current_user.phone + attempts_api_tracker.idv_rate_limited( + limiter_type: :phone_otp, + ) handle_max_attempts('otp_login_attempts') end diff --git a/app/controllers/concerns/idv/verify_info_concern.rb b/app/controllers/concerns/idv/verify_info_concern.rb index 2e7423b1101..80c9ed2fa50 100644 --- a/app/controllers/concerns/idv/verify_info_concern.rb +++ b/app/controllers/concerns/idv/verify_info_concern.rb @@ -123,11 +123,9 @@ def idv_failure(result) ) if ssn_rate_limiter.limited? - idv_failure_log_rate_limited(:proof_ssn) - redirect_to idv_session_errors_ssn_failure_url + rate_limit_redirect!(:proof_ssn, step_name: STEP_NAME) elsif resolution_rate_limiter.limited? - idv_failure_log_rate_limited(:idv_resolution) - redirect_to rate_limited_url + rate_limit_redirect!(:idv_resolution, step_name: STEP_NAME) elsif has_exception && is_mva_exception idv_failure_log_warning redirect_to state_id_warning_url @@ -147,20 +145,6 @@ def idv_failure(result) end end - def idv_failure_log_rate_limited(rate_limit_type) - if rate_limit_type == :proof_ssn - analytics.rate_limit_reached( - limiter_type: :proof_ssn, - step_name: STEP_NAME, - ) - elsif rate_limit_type == :idv_resolution - analytics.rate_limit_reached( - limiter_type: :idv_resolution, - step_name: STEP_NAME, - ) - end - end - def idv_failure_log_error analytics.idv_doc_auth_exception_visited( step_name: STEP_NAME, diff --git a/app/controllers/concerns/rate_limit_concern.rb b/app/controllers/concerns/rate_limit_concern.rb index 996a536906f..3cd8e38a53f 100644 --- a/app/controllers/concerns/rate_limit_concern.rb +++ b/app/controllers/concerns/rate_limit_concern.rb @@ -20,6 +20,7 @@ def confirm_not_rate_limited_after_doc_auth end def confirm_not_rate_limited_for_phone_address_verification + # TODO: Attempts API PII add phone number if idv_attempter_rate_limited?(:proof_address) rate_limit_redirect!(:proof_address) return true @@ -44,12 +45,15 @@ def confirm_not_rate_limited_for_phone_and_letter_address_verification if idv_attempter_rate_limited?(:proof_address) && gpo_verify_by_mail_policy.rate_limited? rate_limit_redirect!(:proof_address) return true + elsif idv_attempter_rate_limited?(:proof_address) || gpo_verify_by_mail_policy.rate_limited? + attempts_api_tracker.idv_rate_limited(limiter_type: :proof_address) end end - def rate_limit_redirect!(rate_limit_type) + def rate_limit_redirect!(rate_limit_type, step_name: nil) if idv_attempter_rate_limited?(rate_limit_type) - analytics.rate_limit_reached(limiter_type: rate_limit_type) + analytics.rate_limit_reached(limiter_type: rate_limit_type, step_name:) + attempts_api_tracker.idv_rate_limited(limiter_type: rate_limit_type) rate_limited_redirect(rate_limit_type) return true end diff --git a/app/controllers/idv/by_mail/enter_code_rate_limited_controller.rb b/app/controllers/idv/by_mail/enter_code_rate_limited_controller.rb index d43c524e62a..7c2513a23f0 100644 --- a/app/controllers/idv/by_mail/enter_code_rate_limited_controller.rb +++ b/app/controllers/idv/by_mail/enter_code_rate_limited_controller.rb @@ -14,6 +14,9 @@ def index analytics.rate_limit_reached( limiter_type: :verify_gpo_key, ) + attempts_api_tracker.idv_rate_limited( + limiter_type: :verify_gpo_key, + ) @expires_at = rate_limiter.expires_at end diff --git a/app/controllers/idv/hybrid_handoff_controller.rb b/app/controllers/idv/hybrid_handoff_controller.rb index 903044b361a..a56cfc84ec7 100644 --- a/app/controllers/idv/hybrid_handoff_controller.rb +++ b/app/controllers/idv/hybrid_handoff_controller.rb @@ -215,6 +215,10 @@ def rate_limited_failure analytics.rate_limit_reached( limiter_type: :idv_send_link, ) + # TODO: Attempts API PII Add phone_number: formatted_destination_phone, + attempts_api_tracker.idv_rate_limited( + limiter_type: :idv_send_link, + ) message = I18n.t( 'doc_auth.errors.send_link_limited', timeout: distance_of_time_in_words( diff --git a/app/controllers/idv/image_uploads_controller.rb b/app/controllers/idv/image_uploads_controller.rb index 6db18a4f4b0..bf0623a4c2d 100644 --- a/app/controllers/idv/image_uploads_controller.rb +++ b/app/controllers/idv/image_uploads_controller.rb @@ -21,8 +21,9 @@ def image_upload_form @image_upload_form ||= Idv::ApiImageUploadForm.new( params, acuant_sdk_upgrade_ab_test_bucket: ab_test_bucket(:ACUANT_SDK), + analytics:, + attempts_api_tracker:, service_provider: current_sp, - analytics: analytics, uuid_prefix: current_sp&.app_id, liveness_checking_required: resolved_authn_context_result.facial_match?, ) diff --git a/app/controllers/idv/phone_controller.rb b/app/controllers/idv/phone_controller.rb index 687e278ddee..c9cd6a04e47 100644 --- a/app/controllers/idv/phone_controller.rb +++ b/app/controllers/idv/phone_controller.rb @@ -135,9 +135,10 @@ def handle_proofing_failure def step @step ||= Idv::PhoneStep.new( - idv_session: idv_session, + idv_session:, trace_id: amzn_trace_id, - analytics: analytics, + analytics:, + attempts_api_tracker:, ) end diff --git a/app/forms/idv/api_image_upload_form.rb b/app/forms/idv/api_image_upload_form.rb index 0fc2b2de5c0..59cab963a79 100644 --- a/app/forms/idv/api_image_upload_form.rb +++ b/app/forms/idv/api_image_upload_form.rb @@ -19,6 +19,7 @@ def initialize( service_provider:, acuant_sdk_upgrade_ab_test_bucket:, analytics: nil, + attempts_api_tracker: nil, uuid_prefix: nil, liveness_checking_required: false ) @@ -26,6 +27,7 @@ def initialize( @service_provider = service_provider @acuant_sdk_upgrade_ab_test_bucket = acuant_sdk_upgrade_ab_test_bucket @analytics = analytics + @attempts_api_tracker = attempts_api_tracker @readable = {} @uuid_prefix = uuid_prefix @liveness_checking_required = liveness_checking_required @@ -70,8 +72,14 @@ def submit private - attr_reader :params, :analytics, :service_provider, :form_response, :uuid_prefix, - :liveness_checking_required, :acuant_sdk_upgrade_ab_test_bucket + attr_reader :acuant_sdk_upgrade_ab_test_bucket, + :analytics, + :attempts_api_tracker, + :form_response, + :liveness_checking_required, + :params, + :service_provider, + :uuid_prefix def abandon_any_ipp_progress user_id && User.find(user_id).establishing_in_person_enrollment&.cancel @@ -358,6 +366,7 @@ def limit_if_rate_limited def track_rate_limited analytics.rate_limit_reached(limiter_type: :idv_doc_auth) + attempts_api_tracker.idv_rate_limited(limiter_type: :idv_doc_auth) end def document_capture_session_uuid diff --git a/app/services/attempts_api/tracker_events.rb b/app/services/attempts_api/tracker_events.rb index 6422eb7d3a5..acf82571d55 100644 --- a/app/services/attempts_api/tracker_events.rb +++ b/app/services/attempts_api/tracker_events.rb @@ -43,6 +43,17 @@ def idv_reproof track_event('idv-reproof') end + # The user has exceeded the rate limit during idv document upload + # @param limiter_type [String<'idv_doc_auth', 'idv_resolution', 'proof_ssn', 'proof_address', + # 'confirmation', 'idv_send_link'] + # Type of rate limit + def idv_rate_limited(limiter_type:) + track_event( + 'idv-rate-limited', + limiter_type:, + ) + end + # @param [Boolean] success True if account successfully deleted # A User deletes their Login.gov account def logged_in_account_purged(success:) diff --git a/app/services/idv/phone_step.rb b/app/services/idv/phone_step.rb index eb13660adce..f1f2a5843b6 100644 --- a/app/services/idv/phone_step.rb +++ b/app/services/idv/phone_step.rb @@ -2,10 +2,11 @@ module Idv class PhoneStep - def initialize(idv_session:, trace_id:, analytics:) + def initialize(idv_session:, trace_id:, analytics:, attempts_api_tracker:) self.idv_session = idv_session @trace_id = trace_id @analytics = analytics + @attempts_api_tracker = attempts_api_tracker end def submit(step_params) @@ -122,6 +123,9 @@ def rate_limiter def rate_limited_result @analytics.rate_limit_reached(limiter_type: :proof_address, step_name: :phone) + @attempts_api_tracker.idv_rate_limited( + limiter_type: :proof_address, + ) FormResponse.new(success: false) end diff --git a/docs/attempts-api/schemas/events/IdentityProofingEvents.yml b/docs/attempts-api/schemas/events/IdentityProofingEvents.yml index 024d826040e..f685979b997 100644 --- a/docs/attempts-api/schemas/events/IdentityProofingEvents.yml +++ b/docs/attempts-api/schemas/events/IdentityProofingEvents.yml @@ -5,8 +5,6 @@ properties: $ref: './identity-proofing/IdvDocumentUploaded.yml' idv-document-upload-submitted: $ref: './identity-proofing/IdvDocumentUploadSubmitted.yml' - idv-document-upload-rate-limited: - $ref: './identity-proofing/IdvDocumentUploadRateLimited.yml' idv-enrollment-complete: $ref: './identity-proofing/IdvEnrollmentComplete.yml' idv-ipp-ready-to-verify-visit: @@ -15,14 +13,10 @@ properties: $ref: './identity-proofing/IdvPhoneOtpSent.yml' idv-phone-otp-submitted: $ref: './identity-proofing/IdvPhoneOtpSubmitted.yml' - idv-phone-otp-submitted-rate-limited: - $ref: './identity-proofing/IdvPhoneOtpSubmittedRateLimited.yml' - idv-phone-otp-sent-rate-limited: - $ref: './identity-proofing/IdvPhoneOtpSentRateLimited.yml' - idv-phone-send-link-rate-limited: - $ref: './identity-proofing/IdvPhoneSendLinkRateLimited.yml' idv-phone-submitted: $ref: './identity-proofing/IdvPhoneSubmitted.yml' + idv-rate-limited: + $ref: './identity-proofing/IdvRateLimited.yml' idv-reproof: $ref: './identity-proofing/IdvReproof.yml' idv-ssn-submitted: @@ -31,8 +25,6 @@ properties: $ref: './identity-proofing/IdvTmxFraudCheck.yml' idv-verification-submitted: $ref: './identity-proofing/IdvVerificationSubmitted.yml' - idv-verification-rate-limited: - $ref: './identity-proofing/IdvVerificationRateLimited.yml' idv-verify-by-mail-letter-requested: $ref: './identity-proofing/IdvVerifyByMailLetterRequested.yml' idv-verify-by-mail-enter-code-submitted: diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvAddressSubmitted.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvAddressSubmitted.yml index f47f11085ae..3b901c0b326 100644 --- a/docs/attempts-api/schemas/events/identity-proofing/IdvAddressSubmitted.yml +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvAddressSubmitted.yml @@ -5,4 +5,39 @@ allOf: - type: object properties: address: - type: string \ No newline at end of file + type: string + address_edited: + type: boolean + description: | + True if updated address does not match document address + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + state: + type: string + description: An OPTIONAL key if the code does not match + enum: + - blank + zipcode: + type: string + enum: + - pattern_mismatch + city: + type: string + enum: + - blank + address1: + type: string + enum: + - blank + - too_long + address2: + type: string + enum: + - too_long + success: + type: boolean + description: | + Indicates whether the entered code matched the code that was sent confirming the address diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvDocumentUploadRateLimited.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvDocumentUploadRateLimited.yml deleted file mode 100644 index 253c65e6d7a..00000000000 --- a/docs/attempts-api/schemas/events/identity-proofing/IdvDocumentUploadRateLimited.yml +++ /dev/null @@ -1,5 +0,0 @@ -description: | - A user exceeds the failure limit for document capture during identity proofing. -allOf: - - $ref: '../shared/EventProperties.yml' - - type: object diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSentRateLimited.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSentRateLimited.yml deleted file mode 100644 index 554f2b63e38..00000000000 --- a/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSentRateLimited.yml +++ /dev/null @@ -1,8 +0,0 @@ -description: | - When the user exceeds the number of times they can request a new security code to be sent to their provided phone number for address verification. -allOf: - - $ref: '../shared/EventProperties.yml' - - type: object - properties: - phone_number: - type: string diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSubmittedRateLimited.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSubmittedRateLimited.yml deleted file mode 100644 index 73ccbd42660..00000000000 --- a/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSubmittedRateLimited.yml +++ /dev/null @@ -1,8 +0,0 @@ -description: | - When the user exceeds the number of times they can try verifying the security code that they received on their phone for phone or address verification during identity proofing. -allOf: - - $ref: '../shared/EventProperties.yml' - - type: object - properties: - phone_number: - type: string diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneSendLinkRateLimited.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneSendLinkRateLimited.yml deleted file mode 100644 index de5293b217c..00000000000 --- a/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneSendLinkRateLimited.yml +++ /dev/null @@ -1,8 +0,0 @@ -description: | - When the user is rate limited for submitting a phone number to receive a mobile upload link too many times during identity verification. -allOf: - - $ref: '../shared/EventProperties.yml' - - type: object - properties: - phone_number: - type: string diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvRateLimited.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvRateLimited.yml new file mode 100644 index 00000000000..ec4a109c8c1 --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvRateLimited.yml @@ -0,0 +1,21 @@ +description: | + The user reaches verification submission rate limits when identity proofing. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + rate_limit_type: + type: string + description: | + Reason for the rate limiting + enum: + - idv_doc_auth + - idv_resolution + - proof_ssn + - proof_address + - phone_confirmation + - idv_send_link + phone: + type: string + description: | + OPTIONAL, the provided phone number (in the event of a phone-related rate limiting) diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvVerificationRateLimited.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvVerificationRateLimited.yml deleted file mode 100644 index ed24326b19a..00000000000 --- a/docs/attempts-api/schemas/events/identity-proofing/IdvVerificationRateLimited.yml +++ /dev/null @@ -1,14 +0,0 @@ -description: | - The user reaches verification submission rate limits when identity proofing. -allOf: - - $ref: '../shared/EventProperties.yml' - - type: object - properties: - throttle_context: - type: string - description: | - `multi-session` context means that multiple sessions attempted to submit verification using the same Social Security number multiple times with failure and exceeded the rate limit. - `single-session` context means that one session attempted to submit verification using the same PII multiple times with failure and exceeded the rate limit. - enum: - - multi-session - - single-session diff --git a/spec/controllers/concerns/idv/phone_otp_rate_limitable_spec.rb b/spec/controllers/concerns/idv/phone_otp_rate_limitable_spec.rb index 765aa87b1d2..6d1b38a5979 100644 --- a/spec/controllers/concerns/idv/phone_otp_rate_limitable_spec.rb +++ b/spec/controllers/concerns/idv/phone_otp_rate_limitable_spec.rb @@ -12,12 +12,32 @@ def handle_max_attempts(_arg = nil) describe '#handle_too_many_otp_sends' do before do stub_analytics + stub_attempts_tracker end it 'calls analytics tracking event' do + expect(@attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :phone_otp, + ) subject.handle_too_many_otp_sends expect(@analytics).to have_logged_event('Idv: Phone OTP sends rate limited') end end + + describe '#handle_too_many_otp_attempts' do + before do + stub_analytics + stub_attempts_tracker + end + + it 'calls analytics tracking event' do + expect(@attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :phone_otp, + ) + subject.handle_too_many_otp_attempts + + expect(@analytics).to have_logged_event('Idv: Phone OTP attempts rate limited') + end + end end diff --git a/spec/controllers/concerns/rate_limit_concern_spec.rb b/spec/controllers/concerns/rate_limit_concern_spec.rb index 09ab0b02f16..d68f4e6f559 100644 --- a/spec/controllers/concerns/rate_limit_concern_spec.rb +++ b/spec/controllers/concerns/rate_limit_concern_spec.rb @@ -27,6 +27,8 @@ def update end before(:each) do + stub_attempts_tracker + sign_in(user) allow(subject).to receive(:current_user).and_return(user) routes.draw do @@ -50,6 +52,9 @@ def update it 'redirects to idv_doc_auth rate limited error page' do rate_limiter = RateLimiter.new(user: user, rate_limit_type: :idv_doc_auth) rate_limiter.increment_to_limited! + expect(@attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :idv_doc_auth, + ) get :show @@ -59,8 +64,13 @@ def update context 'with idv_resolution rate_limiter (VerifyInfo)' do it 'redirects to idv_resolution rate limited error page' do + stub_attempts_tracker + rate_limiter = RateLimiter.new(user: user, rate_limit_type: :idv_resolution) rate_limiter.increment_to_limited! + expect(@attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :idv_resolution, + ) get :show @@ -76,6 +86,9 @@ def update end it 'does not redirect' do + expect(@attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :proof_address, + ) get :show expect(response.body).to eq 'Hello' @@ -94,6 +107,9 @@ def update end it 'does not redirect' do + expect(@attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :proof_address, + ) get :show expect(response.body).to eq 'Hello' @@ -114,6 +130,9 @@ def update end it 'redirects to proof_address rate limited error page' do + expect(@attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :proof_address, + ) get :show expect(response).to redirect_to idv_phone_errors_failure_url @@ -134,6 +153,9 @@ def update context 'ssn is in idv_session' do it 'redirects to proof_ssn rate limited error page' do subject.idv_session.ssn = ssn + expect(@attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :proof_ssn, + ) get :show expect(response).to redirect_to idv_session_errors_ssn_failure_url diff --git a/spec/controllers/idv/by_mail/enter_code_rate_limited_controller_spec.rb b/spec/controllers/idv/by_mail/enter_code_rate_limited_controller_spec.rb index cafa82a396c..3a1beaaa364 100644 --- a/spec/controllers/idv/by_mail/enter_code_rate_limited_controller_spec.rb +++ b/spec/controllers/idv/by_mail/enter_code_rate_limited_controller_spec.rb @@ -14,11 +14,15 @@ stub_sign_in(user) stub_user_with_pending_profile(user) stub_analytics + stub_attempts_tracker RateLimiter.new(rate_limit_type: :verify_gpo_key, user: user).increment_to_limited! end describe '#index' do it 'renders the rate limited page' do + expect(@attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :verify_gpo_key, + ) get :index expect(response).to render_template :index diff --git a/spec/controllers/idv/in_person/verify_info_controller_spec.rb b/spec/controllers/idv/in_person/verify_info_controller_spec.rb index 5061cf0deee..52f1b4dcfd7 100644 --- a/spec/controllers/idv/in_person/verify_info_controller_spec.rb +++ b/spec/controllers/idv/in_person/verify_info_controller_spec.rb @@ -14,6 +14,7 @@ before do stub_analytics + stub_attempts_tracker stub_sign_in(user) subject.idv_session.flow_path = 'standard' subject.idv_session.ssn = Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID[:ssn] @@ -111,6 +112,9 @@ end it 'redirects to rate limited url' do + expect(@attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :idv_resolution, + ) get :show expect(response).to redirect_to idv_session_errors_failure_url diff --git a/spec/controllers/idv/phone_controller_spec.rb b/spec/controllers/idv/phone_controller_spec.rb index 90f60af5356..4b1558a4707 100644 --- a/spec/controllers/idv/phone_controller_spec.rb +++ b/spec/controllers/idv/phone_controller_spec.rb @@ -527,10 +527,15 @@ context 'when the user is rate limited by submission' do before do stub_analytics + stub_attempts_tracker rate_limiter = RateLimiter.new(rate_limit_type: :proof_address, user: user) rate_limiter.increment_to_limited! + expect(@attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :proof_address, + ) + put :create, params: { idv_phone_form: { phone: bad_phone } } end diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb index 993310b14d8..9e53fa0d7de 100644 --- a/spec/controllers/idv/verify_info_controller_spec.rb +++ b/spec/controllers/idv/verify_info_controller_spec.rb @@ -17,6 +17,7 @@ stub_sign_in(user) stub_up_to(:ssn, idv_session: subject.idv_session) stub_analytics + stub_attempts_tracker end describe '#step_info' do @@ -150,6 +151,9 @@ end it 'redirects to ssn failure url' do + expect(@attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :proof_ssn, + ) get :show expect(response).to redirect_to idv_session_errors_ssn_failure_url @@ -167,6 +171,9 @@ end it 'redirects to rate limited url' do + expect(@attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :idv_resolution, + ) get :show expect(response).to redirect_to idv_session_errors_failure_url diff --git a/spec/features/idv/doc_auth/document_capture_spec.rb b/spec/features/idv/doc_auth/document_capture_spec.rb index e8830bd37fa..761b5ebc791 100644 --- a/spec/features/idv/doc_auth/document_capture_spec.rb +++ b/spec/features/idv/doc_auth/document_capture_spec.rb @@ -8,9 +8,13 @@ let(:max_attempts) { IdentityConfig.store.doc_auth_max_attempts } let(:fake_analytics) { FakeAnalytics.new } + let(:attempts_api_tracker) { AttemptsApiTrackingHelper::FakeAttemptsTracker.new } before(:each) do allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics) + allow_any_instance_of(ApplicationController).to receive(:attempts_api_tracker).and_return( + attempts_api_tracker, + ) allow_any_instance_of(ServiceProviderSession).to receive(:sp_name).and_return(@sp_name) end @@ -58,6 +62,10 @@ end it 'logs the rate limited analytics event for doc_auth' do + expect(attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :idv_doc_auth, + ) + attach_and_submit_images expect(fake_analytics).to have_logged_event( 'Rate Limit Reached', @@ -285,6 +293,9 @@ end it 'logs the rate limited analytics event for doc_auth' do + expect(attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :idv_doc_auth, + ) attach_and_submit_images expect(fake_analytics).to have_logged_event( 'Rate Limit Reached', diff --git a/spec/features/idv/doc_auth/hybrid_handoff_spec.rb b/spec/features/idv/doc_auth/hybrid_handoff_spec.rb index ca4905381a2..502d132904c 100644 --- a/spec/features/idv/doc_auth/hybrid_handoff_spec.rb +++ b/spec/features/idv/doc_auth/hybrid_handoff_spec.rb @@ -6,6 +6,7 @@ include ActionView::Helpers::DateHelper let(:fake_analytics) { FakeAnalytics.new } + let(:attempts_api_tracker) { AttemptsApiTrackingHelper::FakeAttemptsTracker.new } let(:idv_send_link_max_attempts) { 3 } let(:idv_send_link_attempt_window_in_minutes) do IdentityConfig.store.idv_send_link_attempt_window_in_minutes @@ -19,6 +20,9 @@ end sign_in_and_2fa_user allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics) + allow_any_instance_of(ApplicationController).to receive(:attempts_api_tracker).and_return( + attempts_api_tracker, + ) end context 'on a desktop device send link' do before do @@ -118,6 +122,9 @@ ) allow(IdentityConfig.store).to receive(:idv_send_link_max_attempts) .and_return(idv_send_link_max_attempts) + expect(attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :idv_send_link, + ) freeze_time do idv_send_link_max_attempts.times do @@ -136,13 +143,14 @@ fill_in :doc_auth_phone, with: '415-555-0199' click_send_link - expect(page).to have_current_path(idv_hybrid_handoff_path) expect(page).to have_content( I18n.t( 'doc_auth.errors.send_link_limited', timeout: timeout, ), ) + expect(page).to have_current_path(idv_hybrid_handoff_path) + expect(page).to have_selector('h1', text: t('doc_auth.headings.hybrid_handoff')) expect(page).to have_selector('h2', text: t('doc_auth.headings.upload_from_phone')) end diff --git a/spec/features/idv/doc_auth/redo_document_capture_spec.rb b/spec/features/idv/doc_auth/redo_document_capture_spec.rb index db863b2c32e..f114e55ec1a 100644 --- a/spec/features/idv/doc_auth/redo_document_capture_spec.rb +++ b/spec/features/idv/doc_auth/redo_document_capture_spec.rb @@ -8,9 +8,13 @@ let(:max_attempts) { IdentityConfig.store.doc_auth_max_attempts } let(:fake_analytics) { FakeAnalytics.new } + let(:attempts_api_tracker) { AttemptsApiTrackingHelper::FakeAttemptsTracker.new } before(:each) do allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics) + allow_any_instance_of(ApplicationController).to receive(:attempts_api_tracker).and_return( + attempts_api_tracker, + ) allow_any_instance_of(ServiceProviderSession).to receive(:sp_name).and_return(@sp_name) end @@ -58,6 +62,10 @@ end it 'logs the rate limited analytics event for doc_auth' do + expect(attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :idv_doc_auth, + ) + attach_and_submit_images expect(fake_analytics).to have_logged_event( 'Rate Limit Reached', @@ -214,6 +222,10 @@ end it 'logs the rate limited analytics event for doc_auth' do + expect(attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :idv_doc_auth, + ) + attach_and_submit_images expect(fake_analytics).to have_logged_event( 'Rate Limit Reached', diff --git a/spec/features/idv/doc_auth/socure_document_capture_spec.rb b/spec/features/idv/doc_auth/socure_document_capture_spec.rb index f7a0ae0e0cb..3abb90a6894 100644 --- a/spec/features/idv/doc_auth/socure_document_capture_spec.rb +++ b/spec/features/idv/doc_auth/socure_document_capture_spec.rb @@ -8,6 +8,7 @@ let(:max_attempts) { 3 } let(:fake_analytics) { FakeAnalytics.new } + let(:attempts_api_tracker) { AttemptsApiTrackingHelper::FakeAttemptsTracker.new } let(:socure_docv_webhook_secret_key) { 'socure_docv_webhook_secret_key' } let(:fake_socure_docv_document_request_endpoint) { 'https://fake-socure.test/document-request' } let(:fake_socure_document_capture_app_url) { 'https://verify.fake-socure.test/something' } @@ -29,6 +30,9 @@ socure_docv_webhook_repeat_endpoints.each { |endpoint| stub_request(:post, endpoint) } allow(IdentityConfig.store).to receive(:ruby_workers_idv_enabled).and_return(false) allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics) + allow_any_instance_of(ApplicationController).to receive(:attempts_api_tracker).and_return( + attempts_api_tracker, + ) allow_any_instance_of(SocureDocvResultsJob).to receive(:analytics).and_return(fake_analytics) @docv_transaction_token = stub_docv_document_request allow(IdentityConfig.store).to receive(:socure_docv_verification_data_test_mode) @@ -133,6 +137,10 @@ end it 'redirects to the rate limited error page' do + expect(attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :idv_doc_auth, + ) + expect(page).to have_current_path(fake_socure_document_capture_app_url) visit idv_socure_document_capture_path expect(page).to have_current_path(idv_socure_document_capture_path) diff --git a/spec/features/idv/doc_auth/verify_info_step_spec.rb b/spec/features/idv/doc_auth/verify_info_step_spec.rb index 27f64fd3cf1..17e0529eb57 100644 --- a/spec/features/idv/doc_auth/verify_info_step_spec.rb +++ b/spec/features/idv/doc_auth/verify_info_step_spec.rb @@ -5,6 +5,7 @@ include DocAuthHelper let(:fake_analytics) { FakeAnalytics.new } + let(:attempts_api_tracker) { AttemptsApiTrackingHelper::FakeAttemptsTracker.new } let(:user) { user_with_2fa } let(:fake_pii_details) do @@ -22,6 +23,9 @@ before do allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics) + allow_any_instance_of(ApplicationController).to receive(:attempts_api_tracker).and_return( + attempts_api_tracker, + ) sign_in_and_2fa_user(user) complete_doc_auth_steps_before_ssn_step end @@ -131,6 +135,10 @@ # proof_ssn_max_attempts is 10, vs 5 for resolution, so it doesn't get triggered it 'rate limits resolution and continues when it expires' do + expect(attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :idv_resolution, + ).twice + (max_resolution_attempts - 2).times do complete_verify_step expect(page).to have_current_path(idv_session_errors_warning_path) @@ -199,6 +207,10 @@ end it 'rate limits ssn and continues when it expires' do + expect(attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :proof_ssn, + ).twice + complete_verify_step expect(page).to have_current_path(idv_session_errors_ssn_failure_path) expect(fake_analytics).to have_logged_event( @@ -208,6 +220,7 @@ ) visit idv_verify_info_url + # second rate limit event expect(page).to have_current_path(idv_session_errors_ssn_failure_path) # Manual expiration is needed because Redis timestamp doesn't always match ruby timestamp diff --git a/spec/forms/idv/api_image_upload_form_spec.rb b/spec/forms/idv/api_image_upload_form_spec.rb index 5e91133dc5d..1ea3d56bffb 100644 --- a/spec/forms/idv/api_image_upload_form_spec.rb +++ b/spec/forms/idv/api_image_upload_form_spec.rb @@ -18,7 +18,8 @@ ), service_provider: build(:service_provider, issuer: 'test_issuer'), analytics: fake_analytics, - liveness_checking_required: liveness_checking_required, + attempts_api_tracker:, + liveness_checking_required:, acuant_sdk_upgrade_ab_test_bucket:, ) end @@ -55,6 +56,7 @@ let(:document_capture_session_uuid) { document_capture_session.uuid } let(:fake_analytics) { FakeAnalytics.new } let(:acuant_sdk_upgrade_ab_test_bucket) {} + let(:attempts_api_tracker) { AttemptsApiTrackingHelper::FakeAttemptsTracker.new } describe '#valid?' do context 'with all valid images' do @@ -84,6 +86,9 @@ context 'when rate limited from submission' do it 'is not valid' do + expect(attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :idv_doc_auth, + ) RateLimiter.new( rate_limit_type: :idv_doc_auth, user: document_capture_session.user, diff --git a/spec/services/idv/phone_step_spec.rb b/spec/services/idv/phone_step_spec.rb index a98978b034c..26fd9783151 100644 --- a/spec/services/idv/phone_step_spec.rb +++ b/spec/services/idv/phone_step_spec.rb @@ -36,12 +36,14 @@ end let(:trace_id) { SecureRandom.uuid } let(:analytics) { FakeAnalytics.new } + let(:attempts_api_tracker) { AttemptsApiTrackingHelper::FakeAttemptsTracker.new } subject do described_class.new( - idv_session: idv_session, - trace_id: trace_id, - analytics: analytics, + idv_session:, + trace_id:, + analytics:, + attempts_api_tracker:, ) end @@ -175,6 +177,7 @@ describe '#failure_reason' do context 'when there are idv attempts remaining' do it 'returns :warning' do + expect(attempts_api_tracker).not_to receive(:idv_rate_limited) subject.submit(phone: bad_phone) expect(subject.async_state.done?).to eq true subject.async_state_done(subject.async_state) @@ -187,6 +190,9 @@ it 'returns :fail' do RateLimiter.new(rate_limit_type: :proof_address, user: user).increment_to_limited! + expect(attempts_api_tracker).to receive(:idv_rate_limited).with( + limiter_type: :proof_address, + ) subject.submit(phone: bad_phone) expect(subject.failure_reason).to eq(:fail) end