diff --git a/app/controllers/idv/otp_verification_controller.rb b/app/controllers/idv/otp_verification_controller.rb index b792ad544eb..9e7a0d12adb 100644 --- a/app/controllers/idv/otp_verification_controller.rb +++ b/app/controllers/idv/otp_verification_controller.rb @@ -61,6 +61,7 @@ def phone_confirmation_otp_verification_form @phone_confirmation_otp_verification_form ||= PhoneConfirmationOtpVerificationForm.new( user: current_user, user_phone_confirmation_session: idv_session.user_phone_confirmation_session, + irs_attempts_api_tracker: irs_attempts_api_tracker, ) end end diff --git a/app/forms/idv/phone_confirmation_otp_verification_form.rb b/app/forms/idv/phone_confirmation_otp_verification_form.rb index 19016f09e6f..6caa41867e7 100644 --- a/app/forms/idv/phone_confirmation_otp_verification_form.rb +++ b/app/forms/idv/phone_confirmation_otp_verification_form.rb @@ -1,10 +1,11 @@ module Idv class PhoneConfirmationOtpVerificationForm - attr_reader :user, :user_phone_confirmation_session, :code + attr_reader :user, :user_phone_confirmation_session, :irs_attempts_api_tracker, :code - def initialize(user:, user_phone_confirmation_session:) + def initialize(user:, user_phone_confirmation_session:, irs_attempts_api_tracker:) @user = user @user_phone_confirmation_session = user_phone_confirmation_session + @irs_attempts_api_tracker = irs_attempts_api_tracker end def submit(code:) @@ -32,11 +33,18 @@ def clear_second_factor_attempts def increment_second_factor_attempts user.second_factor_attempts_count += 1 attributes = {} - attributes[:second_factor_locked_at] = Time.zone.now if user.max_login_attempts? + if user.max_login_attempts? + attributes[:second_factor_locked_at] = Time.zone.now + irs_attempts_api_tracker.idv_phone_otp_submitted_rate_limited(phone: user_phone) + end UpdateUser.new(user: user, attributes: attributes).call end + def user_phone + user_phone_confirmation_session.phone + end + def extra_analytics_attributes { code_expired: user_phone_confirmation_session.expired?, diff --git a/app/services/irs_attempts_api/tracker_events.rb b/app/services/irs_attempts_api/tracker_events.rb index e94c2ca5d32..b54caf3b3d8 100644 --- a/app/services/irs_attempts_api/tracker_events.rb +++ b/app/services/irs_attempts_api/tracker_events.rb @@ -82,6 +82,15 @@ def forgot_password_email_confirmed(success:, failure_reason: nil) ) end + # The user reached the rate limit for Idv phone OTP submitted + # @param [String] phone + def idv_phone_otp_submitted_rate_limited(phone:) + track_event( + :idv_phone_otp_submitted_rate_limited, + phone: phone, + ) + end + # @param [Boolean] success # @param [String] phone_number # The phone upload link was sent during the IDV process diff --git a/spec/forms/idv/phone_confirmation_otp_verification_form_spec.rb b/spec/forms/idv/phone_confirmation_otp_verification_form_spec.rb index f20a3c5faeb..240741933b0 100644 --- a/spec/forms/idv/phone_confirmation_otp_verification_form_spec.rb +++ b/spec/forms/idv/phone_confirmation_otp_verification_form_spec.rb @@ -13,11 +13,19 @@ delivery_method: :sms, ) end + let(:irs_attempts_api_tracker) do + instance_double( + IrsAttemptsApi::Tracker, + idv_phone_otp_submitted_rate_limited: true, + ) + end describe '#submit' do def try_submit(code) described_class.new( - user: user, user_phone_confirmation_session: user_phone_confirmation_session, + user: user, + user_phone_confirmation_session: user_phone_confirmation_session, + irs_attempts_api_tracker: irs_attempts_api_tracker, ).submit(code: code) end @@ -64,6 +72,10 @@ def try_submit(code) context 'when the code is expired' do let(:phone_confirmation_otp_sent_at) { 11.minutes.ago } + before do + allow(IrsAttemptsApi::Tracker).to receive(:new).and_return(irs_attempts_api_tracker) + end + it 'returns an unsuccessful result' do result = try_submit(phone_confirmation_otp_code) @@ -84,6 +96,7 @@ def try_submit(code) expect(user.second_factor_attempts_count).to eq(3) expect(user.second_factor_locked_at).to be_within(1.second).of(Time.zone.now) + expect(irs_attempts_api_tracker).to have_received(:idv_phone_otp_submitted_rate_limited) end end