diff --git a/app/helpers/rate_limit_checker_helper.rb b/app/helpers/rate_limit_checker_helper.rb index 446cc38b2..5d449754f 100644 --- a/app/helpers/rate_limit_checker_helper.rb +++ b/app/helpers/rate_limit_checker_helper.rb @@ -2,9 +2,14 @@ module RateLimitCheckerHelper CONFIGURABLE_RATES = { rate_limit_article_update: { min: 1, - placeholder: 150, + placeholder: 30, description: "The number of article updates a user can make in 30 seconds" }, + rate_limit_user_update: { + min: 1, + placeholder: 5, + description: "The number of user updates a user can make in 30 seconds" + }, rate_limit_feedback_message_creation: { min: 1, placeholder: 5, diff --git a/app/models/site_config.rb b/app/models/site_config.rb index 5ca1ad4a5..5d61b062a 100644 --- a/app/models/site_config.rb +++ b/app/models/site_config.rb @@ -81,9 +81,10 @@ class SiteConfig < RailsSettings::Base field :rate_limit_reaction_creation, type: :integer, default: 10 field :rate_limit_image_upload, type: :integer, default: 9 field :rate_limit_email_recipient, type: :integer, default: 5 - field :rate_limit_article_update, type: :integer, default: 150 + field :rate_limit_article_update, type: :integer, default: 30 field :rate_limit_send_email_confirmation, type: :integer, default: 2 field :rate_limit_feedback_message_creation, type: :integer, default: 5 + field :rate_limit_user_update, type: :integer, default: 5 # Google Analytics Reporting API v4 # diff --git a/app/models/user.rb b/app/models/user.rb index 857629782..c81faf235 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -140,6 +140,7 @@ class User < ApplicationRecord validate :validate_feed_url, if: :feed_url_changed? validate :validate_mastodon_url validate :can_send_confirmation_email + validate :update_rate_limit alias_attribute :positive_reactions_count, :reactions_count alias_attribute :subscribed_to_welcome_notifications?, :welcome_notifications @@ -624,4 +625,13 @@ class User < ApplicationRecord rescue RateLimitChecker::LimitReached => e errors.add(:email, "confirmation could not be sent. #{e.message}") end + + def update_rate_limit + return unless persisted? + + rate_limiter.track_limit_by_action(:user_update) + rate_limiter.check_limit!(:user_update) + rescue RateLimitChecker::LimitReached => e + errors.add(:base, "User could not be saved. #{e.message}") + end end diff --git a/app/services/rate_limit_checker.rb b/app/services/rate_limit_checker.rb index f544ed519..0388c2471 100644 --- a/app/services/rate_limit_checker.rb +++ b/app/services/rate_limit_checker.rb @@ -4,13 +4,14 @@ class RateLimitChecker # retry_after values are the seconds until a user can retry an action ACTION_LIMITERS = { article_update: { retry_after: 30 }, - send_email_confirmation: { retry_after: 120 }, feedback_message_creation: { retry_after: 300 }, image_upload: { retry_after: 30 }, listing_creation: { retry_after: 60 }, - published_article_creation: { retry_after: 30 }, organization_creation: { retry_after: 300 }, - reaction_creation: { retry_after: 30 } + published_article_creation: { retry_after: 30 }, + reaction_creation: { retry_after: 30 }, + send_email_confirmation: { retry_after: 120 }, + user_update: { retry_after: 30 } }.with_indifferent_access.freeze def initialize(user = nil) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index f251f9877..a764e14de 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -206,9 +206,25 @@ RSpec.describe User, type: :model do limiter = RateLimitChecker.new(user) allow(user).to receive(:rate_limiter).and_return(limiter) allow(limiter).to receive(:limit_by_action).and_return(true) + allow(limiter).to receive(:track_limit_by_action) user.update(email: "new_email@yo.com") + expect(user).not_to be_valid expect(user.errors[:email].to_s).to include("confirmation could not be sent. Rate limit reached") + expect(limiter).to have_received(:track_limit_by_action).with(:send_email_confirmation).twice + end + + it "validates update_rate_limit for existing user" do + user = create(:user) + limiter = RateLimitChecker.new(user) + allow(user).to receive(:rate_limiter).and_return(limiter) + allow(limiter).to receive(:limit_by_action).and_return(true) + allow(limiter).to receive(:track_limit_by_action) + user.update(articles_count: 5) + + expect(user).not_to be_valid + expect(user.errors[:base].to_s).to include("could not be saved. Rate limit reached") + expect(limiter).to have_received(:track_limit_by_action).with(:user_update).twice end end