FEATURE: Provide generic modifier interface for rate limiting.
This commit is contained in:
parent
8e72136f54
commit
a8a1f5e759
|
@ -35,6 +35,13 @@ class DiscourseSolved::AnswerController < ::ApplicationController
|
|||
|
||||
def limit_accepts
|
||||
return if current_user.staff?
|
||||
run_rate_limiter =
|
||||
DiscoursePluginRegistry.apply_modifier(
|
||||
:solved_answers_controller_run_rate_limiter,
|
||||
true,
|
||||
current_user,
|
||||
)
|
||||
return if !run_rate_limiter
|
||||
RateLimiter.new(nil, "accept-hr-#{current_user.id}", 20, 1.hour).performed!
|
||||
RateLimiter.new(nil, "accept-min-#{current_user.id}", 4, 30.seconds).performed!
|
||||
end
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
describe DiscourseSolved::AnswerController do
|
||||
fab!(:user)
|
||||
fab!(:staff_user) { Fabricate(:admin) }
|
||||
fab!(:category)
|
||||
fab!(:topic) { Fabricate(:topic, category: category) }
|
||||
fab!(:p) { Fabricate(:post, topic: topic) }
|
||||
fab!(:solution_post) { Fabricate(:post, topic: topic) }
|
||||
|
||||
before do
|
||||
SiteSetting.solved_enabled = true
|
||||
SiteSetting.allow_solved_on_all_topics = true
|
||||
category.custom_fields[DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD] = "true"
|
||||
category.save_custom_fields
|
||||
|
||||
# Give permission to accept solutions
|
||||
user.update!(trust_level: 1)
|
||||
|
||||
# Make user the topic creator so they can accept answers
|
||||
topic.update!(user_id: user.id)
|
||||
end
|
||||
|
||||
describe "#accept" do
|
||||
context "with default rate limiting" do
|
||||
it "applies rate limits to regular users" do
|
||||
sign_in(user)
|
||||
|
||||
# First request should succeed
|
||||
post "/solution/accept.json", params: { id: solution_post.id }
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
# Second request should be rate limited
|
||||
RateLimiter.any_instance.expects(:performed!).raises(RateLimiter::LimitExceeded.new(60))
|
||||
post "/solution/accept.json", params: { id: solution_post.id }
|
||||
expect(response.status).to eq(429)
|
||||
end
|
||||
|
||||
it "does not apply rate limits to staff" do
|
||||
sign_in(staff_user)
|
||||
|
||||
# Staff can make multiple requests without hitting limits
|
||||
post "/solution/accept.json", params: { id: solution_post.id }
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
post "/solution/accept.json", params: { id: solution_post.id }
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
end
|
||||
|
||||
context "with plugin modifier" do
|
||||
it "allows plugins to bypass rate limiting" do
|
||||
sign_in(user)
|
||||
|
||||
# Register a modifier that disables rate limiting
|
||||
DiscoursePluginRegistry.register_modifier(
|
||||
:solved_answers_controller_run_rate_limiter,
|
||||
) do |_, _|
|
||||
false # Skip rate limiting
|
||||
end
|
||||
|
||||
# Multiple requests should succeed without rate limiting
|
||||
post "/solution/accept.json", params: { id: solution_post.id }
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
post "/solution/accept.json", params: { id: solution_post.id }
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
# Clean up
|
||||
DiscoursePluginRegistry.unregister_modifier(:solved_answers_controller_run_rate_limiter)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#unaccept" do
|
||||
before do
|
||||
# Setup an accepted solution
|
||||
sign_in(user)
|
||||
post "/solution/accept.json", params: { id: solution_post.id }
|
||||
expect(response.status).to eq(200)
|
||||
sign_out
|
||||
end
|
||||
|
||||
it "applies rate limits to regular users" do
|
||||
sign_in(user)
|
||||
|
||||
# Should be rate limited
|
||||
RateLimiter.any_instance.expects(:performed!).raises(RateLimiter::LimitExceeded.new(60))
|
||||
post "/solution/unaccept.json", params: { id: solution_post.id }
|
||||
expect(response.status).to eq(429)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue