diff --git a/app/controllers/concerns/idv/verify_info_concern.rb b/app/controllers/concerns/idv/verify_info_concern.rb index de946e5883e..37fd362d35f 100644 --- a/app/controllers/concerns/idv/verify_info_concern.rb +++ b/app/controllers/concerns/idv/verify_info_concern.rb @@ -13,7 +13,7 @@ def shared_update pii[:uuid_prefix] = ServiceProvider.find_by(issuer: sp_session[:issuer])&.app_id set_state_id_type - ssn_throttle.increment! + ssn_rate_limiter.increment! document_capture_session = DocumentCaptureSession.create( user_id: current_user.id, @@ -61,17 +61,17 @@ def aamva_disallowed_for_service_provider? banlist.include?(sp_session[:issuer]) end - def resolution_throttle - @resolution_throttle ||= Throttle.new( + def resolution_rate_limiter + @resolution_rate_limiter ||= RateLimiter.new( user: current_user, - throttle_type: :idv_resolution, + rate_limit_type: :idv_resolution, ) end - def ssn_throttle - @ssn_throttle ||= Throttle.new( + def ssn_rate_limiter + @ssn_rate_limiter ||= RateLimiter.new( target: Pii::Fingerprinter.fingerprint(pii[:ssn]), - throttle_type: :proof_ssn, + rate_limit_type: :proof_ssn, ) end @@ -85,14 +85,14 @@ def idv_failure(result) :mva_exception, ) - resolution_throttle.increment! if proofing_results_exception.blank? + resolution_rate_limiter.increment! if proofing_results_exception.blank? - if ssn_throttle.throttled? - idv_failure_log_throttled(:proof_ssn) + if ssn_rate_limiter.limited? + idv_failure_log_rate_limited(:proof_ssn) redirect_to idv_session_errors_ssn_failure_url - elsif resolution_throttle.throttled? - idv_failure_log_throttled(:idv_resolution) - redirect_to throttled_url + elsif resolution_rate_limiter.limited? + idv_failure_log_rate_limited(:idv_resolution) + redirect_to rate_limited_url elsif proofing_results_exception.present? && is_mva_exception idv_failure_log_warning redirect_to state_id_warning_url @@ -105,14 +105,14 @@ def idv_failure(result) end end - def idv_failure_log_throttled(throttle_type) - if throttle_type == :proof_ssn + def idv_failure_log_rate_limited(rate_limit_type) + if rate_limit_type == :proof_ssn irs_attempts_api_tracker.idv_verification_rate_limited(throttle_context: 'multi-session') analytics.throttler_rate_limit_triggered( throttle_type: :proof_ssn, step_name: STEP_NAME, ) - elsif throttle_type == :idv_resolution + elsif rate_limit_type == :idv_resolution irs_attempts_api_tracker.idv_verification_rate_limited(throttle_context: 'single-session') analytics.throttler_rate_limit_triggered( throttle_type: :idv_resolution, @@ -124,18 +124,18 @@ def idv_failure_log_throttled(throttle_type) def idv_failure_log_error analytics.idv_doc_auth_exception_visited( step_name: STEP_NAME, - remaining_attempts: resolution_throttle.remaining_count, + remaining_attempts: resolution_rate_limiter.remaining_count, ) end def idv_failure_log_warning analytics.idv_doc_auth_warning_visited( step_name: STEP_NAME, - remaining_attempts: resolution_throttle.remaining_count, + remaining_attempts: resolution_rate_limiter.remaining_count, ) end - def throttled_url + def rate_limited_url idv_session_errors_failure_url end @@ -203,7 +203,7 @@ def async_state_done(current_async_state) ) form_response = form_response.merge(check_ssn) if form_response.success? - summarize_result_and_throttle_failures(form_response) + summarize_result_and_rate_limit_failures(form_response) delete_async if form_response.success? @@ -231,10 +231,10 @@ def save_threatmetrix_status(form_response) idv_session.threatmetrix_review_status = review_status end - def summarize_result_and_throttle_failures(summary_result) + def summarize_result_and_rate_limit_failures(summary_result) if summary_result.success? add_proofing_components - ssn_throttle.reset! + ssn_rate_limiter.reset! else idv_failure(summary_result) end diff --git a/app/controllers/concerns/rate_limit_concern.rb b/app/controllers/concerns/rate_limit_concern.rb index ee19cd1ef49..a789ee84f96 100644 --- a/app/controllers/concerns/rate_limit_concern.rb +++ b/app/controllers/concerns/rate_limit_concern.rb @@ -3,8 +3,8 @@ module RateLimitConcern def confirm_not_rate_limited rate_limited = false - %i[idv_resolution idv_doc_auth proof_address proof_ssn].each do |throttle_type| - if rate_limit_redirect!(throttle_type) + %i[idv_resolution idv_doc_auth proof_address proof_ssn].each do |rate_limit_type| + if rate_limit_redirect!(rate_limit_type) rate_limited = true break end @@ -12,21 +12,21 @@ def confirm_not_rate_limited rate_limited end - def rate_limit_redirect!(throttle_type) - if idv_attempter_rate_limited?(throttle_type) - track_rate_limited_event(throttle_type) - rate_limited_redirect(throttle_type) + def rate_limit_redirect!(rate_limit_type) + if idv_attempter_rate_limited?(rate_limit_type) + track_rate_limited_event(rate_limit_type) + rate_limited_redirect(rate_limit_type) return true end end - def track_rate_limited_event(throttle_type) - analytics_args = { throttle_type: throttle_type } + def track_rate_limited_event(rate_limit_type) + analytics_args = { throttle_type: rate_limit_type } throttle_context = 'single-session' - if throttle_type == :proof_address + if rate_limit_type == :proof_address analytics_args[:step_name] = :phone - elsif throttle_type == :proof_ssn + elsif rate_limit_type == :proof_ssn analytics_args[:step_name] = 'verify_info' throttle_context = 'multi-session' end @@ -35,8 +35,8 @@ def track_rate_limited_event(throttle_type) analytics.throttler_rate_limit_triggered(**analytics_args) end - def rate_limited_redirect(throttle_type) - case throttle_type + def rate_limited_redirect(rate_limit_type) + case rate_limit_type when :idv_resolution redirect_to idv_session_errors_failure_url when :idv_doc_auth @@ -48,18 +48,18 @@ def rate_limited_redirect(throttle_type) end end - def idv_attempter_rate_limited?(throttle_type) - if throttle_type == :proof_ssn + def idv_attempter_rate_limited?(rate_limit_type) + if rate_limit_type == :proof_ssn return unless pii_ssn - Throttle.new( + RateLimiter.new( target: Pii::Fingerprinter.fingerprint(pii_ssn), - throttle_type: :proof_ssn, - ).throttled? + rate_limit_type: :proof_ssn, + ).limited? else - Throttle.new( + RateLimiter.new( user: idv_session_user, - throttle_type: throttle_type, - ).throttled? + rate_limit_type: rate_limit_type, + ).limited? end end diff --git a/app/controllers/idv/capture_doc_status_controller.rb b/app/controllers/idv/capture_doc_status_controller.rb index 2503771a2a2..2d226b0111c 100644 --- a/app/controllers/idv/capture_doc_status_controller.rb +++ b/app/controllers/idv/capture_doc_status_controller.rb @@ -16,7 +16,7 @@ def status :unauthorized elsif document_capture_session.cancelled_at :gone - elsif throttled? + elsif rate_limiter.limited? :too_many_requests elsif confirmed_barcode_attention_result? || user_has_establishing_in_person_enrollment? :ok @@ -33,7 +33,7 @@ def status def redirect_url return unless flow_session && document_capture_session - if throttled? + if rate_limiter.limited? idv_session_errors_throttled_url elsif user_has_establishing_in_person_enrollment? idv_in_person_url @@ -59,12 +59,11 @@ def document_capture_session_uuid flow_session[:document_capture_session_uuid] end - def throttled? - throttle.throttled? - end - - def throttle - @throttle ||= Throttle.new(user: document_capture_session.user, throttle_type: :idv_doc_auth) + def rate_limiter + @rate_limiter ||= RateLimiter.new( + user: document_capture_session.user, + rate_limit_type: :idv_doc_auth, + ) end def user_has_establishing_in_person_enrollment? diff --git a/app/controllers/idv/gpo_verify_controller.rb b/app/controllers/idv/gpo_verify_controller.rb index 5fd27f8dd2d..db9beadc674 100644 --- a/app/controllers/idv/gpo_verify_controller.rb +++ b/app/controllers/idv/gpo_verify_controller.rb @@ -18,8 +18,8 @@ def index !gpo_mail.mail_spammed? && !gpo_mail.profile_too_old? - if throttle.throttled? - render_throttled + if rate_limiter.limited? + render_rate_limited elsif pii_locked? redirect_to capture_password_url else @@ -34,9 +34,9 @@ def pii def create @gpo_verify_form = build_gpo_verify_form - throttle.increment! - if throttle.throttled? - render_throttled + rate_limiter.increment! + if rate_limiter.limited? + render_rate_limited return end @@ -85,20 +85,20 @@ def prepare_for_personal_key idv_session.address_confirmed! end - def throttle - @throttle ||= Throttle.new( + def rate_limiter + @rate_limiter ||= RateLimiter.new( user: current_user, - throttle_type: :verify_gpo_key, + rate_limit_type: :verify_gpo_key, ) end - def render_throttled + def render_rate_limited irs_attempts_api_tracker.idv_gpo_verification_rate_limited analytics.throttler_rate_limit_triggered( throttle_type: :verify_gpo_key, ) - @expires_at = throttle.expires_at + @expires_at = rate_limiter.expires_at render :throttled end diff --git a/app/controllers/idv/hybrid_handoff_controller.rb b/app/controllers/idv/hybrid_handoff_controller.rb index ebb61437318..2539f737034 100644 --- a/app/controllers/idv/hybrid_handoff_controller.rb +++ b/app/controllers/idv/hybrid_handoff_controller.rb @@ -38,8 +38,8 @@ def hybrid_flow_chosen? end def handle_phone_submission - throttle.increment! - return throttled_failure if throttle.throttled? + rate_limiter.increment! + return rate_limited_failure if rate_limiter.limited? idv_session.phone_for_mobile_flow = params[:doc_auth][:phone] idv_session.flow_path = 'hybrid' flow_session[:flow_path] = 'hybrid' # temp addition for 50/50 remove in future deploy @@ -147,10 +147,10 @@ def build_form ) end - def throttle - @throttle ||= Throttle.new( + def rate_limiter + @rate_limiter ||= RateLimiter.new( user: current_user, - throttle_type: :idv_send_link, + rate_limit_type: :idv_send_link, ) end @@ -175,7 +175,7 @@ def form_response(destination:) ) end - def throttled_failure + def rate_limited_failure analytics.throttler_rate_limit_triggered( throttle_type: :idv_send_link, ) @@ -183,7 +183,7 @@ def throttled_failure 'errors.doc_auth.send_link_throttle', timeout: distance_of_time_in_words( Time.zone.now, - [throttle.expires_at, Time.zone.now].compact.max, + [rate_limiter.expires_at, Time.zone.now].compact.max, except: :seconds, ), ) diff --git a/app/controllers/idv/phone_controller.rb b/app/controllers/idv/phone_controller.rb index bb93471307f..d1f5cd6ca05 100644 --- a/app/controllers/idv/phone_controller.rb +++ b/app/controllers/idv/phone_controller.rb @@ -19,7 +19,7 @@ def new async_state = step.async_state # It's possible that create redirected here after a success and left the - # throttle maxed out. Check for success before checking throttle. + # rate_limiter maxed out. Check for success before checking rate_limiter. return async_state_done(async_state) if async_state.done? render 'shared/wait' and return if async_state.in_progress? @@ -58,8 +58,8 @@ def create private - def throttle - @throttle ||= Throttle.new(user: current_user, throttle_type: :proof_address) + def rate_limiter + @rate_limiter ||= RateLimiter.new(user: current_user, rate_limit_type: :proof_address) end def redirect_to_next_step @@ -171,7 +171,7 @@ def async_state_done(async_state) ) if async_state.result[:success] - throttle.reset! + rate_limiter.reset! redirect_to_next_step and return end handle_proofing_failure diff --git a/app/controllers/idv/phone_errors_controller.rb b/app/controllers/idv/phone_errors_controller.rb index 58ae128cc96..fc5cc763c61 100644 --- a/app/controllers/idv/phone_errors_controller.rb +++ b/app/controllers/idv/phone_errors_controller.rb @@ -9,7 +9,7 @@ class PhoneErrorsController < ApplicationController before_action :ignore_form_step_wait_requests def warning - @remaining_attempts = throttle.remaining_count + @remaining_attempts = rate_limiter.remaining_count if idv_session.previous_phone_step_params @phone = idv_session.previous_phone_step_params[:phone] @@ -20,24 +20,24 @@ def warning end def timeout - @remaining_step_attempts = throttle.remaining_count + @remaining_step_attempts = rate_limiter.remaining_count track_event(type: :timeout) end def jobfail - @remaining_attempts = throttle.remaining_count + @remaining_attempts = rate_limiter.remaining_count track_event(type: :jobfail) end def failure - @expires_at = throttle.expires_at + @expires_at = rate_limiter.expires_at track_event(type: :failure) end private - def throttle - Throttle.new(user: idv_session.current_user, throttle_type: :proof_address) + def rate_limiter + RateLimiter.new(user: idv_session.current_user, rate_limit_type: :proof_address) end def confirm_idv_phone_step_needed diff --git a/app/controllers/idv/session_errors_controller.rb b/app/controllers/idv/session_errors_controller.rb index 1c21a82c1f2..69524de60a0 100644 --- a/app/controllers/idv/session_errors_controller.rb +++ b/app/controllers/idv/session_errors_controller.rb @@ -13,13 +13,13 @@ def exception end def warning - throttle = Throttle.new( + rate_limiter = RateLimiter.new( user: idv_session_user, - throttle_type: :idv_resolution, + rate_limit_type: :idv_resolution, ) - @remaining_attempts = throttle.remaining_count - log_event(based_on_throttle: throttle) + @remaining_attempts = rate_limiter.remaining_count + log_event(based_on_throttle: rate_limiter) end def state_id_warning @@ -27,34 +27,34 @@ def state_id_warning end def failure - throttle = Throttle.new( + rate_limiter = RateLimiter.new( user: idv_session_user, - throttle_type: :idv_resolution, + rate_limit_type: :idv_resolution, ) - @expires_at = throttle.expires_at + @expires_at = rate_limiter.expires_at @sp_name = decorated_session.sp_name - log_event(based_on_throttle: throttle) + log_event(based_on_throttle: rate_limiter) end def ssn_failure - throttle = nil + rate_limiter = nil if ssn_from_doc - throttle = Throttle.new( + rate_limiter = RateLimiter.new( target: Pii::Fingerprinter.fingerprint(ssn_from_doc), - throttle_type: :proof_ssn, + rate_limit_type: :proof_ssn, ) - @expires_at = throttle.expires_at + @expires_at = rate_limiter.expires_at end - log_event(based_on_throttle: throttle) + log_event(based_on_throttle: rate_limiter) render 'idv/session_errors/failure' end def throttled - throttle = Throttle.new(user: idv_session_user, throttle_type: :idv_doc_auth) - log_event(based_on_throttle: throttle) - @expires_at = throttle.expires_at + rate_limiter = RateLimiter.new(user: idv_session_user, rate_limit_type: :idv_doc_auth) + log_event(based_on_throttle: rate_limiter) + @expires_at = rate_limiter.expires_at end private diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index 070fc34e086..0d7650e7fea 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -31,11 +31,11 @@ def create return process_locked_out_session if session_bad_password_count_max_exceeded? return process_locked_out_user if current_user && user_locked_out?(current_user) - throttle_password_failure = true + rate_limit_password_failure = true self.resource = warden.authenticate!(auth_options) handle_valid_authentication ensure - increment_session_bad_password_count if throttle_password_failure && !current_user + increment_session_bad_password_count if rate_limit_password_failure && !current_user track_authentication_attempt(auth_params[:email]) end diff --git a/app/controllers/users/two_factor_authentication_controller.rb b/app/controllers/users/two_factor_authentication_controller.rb index 765980cc1cd..59c112056e8 100644 --- a/app/controllers/users/two_factor_authentication_controller.rb +++ b/app/controllers/users/two_factor_authentication_controller.rb @@ -264,17 +264,17 @@ def exceeded_otp_send_limit? return otp_rate_limiter.lock_out_user if otp_rate_limiter.exceeded_otp_send_limit? end - def phone_confirmation_throttle - @phone_confirmation_throttle ||= Throttle.new( + def phone_confirmation_rate_limiter + @phone_confirmation_rate_limiter ||= RateLimiter.new( user: current_user, - throttle_type: :phone_confirmation, + rate_limit_type: :phone_confirmation, ) end def exceeded_phone_confirmation_limit? return false unless UserSessionContext.confirmation_context?(context) - phone_confirmation_throttle.increment! - phone_confirmation_throttle.throttled? + phone_confirmation_rate_limiter.increment! + phone_confirmation_rate_limiter.limited? end def send_user_otp(method) @@ -369,7 +369,7 @@ def handle_too_many_confirmation_sends 'errors.messages.phone_confirmation_throttled', timeout: distance_of_time_in_words( Time.zone.now, - [phone_confirmation_throttle.expires_at, Time.zone.now].compact.max, + [phone_confirmation_rate_limiter.expires_at, Time.zone.now].compact.max, except: :seconds, ), ) diff --git a/app/controllers/users/verify_personal_key_controller.rb b/app/controllers/users/verify_personal_key_controller.rb index 68d173af9e1..50f56f51e91 100644 --- a/app/controllers/users/verify_personal_key_controller.rb +++ b/app/controllers/users/verify_personal_key_controller.rb @@ -13,17 +13,17 @@ def new personal_key: '', ) - if throttle.throttled? - render_throttled + if rate_limiter.limited? + render_rate_limited else render :new end end def create - throttle.increment! - if throttle.throttled? - render_throttled + rate_limiter.increment! + if rate_limiter.limited? + render_rate_limited else result = personal_key_form.submit @@ -45,21 +45,21 @@ def create private - def throttle - @throttle ||= Throttle.new( + def rate_limiter + @rate_limiter ||= RateLimiter.new( user: current_user, - throttle_type: :verify_personal_key, + rate_limit_type: :verify_personal_key, ) end - def render_throttled + def render_rate_limited analytics.throttler_rate_limit_triggered( throttle_type: :verify_personal_key, ) irs_attempts_api_tracker.personal_key_reactivation_rate_limited - @expires_at = throttle.expires_at + @expires_at = rate_limiter.expires_at render :throttled end diff --git a/app/forms/idv/api_image_upload_form.rb b/app/forms/idv/api_image_upload_form.rb index 15683954d0e..968da03566a 100644 --- a/app/forms/idv/api_image_upload_form.rb +++ b/app/forms/idv/api_image_upload_form.rb @@ -8,7 +8,7 @@ class ApiImageUploadForm validates_presence_of :document_capture_session validate :validate_images - validate :throttle_if_rate_limited + validate :limit_if_rate_limited def initialize(params, service_provider:, analytics: nil, uuid_prefix: nil, irs_attempts_api_tracker: nil, store_encrypted_images: false) @@ -32,7 +32,7 @@ def submit if client_response.success? doc_pii_response = validate_pii_from_doc(client_response) - throttle.reset! + rate_limiter.reset! end end @@ -52,15 +52,15 @@ def submit attr_reader :params, :analytics, :service_provider, :form_response, :uuid_prefix, :irs_attempts_api_tracker - def increment_throttle! + def increment_rate_limiter! return unless document_capture_session - throttle.increment! + rate_limiter.increment! end def validate_form success = valid? - increment_throttle! - track_rate_limited if throttled? + increment_rate_limiter! + track_rate_limited if rate_limited? response = Idv::DocAuthFormResponse.new( success: success, @@ -130,11 +130,11 @@ def extra_attributes end def remaining_attempts - throttle.remaining_count if document_capture_session + rate_limiter.remaining_count if document_capture_session end def attempts - throttle.attempts if document_capture_session + rate_limiter.attempts if document_capture_session end def determine_response(form_response:, client_response:, doc_pii_response:) @@ -184,9 +184,9 @@ def validate_images end end - def throttle_if_rate_limited + def limit_if_rate_limited return unless document_capture_session - return unless throttled? + return unless rate_limited? errors.add(:limit, t('errors.doc_auth.throttled_heading'), type: :throttled) end @@ -306,15 +306,15 @@ def user_uuid document_capture_session&.user&.uuid end - def throttle - @throttle ||= Throttle.new( + def rate_limiter + @rate_limiter ||= RateLimiter.new( user: document_capture_session.user, - throttle_type: :idv_doc_auth, + rate_limit_type: :idv_doc_auth, ) end - def throttled? - throttle.throttled? if document_capture_session + def rate_limited? + rate_limiter.limited? if document_capture_session end def track_event(response) diff --git a/app/forms/register_user_email_form.rb b/app/forms/register_user_email_form.rb index a84a3f9a3a6..c5682d73e4e 100644 --- a/app/forms/register_user_email_form.rb +++ b/app/forms/register_user_email_form.rb @@ -15,7 +15,7 @@ def self.model_name end def initialize(analytics:, attempts_tracker:, password_reset_requested: false) - @throttled = false + @rate_limited = false @password_reset_requested = password_reset_requested @analytics = analytics @attempts_tracker = attempts_tracker @@ -110,16 +110,16 @@ def extra_analytics_attributes email_already_exists: email_taken?, user_id: user.uuid || existing_user.uuid, domain_name: email&.split('@')&.last, - throttled: @throttled, + throttled: @rate_limited, } end def send_sign_up_unconfirmed_email(request_id) - throttler = Throttle.new(user: existing_user, throttle_type: :reg_unconfirmed_email) - throttler.increment! - @throttled = throttler.throttled? + rate_limiter = RateLimiter.new(user: existing_user, rate_limit_type: :reg_unconfirmed_email) + rate_limiter.increment! + @rate_limited = rate_limiter.limited? - if @throttled + if @rate_limited @analytics.throttler_rate_limit_triggered( throttle_type: :reg_unconfirmed_email, ) @@ -132,11 +132,11 @@ def send_sign_up_unconfirmed_email(request_id) end def send_sign_up_confirmed_email - throttler = Throttle.new(user: existing_user, throttle_type: :reg_confirmed_email) - throttler.increment! - @throttled = throttler.throttled? + rate_limiter = RateLimiter.new(user: existing_user, rate_limit_type: :reg_confirmed_email) + rate_limiter.increment! + @rate_limited = rate_limiter.limited? - if @throttled + if @rate_limited @analytics.throttler_rate_limit_triggered( throttle_type: :reg_confirmed_email, ) diff --git a/app/services/idv/phone_step.rb b/app/services/idv/phone_step.rb index 2265d1c8d19..caf6f1fc4c9 100644 --- a/app/services/idv/phone_step.rb +++ b/app/services/idv/phone_step.rb @@ -8,8 +8,8 @@ def initialize(idv_session:, trace_id:, analytics:, attempts_tracker:) end def submit(step_params) - return throttled_result if throttle.throttled? - throttle.increment! + return rate_limited_result if rate_limiter.limited? + rate_limiter.increment! self.step_params = step_params idv_session.previous_phone_step_params = step_params.slice( @@ -20,7 +20,7 @@ def submit(step_params) end def failure_reason - return :fail if throttle.throttled? + return :fail if rate_limiter.limited? return :no_idv_result if idv_result.nil? return :timeout if idv_result[:timed_out] return :jobfail if idv_result[:exception].present? @@ -108,11 +108,14 @@ def otp_delivery_preference preference.to_sym end - def throttle - @throttle ||= Throttle.new(user: idv_session.current_user, throttle_type: :proof_address) + def rate_limiter + @rate_limiter ||= RateLimiter.new( + user: idv_session.current_user, + rate_limit_type: :proof_address, + ) end - def throttled_result + def rate_limited_result @attempts_tracker.idv_phone_otp_sent_rate_limited @analytics.throttler_rate_limit_triggered(throttle_type: :proof_address, step_name: :phone) FormResponse.new(success: false) diff --git a/app/services/idv/steps/doc_auth_base_step.rb b/app/services/idv/steps/doc_auth_base_step.rb index 8d244a258d1..05563836e3a 100644 --- a/app/services/idv/steps/doc_auth_base_step.rb +++ b/app/services/idv/steps/doc_auth_base_step.rb @@ -53,19 +53,19 @@ def hybrid_flow_mobile? user_id_from_token.present? end - def throttled_response + def rate_limited_response @flow.analytics.throttler_rate_limit_triggered( throttle_type: :idv_doc_auth, ) @flow.irs_attempts_api_tracker.idv_document_upload_rate_limited - redirect_to throttled_url + redirect_to rate_limited_url DocAuth::Response.new( success: false, errors: { limit: I18n.t('errors.doc_auth.throttled_heading') }, ) end - def throttled_url + def rate_limited_url idv_session_errors_throttled_url end diff --git a/app/services/otp_rate_limiter.rb b/app/services/otp_rate_limiter.rb index b0aa75d31d7..192536da69f 100644 --- a/app/services/otp_rate_limiter.rb +++ b/app/services/otp_rate_limiter.rb @@ -15,15 +15,15 @@ def exceeded_otp_send_limit? end def max_requests_reached? - throttle.throttled? + rate_limiter.limited? end def rate_limit_period_expired? - throttle.expired? + rate_limiter.expired? end def reset_count_and_otp_last_sent_at - throttle.reset! + rate_limiter.reset! end def lock_out_user @@ -31,15 +31,15 @@ def lock_out_user end def increment - throttle.increment! + rate_limiter.increment! end def otp_last_sent_at - throttle.attempted_at + rate_limiter.attempted_at end - def throttle - @throttle ||= Throttle.new(throttle_type: :phone_otp, target: throttle_key) + def rate_limiter + @rate_limiter ||= RateLimiter.new(rate_limit_type: :phone_otp, target: rate_limit_key) end private @@ -58,7 +58,7 @@ def phone_fingerprint @phone_fingerprint ||= Pii::Fingerprinter.fingerprint(PhoneFormatter.format(phone)) end - def throttle_key + def rate_limit_key "#{phone_fingerprint}:#{phone_confirmed}" end end diff --git a/app/services/throttle.rb b/app/services/rate_limiter.rb similarity index 71% rename from app/services/throttle.rb rename to app/services/rate_limiter.rb index 577e7900244..6e7a0f8b656 100644 --- a/app/services/throttle.rb +++ b/app/services/rate_limiter.rb @@ -1,24 +1,24 @@ # This class is similar to RedisRateLimiter, but differs in that -# the throttle period begins once the maximum number of allowed +# the rate limit period begins once the maximum number of allowed # attempts has been reached. -class Throttle - attr_reader :throttle_type +class RateLimiter + attr_reader :rate_limit_type - def initialize(throttle_type:, user: nil, target: nil) - @throttle_type = throttle_type + def initialize(rate_limit_type:, user: nil, target: nil) + @rate_limit_type = rate_limit_type @user = user @target = target - unless Throttle.throttle_config.key?(throttle_type) + unless RateLimiter.rate_limit_config.key?(rate_limit_type) raise ArgumentError, - 'throttle_type is not valid' + 'rate_limit_type is not valid' end if @user.blank? && @target.blank? - raise ArgumentError, 'Throttle must have a user or a target, but neither were provided' + raise ArgumentError, 'RateLimiter must have a user or a target, but neither were provided' end if @user.present? && @target.present? - raise ArgumentError, 'Throttle must have a user or a target, but both were provided' + raise ArgumentError, 'RateLimiter must have a user or a target, but both were provided' end if target && !target.is_a?(String) @@ -35,7 +35,7 @@ def attempts @redis_attempts.to_i end - def throttled? + def limited? !expired? && maxed? end @@ -49,13 +49,13 @@ def attempted_at def expires_at return Time.zone.now if attempted_at.blank? - attempted_at + Throttle.attempt_window_in_minutes(throttle_type).minutes + attempted_at + RateLimiter.attempt_window_in_minutes(rate_limit_type).minutes end def remaining_count - return 0 if throttled? + return 0 if limited? - Throttle.max_attempts(throttle_type) - attempts + RateLimiter.max_attempts(rate_limit_type) - attempts end def expired? @@ -63,11 +63,11 @@ def expired? end def maxed? - attempts && attempts >= Throttle.max_attempts(throttle_type) + attempts && attempts >= RateLimiter.max_attempts(rate_limit_type) end def increment! - return if throttled? + return if limited? value = nil REDIS_THROTTLE_POOL.with do |client| @@ -75,7 +75,7 @@ def increment! multi.incr(key) multi.expire( key, - Throttle.attempt_window_in_minutes(throttle_type).minutes.seconds.to_i, + RateLimiter.attempt_window_in_minutes(rate_limit_type).minutes.seconds.to_i, ) end end @@ -86,7 +86,7 @@ def increment! attempts end - # Retrieve the current state of the throttle from Redis + # Retrieve the current state of the rate limit from Redis # We use EXPIRETIME to calculate when the action was last attempted. def fetch_state! value = nil @@ -105,7 +105,7 @@ def fetch_state! else @redis_attempted_at = ActiveSupport::TimeZone['UTC'].at(expiretime).in_time_zone(Time.zone) - - Throttle.attempt_window_in_minutes(throttle_type).minutes + RateLimiter.attempt_window_in_minutes(rate_limit_type).minutes end self @@ -120,15 +120,16 @@ def reset! @redis_attempted_at = nil end - def increment_to_throttled! - value = Throttle.max_attempts(throttle_type) + def increment_to_limited! + value = RateLimiter.max_attempts(rate_limit_type) now = Time.zone.now REDIS_THROTTLE_POOL.with do |client| client.set( key, value, - exat: now.to_i + Throttle.attempt_window_in_minutes(throttle_type).minutes.seconds.to_i, + exat: now.to_i + + RateLimiter.attempt_window_in_minutes(rate_limit_type).minutes.seconds.to_i, ) end @@ -138,31 +139,32 @@ def increment_to_throttled! attempts end + # still uses throttle terminology because of persisted data in redis def key if @user - "throttle:throttle:#{@user.id}:#{throttle_type}" + "throttle:throttle:#{@user.id}:#{rate_limit_type}" else - "throttle:throttle:#{@target}:#{throttle_type}" + "throttle:throttle:#{@target}:#{rate_limit_type}" end end - def self.attempt_window_in_minutes(throttle_type) - throttle_config.dig(throttle_type, :attempt_window) + def self.attempt_window_in_minutes(rate_limit_type) + rate_limit_config.dig(rate_limit_type, :attempt_window) end - def self.max_attempts(throttle_type) - throttle_config.dig(throttle_type, :max_attempts) + def self.max_attempts(rate_limit_type) + rate_limit_config.dig(rate_limit_type, :max_attempts) end - def self.throttle_config + def self.rate_limit_config if Rails.env.production? - CACHED_THROTTLE_CONFIG + CACHED_RATE_LIMIT_CONFIG else - load_throttle_config + load_rate_limit_config end end - def self.load_throttle_config + def self.load_rate_limit_config { idv_doc_auth: { max_attempts: IdentityConfig.store.doc_auth_max_attempts, @@ -215,5 +217,5 @@ def self.load_throttle_config }.with_indifferent_access end - CACHED_THROTTLE_CONFIG = self.load_throttle_config.with_indifferent_access.freeze + CACHED_RATE_LIMIT_CONFIG = self.load_rate_limit_config.with_indifferent_access.freeze end diff --git a/app/services/request_password_reset.rb b/app/services/request_password_reset.rb index aa2b588d369..ba59e2f0a25 100644 --- a/app/services/request_password_reset.rb +++ b/app/services/request_password_reset.rb @@ -21,9 +21,9 @@ def perform private def send_reset_password_instructions - throttle = Throttle.new(user: user, throttle_type: :reset_password_email) - throttle.increment! - if throttle.throttled? + rate_limiter = RateLimiter.new(user: user, rate_limit_type: :reset_password_email) + rate_limiter.increment! + if rate_limiter.limited? analytics.throttler_rate_limit_triggered(throttle_type: :reset_password_email) irs_attempts_api_tracker.forgot_password_email_rate_limited(email: email) else diff --git a/spec/controllers/concerns/rate_limit_concern_spec.rb b/spec/controllers/concerns/rate_limit_concern_spec.rb index 8020d96a902..b34aa79b3c7 100644 --- a/spec/controllers/concerns/rate_limit_concern_spec.rb +++ b/spec/controllers/concerns/rate_limit_concern_spec.rb @@ -33,7 +33,7 @@ def update end end - context 'user is not throttled' do + context 'user is not rate limited' do let(:user) { create(:user, :fully_registered) } it 'does not redirect' do @@ -44,10 +44,10 @@ def update end end - context 'with idv_doc_auth throttle (DocumentCapture)' do - it 'redirects to idv_doc_auth throttled error page' do - throttle = Throttle.new(user: user, throttle_type: :idv_doc_auth) - throttle.increment_to_throttled! + context 'with idv_doc_auth rate_limiter (DocumentCapture)' do + 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! get :show @@ -55,10 +55,10 @@ def update end end - context 'with idv_resolution throttle (VerifyInfo)' do - it 'redirects to idv_resolution throttled error page' do - throttle = Throttle.new(user: user, throttle_type: :idv_resolution) - throttle.increment_to_throttled! + context 'with idv_resolution rate_limiter (VerifyInfo)' do + it 'redirects to idv_resolution rate limited error page' do + rate_limiter = RateLimiter.new(user: user, rate_limit_type: :idv_resolution) + rate_limiter.increment_to_limited! get :show @@ -66,13 +66,13 @@ def update end end - context 'with proof_address throttle (PhoneStep)' do + context 'with proof_address rate_limiter (PhoneStep)' do before do - throttle = Throttle.new(user: user, throttle_type: :proof_address) - throttle.increment_to_throttled! + rate_limiter = RateLimiter.new(user: user, rate_limit_type: :proof_address) + rate_limiter.increment_to_limited! end - it 'redirects to proof_address throttled error page' do + it 'redirects to proof_address rate limited error page' do get :show expect(response).to redirect_to idv_phone_errors_failure_url diff --git a/spec/controllers/idv/capture_doc_status_controller_spec.rb b/spec/controllers/idv/capture_doc_status_controller_spec.rb index a6909af3369..47ac01691fd 100644 --- a/spec/controllers/idv/capture_doc_status_controller_spec.rb +++ b/spec/controllers/idv/capture_doc_status_controller_spec.rb @@ -59,12 +59,12 @@ end end - context 'when the user is throttled' do + context 'when the user is rate limited' do before do - Throttle.new(throttle_type: :idv_doc_auth, user: user).increment_to_throttled! + RateLimiter.new(rate_limit_type: :idv_doc_auth, user: user).increment_to_limited! end - it 'returns throttled with redirect' do + it 'returns rate_limited with redirect' do get :show expect(response.status).to eq(429) diff --git a/spec/controllers/idv/document_capture_controller_spec.rb b/spec/controllers/idv/document_capture_controller_spec.rb index 31b22244348..370ddce6c0a 100644 --- a/spec/controllers/idv/document_capture_controller_spec.rb +++ b/spec/controllers/idv/document_capture_controller_spec.rb @@ -123,11 +123,11 @@ get :show end - context 'user is rate_limited' do + context 'user is rate limited' do it 'redirects to rate limited page' do user = create(:user) - Throttle.new(throttle_type: :idv_doc_auth, user: user).increment_to_throttled! + RateLimiter.new(rate_limit_type: :idv_doc_auth, user: user).increment_to_limited! allow(subject).to receive(:current_user).and_return(user) get :show diff --git a/spec/controllers/idv/gpo_verify_controller_spec.rb b/spec/controllers/idv/gpo_verify_controller_spec.rb index 7db2a9d2531..b9d5cae96ec 100644 --- a/spec/controllers/idv/gpo_verify_controller_spec.rb +++ b/spec/controllers/idv/gpo_verify_controller_spec.rb @@ -58,8 +58,8 @@ expect(assigns(:user_can_request_another_gpo_code)).to eql(true) end - it 'shows throttled page is user is throttled' do - Throttle.new(throttle_type: :verify_gpo_key, user: user).increment_to_throttled! + it 'shows rate limited page if user is rate limited' do + RateLimiter.new(rate_limit_type: :verify_gpo_key, user: user).increment_to_limited! action @@ -85,12 +85,12 @@ end end - context 'with throttle reached' do + context 'with rate limit reached' do before do - Throttle.new(throttle_type: :verify_gpo_key, user: user).increment_to_throttled! + RateLimiter.new(rate_limit_type: :verify_gpo_key, user: user).increment_to_limited! end - it 'renders throttled page' do + it 'renders rate limited page' do expect(@analytics).to receive(:track_event).with( 'IdV: GPO verification visited', ).once @@ -329,7 +329,7 @@ end end - context 'with throttle reached' do + context 'with rate limit reached' do let(:submitted_otp) { 'a-wrong-otp' } it 'renders the index page to show errors' do diff --git a/spec/controllers/idv/image_uploads_controller_spec.rb b/spec/controllers/idv/image_uploads_controller_spec.rb index f1c34aab70a..9f6f103165d 100644 --- a/spec/controllers/idv/image_uploads_controller_spec.rb +++ b/spec/controllers/idv/image_uploads_controller_spec.rb @@ -186,7 +186,7 @@ context 'throttling' do it 'returns remaining_attempts with error' do params.delete(:front) - Throttle.new(throttle_type: :idv_doc_auth, user: user).increment! + RateLimiter.new(rate_limit_type: :idv_doc_auth, user: user).increment! action @@ -195,14 +195,14 @@ { success: false, errors: [{ field: 'front', message: 'Please fill in this field.' }], - remaining_attempts: Throttle.max_attempts(:idv_doc_auth) - 2, + remaining_attempts: RateLimiter.max_attempts(:idv_doc_auth) - 2, result_failed: false, ocr_pii: nil, }, ) end - context 'when throttled' do + context 'when rate limited' do let(:redirect_url) { idv_session_errors_throttled_url } let(:error_json) do { @@ -216,7 +216,7 @@ end before do - Throttle.new(throttle_type: :idv_doc_auth, user: user).increment_to_throttled! + RateLimiter.new(rate_limit_type: :idv_doc_auth, user: user).increment_to_limited! action end @@ -238,7 +238,7 @@ end it 'tracks events' do - Throttle.new(throttle_type: :idv_doc_auth, user: user).increment_to_throttled! + RateLimiter.new(rate_limit_type: :idv_doc_auth, user: user).increment_to_limited! stub_analytics stub_attempts_tracker 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 a02aaf946f3..c19ca5bfaf6 100644 --- a/spec/controllers/idv/in_person/verify_info_controller_spec.rb +++ b/spec/controllers/idv/in_person/verify_info_controller_spec.rb @@ -166,7 +166,7 @@ and_return(10) end - it 'throttles them all' do + it 'rate limits them all' do put :update subject.idv_session.verify_info_step_document_capture_session_uuid = nil put :update diff --git a/spec/controllers/idv/phone_controller_spec.rb b/spec/controllers/idv/phone_controller_spec.rb index 80a355db29b..274909c3cc7 100644 --- a/spec/controllers/idv/phone_controller_spec.rb +++ b/spec/controllers/idv/phone_controller_spec.rb @@ -3,7 +3,7 @@ RSpec.describe Idv::PhoneController do include IdvHelper - let(:max_attempts) { Throttle.max_attempts(:proof_address) } + let(:max_attempts) { RateLimiter.max_attempts(:proof_address) } let(:good_phone) { '+1 (703) 555-0000' } let(:bad_phone) do Proofing::Mock::AddressMockClient::UNVERIFIABLE_PHONE_NUMBER @@ -97,9 +97,9 @@ end end - context 'when the user is throttled' do + context 'when the user is rate limited' do before do - Throttle.new(throttle_type: :proof_address, user: user).increment_to_throttled! + RateLimiter.new(rate_limit_type: :proof_address, user: user).increment_to_limited! end it 'redirects to fail' do @@ -484,15 +484,15 @@ get :new end - context 'when the user is throttled by submission' do + context 'when the user is rate limited by submission' do before do stub_analytics user = create(:user, with: { phone: '+1 (415) 555-0130' }) stub_verify_steps_one_and_two(user) - throttle = Throttle.new(throttle_type: :proof_address, user: user) - throttle.increment_to_throttled! + rate_limiter = RateLimiter.new(rate_limit_type: :proof_address, user: user) + rate_limiter.increment_to_limited! put :create, params: { idv_phone_form: { phone: bad_phone } } end @@ -501,7 +501,7 @@ expect(response).to redirect_to idv_phone_errors_failure_url end - it 'tracks throttled event' do + it 'tracks rate limited event' do expect(@analytics).to have_logged_event( 'Throttler Rate Limit Triggered', { diff --git a/spec/controllers/idv/phone_errors_controller_spec.rb b/spec/controllers/idv/phone_errors_controller_spec.rb index 85b3d2bc316..dfe479cbc3e 100644 --- a/spec/controllers/idv/phone_errors_controller_spec.rb +++ b/spec/controllers/idv/phone_errors_controller_spec.rb @@ -131,9 +131,9 @@ end end - context 'with throttle attempts' do + context 'with rate limit attempts' do before do - Throttle.new(throttle_type: :proof_address, user: user).increment! + RateLimiter.new(rate_limit_type: :proof_address, user: user).increment! end it 'assigns remaining count' do @@ -160,11 +160,11 @@ it_behaves_like 'an idv phone errors controller action' - context 'with throttle attempts' do + context 'with rate limit attempts' do let(:user) { create(:user) } before do - Throttle.new(throttle_type: :proof_address, user: user).increment! + RateLimiter.new(rate_limit_type: :proof_address, user: user).increment! end it 'assigns remaining count' do @@ -181,11 +181,11 @@ it_behaves_like 'an idv phone errors controller action' - context 'with throttle attempts' do + context 'with rate limit attempts' do let(:user) { create(:user) } before do - Throttle.new(throttle_type: :proof_address, user: user).increment! + RateLimiter.new(rate_limit_type: :proof_address, user: user).increment! end it 'assigns remaining count' do @@ -212,11 +212,11 @@ it_behaves_like 'an idv phone errors controller action' - context 'while throttled' do + context 'while rate limited' do let(:user) { create(:user) } it 'assigns expiration time' do - Throttle.new(throttle_type: :proof_address, user: user).increment_to_throttled! + RateLimiter.new(rate_limit_type: :proof_address, user: user).increment_to_limited! get action expect(assigns(:expires_at)).to be_kind_of(Time) @@ -225,15 +225,15 @@ it 'logs an event' do freeze_time do attempted_at = Time.zone.now.utc - Throttle.new(throttle_type: :proof_address, user: user).increment_to_throttled! - throttle_window = Throttle.attempt_window_in_minutes(:proof_address).minutes + RateLimiter.new(rate_limit_type: :proof_address, user: user).increment_to_limited! + rate_limit_window = RateLimiter.attempt_window_in_minutes(:proof_address).minutes get action expect(@analytics).to have_received(:track_event).with( 'IdV: phone error visited', type: action, - throttle_expires_at: attempted_at + throttle_window, + throttle_expires_at: attempted_at + rate_limit_window, ) end end diff --git a/spec/controllers/idv/session_errors_controller_spec.rb b/spec/controllers/idv/session_errors_controller_spec.rb index ead5955b9e6..5abc75b1b5d 100644 --- a/spec/controllers/idv/session_errors_controller_spec.rb +++ b/spec/controllers/idv/session_errors_controller_spec.rb @@ -155,11 +155,11 @@ it_behaves_like 'an idv session errors controller action' - context 'with throttle attempts' do + context 'with rate limit attempts' do let(:user) { create(:user) } before do - Throttle.new(throttle_type: :proof_address, user: user).increment! + RateLimiter.new(rate_limit_type: :proof_address, user: user).increment! end it 'assigns remaining count' do @@ -231,11 +231,11 @@ it_behaves_like 'an idv session errors controller action' - context 'while throttled' do + context 'while rate limited' do let(:user) { create(:user) } before do - Throttle.new(throttle_type: :proof_address, user: user).increment_to_throttled! + RateLimiter.new(rate_limit_type: :proof_address, user: user).increment_to_limited! end it 'assigns expiration time' do @@ -271,7 +271,7 @@ it_behaves_like 'an idv session errors controller action' - context 'while throttled' do + context 'while rate limited' do let(:user) { build(:user) } let(:ssn) { '666666666' } @@ -280,10 +280,10 @@ end before do - Throttle.new( - throttle_type: :proof_ssn, + RateLimiter.new( + rate_limit_type: :proof_ssn, target: Pii::Fingerprinter.fingerprint(ssn), - ).increment_to_throttled! + ).increment_to_limited! controller.user_session['idv/doc_auth'] = { 'pii_from_doc' => { 'ssn' => ssn } } end @@ -312,11 +312,11 @@ it_behaves_like 'an idv session errors controller action' - context 'while throttled' do + context 'while rate limited' do let(:user) { create(:user) } before do - Throttle.new(throttle_type: :idv_doc_auth, user: user).increment_to_throttled! + RateLimiter.new(rate_limit_type: :idv_doc_auth, user: user).increment_to_limited! end it 'assigns expiration time' do diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb index 3e10e4aebac..05ee06b76e7 100644 --- a/spec/controllers/idv/verify_info_controller_spec.rb +++ b/spec/controllers/idv/verify_info_controller_spec.rb @@ -126,14 +126,14 @@ expect(response).to redirect_to(idv_ssn_url) end - context 'when the user is ssn throttled' do + context 'when the user is ssn rate limited' do before do - Throttle.new( + RateLimiter.new( target: Pii::Fingerprinter.fingerprint( Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN[:ssn], ), - throttle_type: :proof_ssn, - ).increment_to_throttled! + rate_limit_type: :proof_ssn, + ).increment_to_limited! end it 'redirects to ssn failure url' do @@ -150,15 +150,15 @@ end end - context 'when the user is proofing throttled' do + context 'when the user is proofing rate limited' do before do - Throttle.new( + RateLimiter.new( user: subject.current_user, - throttle_type: :idv_resolution, - ).increment_to_throttled! + rate_limit_type: :idv_resolution, + ).increment_to_limited! end - it 'redirects to throttled url' do + it 'redirects to rate limited url' do get :show expect(response).to redirect_to idv_session_errors_failure_url @@ -422,14 +422,14 @@ ) end - context 'when the user is ssn throttled' do + context 'when the user is ssn rate limited' do before do - Throttle.new( + RateLimiter.new( target: Pii::Fingerprinter.fingerprint( Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN[:ssn], ), - throttle_type: :proof_ssn, - ).increment_to_throttled! + rate_limit_type: :proof_ssn, + ).increment_to_limited! end it 'redirects to ssn failure url' do @@ -446,15 +446,15 @@ end end - context 'when the user is proofing throttled' do + context 'when the user is proofing rate limited' do before do - Throttle.new( + RateLimiter.new( user: subject.current_user, - throttle_type: :idv_resolution, - ).increment_to_throttled! + rate_limit_type: :idv_resolution, + ).increment_to_limited! end - it 'redirects to throttled url' do + it 'redirects to rate limited url' do put :update expect(response).to redirect_to idv_session_errors_failure_url diff --git a/spec/controllers/idv_controller_spec.rb b/spec/controllers/idv_controller_spec.rb index 940920bcebf..9beab394d08 100644 --- a/spec/controllers/idv_controller_spec.rb +++ b/spec/controllers/idv_controller_spec.rb @@ -54,7 +54,7 @@ :profile, user: user, ) - Throttle.new(throttle_type: :idv_resolution, user: user).increment_to_throttled! + RateLimiter.new(rate_limit_type: :idv_resolution, user: user).increment_to_limited! stub_sign_in(profile.user) end @@ -81,12 +81,12 @@ :profile, user: user, ) - Throttle.new(throttle_type: :idv_doc_auth, user: user).increment_to_throttled! + RateLimiter.new(rate_limit_type: :idv_doc_auth, user: user).increment_to_limited! stub_sign_in(profile.user) end - it 'redirects to throttled page' do + it 'redirects to rate limited page' do get :index expect(response).to redirect_to idv_session_errors_throttled_url @@ -100,12 +100,12 @@ :profile, user: user, ) - Throttle.new(throttle_type: :proof_address, user: user).increment_to_throttled! + RateLimiter.new(rate_limit_type: :proof_address, user: user).increment_to_limited! stub_sign_in(profile.user) end - it 'redirects to throttled page' do + it 'redirects to rate limited page' do get :index expect(response).to redirect_to idv_phone_errors_failure_url diff --git a/spec/controllers/users/two_factor_authentication_controller_spec.rb b/spec/controllers/users/two_factor_authentication_controller_spec.rb index d769ca75103..4ab40b4e664 100644 --- a/spec/controllers/users/two_factor_authentication_controller_spec.rb +++ b/spec/controllers/users/two_factor_authentication_controller_spec.rb @@ -619,7 +619,7 @@ def index end timeout = distance_of_time_in_words( - Throttle.attempt_window_in_minutes(:phone_confirmation).minutes, + RateLimiter.attempt_window_in_minutes(:phone_confirmation).minutes, ) expect(flash[:error]).to eq( @@ -672,7 +672,7 @@ def index end timeout = distance_of_time_in_words( - Throttle.attempt_window_in_minutes(:phone_confirmation).minutes, + RateLimiter.attempt_window_in_minutes(:phone_confirmation).minutes, ) expect(flash[:error]).to eq( diff --git a/spec/controllers/users/verify_personal_key_controller_spec.rb b/spec/controllers/users/verify_personal_key_controller_spec.rb index 0262334b471..cfe620dc3a5 100644 --- a/spec/controllers/users/verify_personal_key_controller_spec.rb +++ b/spec/controllers/users/verify_personal_key_controller_spec.rb @@ -36,8 +36,8 @@ expect(subject.flash[:info]).to eq(t('notices.account_reactivation')) end - it 'shows throttled page after being throttled' do - Throttle.new(throttle_type: :verify_personal_key, user: user).increment_to_throttled! + it 'shows rate limited page after being rate limited' do + RateLimiter.new(rate_limit_type: :verify_personal_key, user: user).increment_to_limited! get :new @@ -45,14 +45,14 @@ end end - context 'with throttle reached' do + context 'with rate limit reached' do let!(:profiles) { [create(:profile, :verified, :password_reset, user: user)] } before do - Throttle.new(throttle_type: :verify_personal_key, user: user).increment_to_throttled! + RateLimiter.new(rate_limit_type: :verify_personal_key, user: user).increment_to_limited! end - it 'renders throttled page' do + it 'renders rate limited page' do stub_analytics stub_attempts_tracker expect(@analytics).to receive(:track_event).with( @@ -156,8 +156,8 @@ end end - context 'with throttle reached' do - it 'renders throttled page' do + context 'with rate limit reached' do + it 'renders rate limited page' do stub_analytics stub_attempts_tracker expect(@analytics).to receive(:track_event).with( @@ -174,7 +174,7 @@ expect(@irs_attempts_api_tracker).to receive(:personal_key_reactivation_rate_limited).once - max_attempts = Throttle.max_attempts(:verify_personal_key) + max_attempts = RateLimiter.max_attempts(:verify_personal_key) max_attempts.times { post :create, params: personal_key_bad_params } expect(response).to render_template(:throttled) diff --git a/spec/features/idv/doc_auth/document_capture_spec.rb b/spec/features/idv/doc_auth/document_capture_spec.rb index c7295e76e73..87751c5f77f 100644 --- a/spec/features/idv/doc_auth/document_capture_spec.rb +++ b/spec/features/idv/doc_auth/document_capture_spec.rb @@ -72,7 +72,7 @@ end end - context 'throttles calls to acuant', allow_browser_log: true do + context 'rate limits calls to acuant', allow_browser_log: true do let(:fake_attempts_tracker) { IrsAttemptsApiTrackingHelper::FakeAttemptsTracker.new } before do allow_any_instance_of(ApplicationController).to receive( @@ -94,11 +94,11 @@ end end - it 'redirects to the throttled error page' do + it 'redirects to the rate limited error page' do freeze_time do attach_and_submit_images timeout = distance_of_time_in_words( - Throttle.attempt_window_in_minutes(:idv_doc_auth).minutes, + RateLimiter.attempt_window_in_minutes(:idv_doc_auth).minutes, ) message = strip_tags(t('errors.doc_auth.throttled_text_html', timeout: timeout)) expect(page).to have_content(message) @@ -106,7 +106,7 @@ end end - it 'logs the throttled analytics event for doc_auth' do + it 'logs the rate limited analytics event for doc_auth' do attach_and_submit_images expect(fake_analytics).to have_logged_event( 'Throttler Rate Limit Triggered', diff --git a/spec/features/idv/doc_auth/hybrid_handoff_spec.rb b/spec/features/idv/doc_auth/hybrid_handoff_spec.rb index 727e8d000d9..de629566fd8 100644 --- a/spec/features/idv/doc_auth/hybrid_handoff_spec.rb +++ b/spec/features/idv/doc_auth/hybrid_handoff_spec.rb @@ -167,12 +167,12 @@ expect(page.find(':focus')).to match_css('.phone-input__number') end - it 'throttles sending the link' do + it 'rate limits sending the link' do user = user_with_2fa sign_in_and_2fa_user(user) complete_doc_auth_steps_before_hybrid_handoff_step timeout = distance_of_time_in_words( - Throttle.attempt_window_in_minutes(:idv_send_link).minutes, + RateLimiter.attempt_window_in_minutes(:idv_send_link).minutes, ) allow(IdentityConfig.store).to receive(:idv_send_link_max_attempts). and_return(idv_send_link_max_attempts) @@ -211,9 +211,9 @@ throttle_type: :idv_send_link, ) - # Manual expiration is needed for now since the Throttle uses + # Manual expiration is needed for now since the RateLimiter uses # Redis ttl instead of expiretime - Throttle.new(throttle_type: :idv_send_link, user: user).reset! + RateLimiter.new(rate_limit_type: :idv_send_link, user: user).reset! travel_to(Time.zone.now + idv_send_link_attempt_window_in_minutes.minutes) do fill_in :doc_auth_phone, with: '415-555-0199' click_send_link 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 750810c695b..db01993d916 100644 --- a/spec/features/idv/doc_auth/verify_info_step_spec.rb +++ b/spec/features/idv/doc_auth/verify_info_step_spec.rb @@ -181,7 +181,7 @@ end # proof_ssn_max_attempts is 10, vs 5 for resolution, so it doesn't get triggered - it 'throttles resolution and continues when it expires' do + it 'rate limits resolution and continues when it expires' do expect(fake_attempts_tracker).to receive(:idv_verification_rate_limited).at_least(1).times. with({ throttle_context: 'single-session' }) @@ -246,7 +246,7 @@ end end - it 'throttles ssn and continues when it expires' do + it 'rate limits ssn and continues when it expires' do expect(fake_attempts_tracker).to receive(:idv_verification_rate_limited).at_least(1).times. with({ throttle_context: 'multi-session' }) click_idv_continue diff --git a/spec/features/idv/phone_otp_rate_limiting_spec.rb b/spec/features/idv/phone_otp_rate_limiting_spec.rb index 8cf18b2a1e8..5f4e2bd8bca 100644 --- a/spec/features/idv/phone_otp_rate_limiting_spec.rb +++ b/spec/features/idv/phone_otp_rate_limiting_spec.rb @@ -10,7 +10,7 @@ start_idv_from_sp complete_idv_steps_before_phone_otp_verification_step(user) - (Throttle.max_attempts(:phone_otp) - 1).times do + (RateLimiter.max_attempts(:phone_otp) - 1).times do click_on t('links.two_factor_authentication.send_another_code') end @@ -67,8 +67,8 @@ def expect_rate_limit_to_expire(user) retry_minutes = IdentityConfig.store.lockout_period_in_minutes + 1 travel_to(retry_minutes.minutes.from_now) do # This is not good and we can likely drop it once we have upgraded to Redis 7 and switched - # Throttle to use EXPIRETIME rather than TTL - allow_any_instance_of(Throttle).to receive(:attempted_at).and_return( + # RateLimiter to use EXPIRETIME rather than TTL + allow_any_instance_of(RateLimiter).to receive(:attempted_at).and_return( retry_minutes.minutes.ago, ) start_idv_from_sp diff --git a/spec/features/idv/steps/phone_step_spec.rb b/spec/features/idv/steps/phone_step_spec.rb index 4a6ff06afd3..4ed28ba7da1 100644 --- a/spec/features/idv/steps/phone_step_spec.rb +++ b/spec/features/idv/steps/phone_step_spec.rb @@ -227,7 +227,7 @@ end before do - (Throttle.max_attempts(:proof_address) - 1).times do + (RateLimiter.max_attempts(:proof_address) - 1).times do fill_out_phone_form_fail click_idv_continue_for_step(:phone) click_on t('idv.failure.phone.warning.try_again_button') diff --git a/spec/features/visitors/password_recovery_spec.rb b/spec/features/visitors/password_recovery_spec.rb index 554649423c4..14b66cc2c3e 100644 --- a/spec/features/visitors/password_recovery_spec.rb +++ b/spec/features/visitors/password_recovery_spec.rb @@ -264,7 +264,7 @@ expect(current_path).to eq new_user_password_path end - it 'throttles reset passwords requests' do + it 'rate limits reset passwords requests' do user = create(:user, :fully_registered) email = user.email diff --git a/spec/features/visitors/resend_email_confirmation_spec.rb b/spec/features/visitors/resend_email_confirmation_spec.rb index 0a349d8138e..74804aa7e01 100644 --- a/spec/features/visitors/resend_email_confirmation_spec.rb +++ b/spec/features/visitors/resend_email_confirmation_spec.rb @@ -19,7 +19,7 @@ expect(unread_emails_for(user.email)).to be_present end - scenario 'user throttled sending confirmation emails' do + scenario 'user rate limited sending confirmation emails' do user.save! email = user.email diff --git a/spec/features/visitors/sign_up_with_email_spec.rb b/spec/features/visitors/sign_up_with_email_spec.rb index 2df4db05d8e..b9aaf9316bf 100644 --- a/spec/features/visitors/sign_up_with_email_spec.rb +++ b/spec/features/visitors/sign_up_with_email_spec.rb @@ -67,7 +67,7 @@ end end - it 'throttles sending confirmations after limit is reached' do + it 'rate limits sending confirmations after limit is reached' do email = 'test@test.com' sign_up_with(email) diff --git a/spec/forms/idv/api_image_upload_form_spec.rb b/spec/forms/idv/api_image_upload_form_spec.rb index 7821c376557..8f7190d0e7b 100644 --- a/spec/forms/idv/api_image_upload_form_spec.rb +++ b/spec/forms/idv/api_image_upload_form_spec.rb @@ -57,14 +57,14 @@ end end - context 'when throttled from submission' do + context 'when rate limited from submission' do it 'is not valid' do expect(irs_attempts_api_tracker).to receive(:idv_document_upload_rate_limited).with(no_args) - Throttle.new( - throttle_type: :idv_doc_auth, + RateLimiter.new( + rate_limit_type: :idv_doc_auth, user: document_capture_session.user, - ).increment_to_throttled! + ).increment_to_limited! form.submit expect(form.valid?).to eq(false) diff --git a/spec/forms/register_user_email_form_spec.rb b/spec/forms/register_user_email_form_spec.rb index 3c571fd3a81..dbafed2e4cb 100644 --- a/spec/forms/register_user_email_form_spec.rb +++ b/spec/forms/register_user_email_form_spec.rb @@ -34,7 +34,7 @@ ) end - it 'creates throttle events after reaching throttle limit' do + it 'creates rate_limiter events after reaching rate_limiter limit' do expect(attempts_tracker).to receive( :user_registration_email_submission_rate_limited, ).with( @@ -95,7 +95,7 @@ end.to change { existing_user.reload.email_language }.from('en').to('fr') end - it 'creates throttle events after reaching throttle limit' do + it 'creates rate_limiter events after reaching rate_limiter limit' do expect(attempts_tracker).to receive( :user_registration_email_submission_rate_limited, ).with( diff --git a/spec/javascript/packages/document-capture-polling/index-spec.js b/spec/javascript/packages/document-capture-polling/index-spec.js index 8cd21f90e78..8f722a5f315 100644 --- a/spec/javascript/packages/document-capture-polling/index-spec.js +++ b/spec/javascript/packages/document-capture-polling/index-spec.js @@ -124,7 +124,7 @@ describe('DocumentCapturePolling', () => { expect(subject.elements.form.submit).to.have.been.called(); }); - it('redirects when throttled', async () => { + it('redirects when rate limited', async () => { sandbox .stub(window, 'fetch') .withArgs('/status') diff --git a/spec/lib/telephony/pinpoint/sms_sender_spec.rb b/spec/lib/telephony/pinpoint/sms_sender_spec.rb index 8969b46a38d..988291c0a3d 100644 --- a/spec/lib/telephony/pinpoint/sms_sender_spec.rb +++ b/spec/lib/telephony/pinpoint/sms_sender_spec.rb @@ -101,7 +101,7 @@ def ==(other) end end - context 'when the request is throttled' do + context 'when the request is rate limited' do let(:delivery_status) { 'THROTTLED' } it 'raises an opt out error' do diff --git a/spec/presenters/image_upload_response_presenter_spec.rb b/spec/presenters/image_upload_response_presenter_spec.rb index 5c327ef2bd4..beb44410db3 100644 --- a/spec/presenters/image_upload_response_presenter_spec.rb +++ b/spec/presenters/image_upload_response_presenter_spec.rb @@ -105,7 +105,7 @@ end end - context 'throttled' do + context 'rate limited' do let(:extra_attributes) do { remaining_attempts: 0, flow_path: 'standard' } end diff --git a/spec/services/idv/phone_step_spec.rb b/spec/services/idv/phone_step_spec.rb index 3435e7e856e..f95669945d7 100644 --- a/spec/services/idv/phone_step_spec.rb +++ b/spec/services/idv/phone_step_spec.rb @@ -52,7 +52,7 @@ end describe '#submit' do - let(:throttle) { Throttle.new(throttle_type: :proof_address, user: user) } + let(:rate_limiter) { RateLimiter.new(rate_limit_type: :proof_address, user: user) } let(:mock_vendor) do { vendor_name: 'AddressMock', @@ -118,23 +118,23 @@ it 'increments step attempts' do expect do subject.submit(phone: bad_phone) - end.to(change { throttle.fetch_state!.attempts }.by(1)) + end.to(change { rate_limiter.fetch_state!.attempts }.by(1)) end it 'increments step attempts when the vendor request times out' do expect do subject.submit(phone: timeout_phone) - end.to(change { throttle.fetch_state!.attempts }.by(1)) + end.to(change { rate_limiter.fetch_state!.attempts }.by(1)) end it 'does not increment step attempts when the vendor raises an exception' do expect do subject.submit(phone: fail_phone) - end.to(change { throttle.fetch_state!.attempts }.by(1)) + end.to(change { rate_limiter.fetch_state!.attempts }.by(1)) end - it 'logs a throttled attempts_tracker event' do - throttle.increment_to_throttled! + it 'logs a rate limited attempts_tracker event' do + rate_limiter.increment_to_limited! expect(@irs_attempts_api_tracker).to receive(:idv_phone_otp_sent_rate_limited) subject.submit(phone: bad_phone) @@ -198,7 +198,7 @@ context 'when there are not idv attempts remaining' do it 'returns :fail' do - Throttle.new(throttle_type: :proof_address, user: user).increment_to_throttled! + RateLimiter.new(rate_limit_type: :proof_address, user: user).increment_to_limited! subject.submit(phone: bad_phone) expect(subject.failure_reason).to eq(:fail) diff --git a/spec/services/otp_rate_limiter_spec.rb b/spec/services/otp_rate_limiter_spec.rb index 88c2ba2f0f4..27c6187d5a8 100644 --- a/spec/services/otp_rate_limiter_spec.rb +++ b/spec/services/otp_rate_limiter_spec.rb @@ -62,7 +62,7 @@ otp_rate_limiter.increment expect { otp_rate_limiter.increment }. - to change { otp_rate_limiter.throttle.attempts }.from(1).to(2) + to change { otp_rate_limiter.rate_limiter.attempts }.from(1).to(2) end end diff --git a/spec/services/rate_limiter_spec.rb b/spec/services/rate_limiter_spec.rb new file mode 100644 index 00000000000..9a4d629e454 --- /dev/null +++ b/spec/services/rate_limiter_spec.rb @@ -0,0 +1,191 @@ +require 'rails_helper' + +RSpec.describe RateLimiter do + let(:rate_limit_type) { :idv_doc_auth } + let(:max_attempts) { 3 } + let(:attempt_window) { 10 } + before(:each) do + allow(IdentityConfig.store).to receive(:doc_auth_max_attempts).and_return(max_attempts) + allow(IdentityConfig.store).to receive(:doc_auth_attempt_window_in_minutes). + and_return(attempt_window) + end + + describe '.new' do + context 'when target is a string' do + subject(:for_target) { RateLimiter.new(target: target, rate_limit_type: rate_limit_type) } + + context 'target is not a string' do + it 'raises an error' do + expect { RateLimiter.new(target: 3, rate_limit_type: rate_limit_type) }. + to raise_error(ArgumentError) + end + end + end + + it 'throws an error when neither user nor target are provided' do + expect { RateLimiter.new(rate_limit_type: rate_limit_type) }. + to raise_error( + ArgumentError, + 'RateLimiter must have a user or a target, but neither were provided', + ) + end + + it 'throws an error when both user and target are provided' do + expect { RateLimiter.new(rate_limit_type: rate_limit_type) }. + to raise_error( + ArgumentError, + 'RateLimiter must have a user or a target, but neither were provided', + ) + end + + it 'throws an error for an invalid rate_limit_type' do + expect { RateLimiter.new(rate_limit_type: :abc_123, target: '1') }. + to raise_error( + ArgumentError, + 'rate_limit_type is not valid', + ) + end + end + + describe '.attempt_window_in_minutes' do + it 'returns configured attempt window for rate_limiter type' do + expect(RateLimiter.attempt_window_in_minutes(rate_limit_type)).to eq(attempt_window) + end + + it 'is indifferent to rate_limiter type stringiness' do + expect(RateLimiter.attempt_window_in_minutes(rate_limit_type.to_s)).to eq(attempt_window) + end + end + + describe '.max_attempts' do + it 'returns configured attempt window for rate_limiter type' do + expect(RateLimiter.max_attempts(rate_limit_type)).to eq(max_attempts) + end + + it 'is indifferent to rate_limiter type stringiness' do + expect(RateLimiter.max_attempts(rate_limit_type.to_s)).to eq(max_attempts) + end + end + + describe '#increment!' do + subject(:rate_limiter) { RateLimiter.new(target: 'aaa', rate_limit_type: :idv_doc_auth) } + let(:max_attempts) { 1 } # Picked up by before block at top of test file + + it 'increments attempts' do + expect(rate_limiter.attempts).to eq 0 + rate_limiter.increment! + expect(rate_limiter.attempts).to eq 1 + end + + it 'does nothing if already rate limited' do + expect(rate_limiter.attempts).to eq 0 + rate_limiter.increment! + expect(rate_limiter.attempts).to eq 1 + expect(rate_limiter.limited?).to eq(true) + current_expiration = rate_limiter.expires_at + travel 5.minutes do # move within 10 minute expiration window + rate_limiter.increment! + expect(rate_limiter.attempts).to eq 1 + expect(rate_limiter.expires_at).to eq current_expiration + end + end + end + + describe '#limited?' do + let(:rate_limit_type) { :idv_doc_auth } + let(:max_attempts) { RateLimiter.max_attempts(rate_limit_type) } + let(:attempt_window_in_minutes) { RateLimiter.attempt_window_in_minutes(rate_limit_type) } + + subject(:rate_limiter) { RateLimiter.new(target: '1', rate_limit_type: rate_limit_type) } + + it 'returns true if rate limited' do + max_attempts.times do + rate_limiter.increment! + end + + expect(rate_limiter.limited?).to eq(true) + end + + it 'returns false if the attempts < max_attempts' do + (max_attempts - 1).times do + expect(rate_limiter.limited?).to eq(false) + rate_limiter.increment! + end + + expect(rate_limiter.limited?).to eq(false) + end + + it 'returns false if the attempts <= max_attempts but the window is expired' do + max_attempts.times do + rate_limiter.increment! + end + + travel(attempt_window_in_minutes.minutes + 1) do + expect(rate_limiter.limited?).to eq(false) + end + end + end + + describe '#expires_at' do + let(:attempted_at) { nil } + let(:rate_limiter) { RateLimiter.new(target: '1', rate_limit_type: rate_limit_type) } + + context 'without having attempted' do + it 'returns current time' do + freeze_time do + expect(rate_limiter.expires_at).to eq(Time.zone.now) + end + end + end + + context 'with expired rate_limiter' do + it 'returns expiration time' do + rate_limiter.increment! + + travel_to(rate_limiter.attempted_at + 3.days) do + expect(rate_limiter.expires_at).to be_within(1.second). + of(rate_limiter.attempted_at + attempt_window.minutes) + end + end + end + + context 'with active rate_limiter' do + it 'returns expiration time' do + freeze_time do + rate_limiter.increment! + expect(rate_limiter.expires_at).to be_within(1.second). + of(rate_limiter.attempted_at + attempt_window.minutes) + end + end + end + end + + describe '#reset' do + let(:target) { '1' } + let(:subject) { described_class } + + subject(:rate_limiter) { RateLimiter.new(target: target, rate_limit_type: rate_limit_type) } + + it 'resets attempt count to 0' do + rate_limiter.increment! + + expect { rate_limiter.reset! }.to change { rate_limiter.attempts }.to(0) + end + end + + describe '#remaining_count' do + let(:target) { '1' } + let(:subject) { described_class } + + subject(:rate_limiter) { RateLimiter.new(target: target, rate_limit_type: rate_limit_type) } + + it 'returns maximium remaining attempts with zero attempts' do + expect(rate_limiter.remaining_count).to eq(RateLimiter.max_attempts(rate_limit_type)) + end + + it 'returns zero when rate_limiter limit is reached' do + rate_limiter.increment_to_limited! + expect(rate_limiter.remaining_count).to eq(0) + end + end +end diff --git a/spec/services/request_password_reset_spec.rb b/spec/services/request_password_reset_spec.rb index 6534cb77f8b..aac89f3b4d4 100644 --- a/spec/services/request_password_reset_spec.rb +++ b/spec/services/request_password_reset_spec.rb @@ -164,7 +164,7 @@ context 'when the user requests password resets above the allowable threshold' do let(:analytics) { FakeAnalytics.new } - it 'throttles the email sending and logs a throttle event' do + it 'rate limits the email sending and logs a rate limit event' do max_attempts = IdentityConfig.store.reset_password_email_max_attempts (max_attempts - 1).times do @@ -178,7 +178,7 @@ to(change { user.reload.reset_password_token }) end - # extra time, throttled + # extra time, rate limited expect do RequestPasswordReset.new( email: email, @@ -197,7 +197,7 @@ ) end - it 'only sends a push notification when the attempts have not been throttled' do + it 'only sends a push notification when the attempts have not been rate limited' do max_attempts = IdentityConfig.store.reset_password_email_max_attempts expect(PushNotification::HttpPush).to receive(:deliver). @@ -215,7 +215,7 @@ to(change { user.reload.reset_password_token }) end - # extra time, throttled + # extra time, rate limited expect do RequestPasswordReset.new( email: email, diff --git a/spec/services/throttle_spec.rb b/spec/services/throttle_spec.rb deleted file mode 100644 index c97c007d345..00000000000 --- a/spec/services/throttle_spec.rb +++ /dev/null @@ -1,191 +0,0 @@ -require 'rails_helper' - -RSpec.describe Throttle do - let(:throttle_type) { :idv_doc_auth } - let(:max_attempts) { 3 } - let(:attempt_window) { 10 } - before(:each) do - allow(IdentityConfig.store).to receive(:doc_auth_max_attempts).and_return(max_attempts) - allow(IdentityConfig.store).to receive(:doc_auth_attempt_window_in_minutes). - and_return(attempt_window) - end - - describe '.new' do - context 'when target is a string' do - subject(:for_target) { Throttle.new(target: target, throttle_type: throttle_type) } - - context 'target is not a string' do - it 'raises an error' do - expect { Throttle.new(target: 3, throttle_type: throttle_type) }. - to raise_error(ArgumentError) - end - end - end - - it 'throws an error when neither user nor target are provided' do - expect { Throttle.new(throttle_type: throttle_type) }. - to raise_error( - ArgumentError, - 'Throttle must have a user or a target, but neither were provided', - ) - end - - it 'throws an error when both user and target are provided' do - expect { Throttle.new(throttle_type: throttle_type) }. - to raise_error( - ArgumentError, - 'Throttle must have a user or a target, but neither were provided', - ) - end - - it 'throws an error for an invalid throttle_type' do - expect { Throttle.new(throttle_type: :abc_123, target: '1') }. - to raise_error( - ArgumentError, - 'throttle_type is not valid', - ) - end - end - - describe '.attempt_window_in_minutes' do - it 'returns configured attempt window for throttle type' do - expect(Throttle.attempt_window_in_minutes(throttle_type)).to eq(attempt_window) - end - - it 'is indifferent to throttle type stringiness' do - expect(Throttle.attempt_window_in_minutes(throttle_type.to_s)).to eq(attempt_window) - end - end - - describe '.max_attempts' do - it 'returns configured attempt window for throttle type' do - expect(Throttle.max_attempts(throttle_type)).to eq(max_attempts) - end - - it 'is indifferent to throttle type stringiness' do - expect(Throttle.max_attempts(throttle_type.to_s)).to eq(max_attempts) - end - end - - describe '#increment!' do - subject(:throttle) { Throttle.new(target: 'aaa', throttle_type: :idv_doc_auth) } - let(:max_attempts) { 1 } # Picked up by before block at top of test file - - it 'increments attempts' do - expect(throttle.attempts).to eq 0 - throttle.increment! - expect(throttle.attempts).to eq 1 - end - - it 'does nothing if already throttled' do - expect(throttle.attempts).to eq 0 - throttle.increment! - expect(throttle.attempts).to eq 1 - expect(throttle.throttled?).to eq(true) - current_expiration = throttle.expires_at - travel 5.minutes do # move within 10 minute expiration window - throttle.increment! - expect(throttle.attempts).to eq 1 - expect(throttle.expires_at).to eq current_expiration - end - end - end - - describe '#throttled?' do - let(:throttle_type) { :idv_doc_auth } - let(:max_attempts) { Throttle.max_attempts(throttle_type) } - let(:attempt_window_in_minutes) { Throttle.attempt_window_in_minutes(throttle_type) } - - subject(:throttle) { Throttle.new(target: '1', throttle_type: throttle_type) } - - it 'returns true if throttled' do - max_attempts.times do - throttle.increment! - end - - expect(throttle.throttled?).to eq(true) - end - - it 'returns false if the attempts < max_attempts' do - (max_attempts - 1).times do - expect(throttle.throttled?).to eq(false) - throttle.increment! - end - - expect(throttle.throttled?).to eq(false) - end - - it 'returns false if the attempts <= max_attempts but the window is expired' do - max_attempts.times do - throttle.increment! - end - - travel(attempt_window_in_minutes.minutes + 1) do - expect(throttle.throttled?).to eq(false) - end - end - end - - describe '#expires_at' do - let(:attempted_at) { nil } - let(:throttle) { Throttle.new(target: '1', throttle_type: throttle_type) } - - context 'without having attempted' do - it 'returns current time' do - freeze_time do - expect(throttle.expires_at).to eq(Time.zone.now) - end - end - end - - context 'with expired throttle' do - it 'returns expiration time' do - throttle.increment! - - travel_to(throttle.attempted_at + 3.days) do - expect(throttle.expires_at).to be_within(1.second). - of(throttle.attempted_at + attempt_window.minutes) - end - end - end - - context 'with active throttle' do - it 'returns expiration time' do - freeze_time do - throttle.increment! - expect(throttle.expires_at).to be_within(1.second). - of(throttle.attempted_at + attempt_window.minutes) - end - end - end - end - - describe '#reset' do - let(:target) { '1' } - let(:subject) { described_class } - - subject(:throttle) { Throttle.new(target: target, throttle_type: throttle_type) } - - it 'resets attempt count to 0' do - throttle.increment! - - expect { throttle.reset! }.to change { throttle.attempts }.to(0) - end - end - - describe '#remaining_count' do - let(:target) { '1' } - let(:subject) { described_class } - - subject(:throttle) { Throttle.new(target: target, throttle_type: throttle_type) } - - it 'returns maximium remaining attempts with zero attempts' do - expect(throttle.remaining_count).to eq(Throttle.max_attempts(throttle_type)) - end - - it 'returns zero when throttle limit is reached' do - throttle.increment_to_throttled! - expect(throttle.remaining_count).to eq(0) - end - end -end diff --git a/spec/support/idv_examples/max_attempts.rb b/spec/support/idv_examples/max_attempts.rb index bfb7a55f75e..ef0e19cdf47 100644 --- a/spec/support/idv_examples/max_attempts.rb +++ b/spec/support/idv_examples/max_attempts.rb @@ -44,7 +44,7 @@ context 'after completing one less than the max attempts' do it 'allows the user to continue if their last attempt is successful' do - (Throttle.max_attempts(:proof_address) - 1).times do + (RateLimiter.max_attempts(:proof_address) - 1).times do fill_out_phone_form_fail click_idv_continue_for_step(step) click_on t('idv.failure.phone.warning.try_again_button') @@ -57,7 +57,7 @@ end def perfom_maximum_allowed_idv_step_attempts(step) - (Throttle.max_attempts(:proof_address) - 1).times do + (RateLimiter.max_attempts(:proof_address) - 1).times do yield click_idv_continue_for_step(step) click_on t('idv.failure.phone.warning.try_again_button')