diff --git a/app/controllers/reactions_controller.rb b/app/controllers/reactions_controller.rb index c9ac88a13..730ead688 100644 --- a/app/controllers/reactions_controller.rb +++ b/app/controllers/reactions_controller.rb @@ -1,5 +1,6 @@ class ReactionsController < ApplicationController before_action :set_cache_control_headers, only: [:index], unless: -> { current_user } + before_action :check_limit, only: [:create] after_action :verify_authorized def index @@ -73,6 +74,7 @@ class ReactionsController < ApplicationController reaction = build_reaction(category) if reaction.save + rate_limiter.track_limit_by_action(:reaction_creation) Moderator::SinkArticles.call(reaction.reactable_id) if reaction.vomit_on_user? Notification.send_reaction_notification(reaction, reaction.target_user) @@ -130,4 +132,8 @@ class ReactionsController < ApplicationController context: "readinglist_reaction", rating: current_user.experience_level) end + + def check_limit + rate_limit!(:reaction_creation) + end end diff --git a/app/models/site_config.rb b/app/models/site_config.rb index 2fd12402d..a8f1be2c0 100644 --- a/app/models/site_config.rb +++ b/app/models/site_config.rb @@ -77,6 +77,7 @@ class SiteConfig < RailsSettings::Base field :rate_limit_listing_creation, type: :integer, default: 1 field :rate_limit_published_article_creation, type: :integer, default: 9 field :rate_limit_organization_creation, type: :integer, default: 1 + 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 diff --git a/app/services/rate_limit_checker.rb b/app/services/rate_limit_checker.rb index 9e08fb654..b9141f329 100644 --- a/app/services/rate_limit_checker.rb +++ b/app/services/rate_limit_checker.rb @@ -7,7 +7,8 @@ class RateLimitChecker image_upload: { retry_after: 30 }, listing_creation: { retry_after: 60 }, published_article_creation: { retry_after: 30 }, - organization_creation: { retry_after: 300 } + organization_creation: { retry_after: 300 }, + reaction_creation: { retry_after: 30 } }.with_indifferent_access.freeze CONFIGURABLE_RATES = { @@ -17,7 +18,8 @@ class RateLimitChecker rate_limit_published_article_creation: { min: 0, placeholder: 9, description: "The number of articles a user can create within 30 seconds" }, rate_limit_image_upload: { min: 0, placeholder: 9, description: "The number of images a user can upload within 30 seconds" }, rate_limit_email_recipient: { min: 0, placeholder: 5, description: "The number of emails we send to a user within 2 minutes" }, - rate_limit_organization_creation: { min: 1, placeholder: 1, description: "The number of organizations a user can create within a 5 minute period" } + rate_limit_organization_creation: { min: 1, placeholder: 1, description: "The number of organizations a user can create within a 5 minute period" }, + rate_limit_reaction_creation: { min: 1, placeholder: 10, description: "The number of times a user can react in a 30 second period" } }.freeze def initialize(user = nil) diff --git a/spec/requests/reactions_spec.rb b/spec/requests/reactions_spec.rb index 771923d04..b4ccca18e 100644 --- a/spec/requests/reactions_spec.rb +++ b/spec/requests/reactions_spec.rb @@ -148,6 +148,29 @@ RSpec.describe "Reactions", type: :request do } end + context "when rate limiting" do + let(:rate_limiter) { RateLimitChecker.new(user) } + + before do + allow(RateLimitChecker).to receive(:new).and_return(rate_limiter) + sign_in user + end + + it "increments rate limit for reaction_creation" do + allow(rate_limiter).to receive(:track_limit_by_action) + post "/reactions", params: article_params + + expect(rate_limiter).to have_received(:track_limit_by_action).with(:reaction_creation) + end + + it "returns a 429 status when rate limit is reached" do + allow(rate_limiter).to receive(:limit_by_action).and_return(true) + post "/reactions", params: article_params + + expect(response.status).to eq(429) + end + end + context "when reacting to an article" do before do sign_in user