From b652147e7ac537d714e24c344650793bd05edbde Mon Sep 17 00:00:00 2001 From: Molly Struve Date: Wed, 8 Apr 2020 18:17:51 -0500 Subject: [PATCH] throttle API get and write endpoints (#7167) --- config/initializers/rack/attack.rb | 12 +++++++ spec/initializers/rack/attack_spec.rb | 49 +++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/config/initializers/rack/attack.rb b/config/initializers/rack/attack.rb index 5829addf8..dfdf4171f 100644 --- a/config/initializers/rack/attack.rb +++ b/config/initializers/rack/attack.rb @@ -4,4 +4,16 @@ class Rack::Attack request.env["HTTP_FASTLY_CLIENT_IP"].to_s end end + + throttle("api_throttle", limit: 3, period: 1) do |request| + if request.path.starts_with?("/api/") && request.get? && request.env["HTTP_FASTLY_CLIENT_IP"].present? + request.env["HTTP_FASTLY_CLIENT_IP"].to_s + end + end + + throttle("api_write_throttle", limit: 1, period: 1) do |request| + if request.path.starts_with?("/api/") && (request.put? || request.post? || request.delete?) + request.env["HTTP_API_KEY"] + end + end end diff --git a/spec/initializers/rack/attack_spec.rb b/spec/initializers/rack/attack_spec.rb index 33fb6a952..3370a19d7 100644 --- a/spec/initializers/rack/attack_spec.rb +++ b/spec/initializers/rack/attack_spec.rb @@ -1,13 +1,13 @@ require "rails_helper" describe Rack::Attack, type: :request, throttle: true do - describe "search_throttle" do - before do - redis_url = "redis://localhost:6379" - cache_db = ActiveSupport::Cache::RedisStore.new(redis_url) - allow(Rails).to receive(:cache) { cache_db } - end + before do + redis_url = "redis://localhost:6379" + cache_db = ActiveSupport::Cache::RedisStore.new(redis_url) + allow(Rails).to receive(:cache) { cache_db } + end + describe "search_throttle" do it "throttles /search endpoints based on IP" do Timecop.freeze do allow(Search::User).to receive(:search_documents).and_return({}) @@ -23,4 +23,41 @@ describe Rack::Attack, type: :request, throttle: true do end end end + + describe "api_throttle" do + it "throttles api get endpoints based on IP" do + Timecop.freeze do + valid_responses = Array.new(3).map do + get api_articles_path, headers: { "HTTP_FASTLY_CLIENT_IP" => "5.6.7.8" } + end + throttled_response = get api_articles_path, headers: { "HTTP_FASTLY_CLIENT_IP" => "5.6.7.8" } + new_ip_response = get api_articles_path, headers: { "HTTP_FASTLY_CLIENT_IP" => "1.1.1.1" } + + valid_responses.each { |r| expect(r).not_to eq(429) } + expect(throttled_response).to eq(429) + expect(new_ip_response).not_to eq(429) + end + end + end + + describe "api_write_throttle" do + let(:api_secret) { create(:api_secret) } + let(:another_api_secret) { create(:api_secret) } + + it "throttles api write endpoints based on api-key" do + headers = { "api-key" => api_secret.secret, "content-type" => "application/json" } + dif_headers = { "api-key" => another_api_secret.secret, "content-type" => "application/json" } + params = { body_markdown: "", title: Faker::Book.title } + + Timecop.freeze do + valid_response = post api_articles_path, params: { article: params }.to_json, headers: headers + throttled_response = post api_articles_path, params: { article: params }.to_json, headers: headers + new_api_response = post api_articles_path, params: { article: params }.to_json, headers: dif_headers + + expect(valid_response).not_to eq(429) + expect(throttled_response).to eq(429) + expect(new_api_response).not_to eq(429) + end + end + end end