diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index 29d287f69..df562261e 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -1,21 +1,25 @@ -if ENV['RAILS_ENV'] != 'test' +if Rails.env.production? class Rack::Attack - throttle('logins/ip', limit: 5, period: 20.seconds) do |req| + throttle('/users/sign_in/ip', limit: 5, period: 20.seconds) do |req| if req.path == '/users/sign_in' && req.post? - req.ip + req.remote_ip end end throttle('stats/ip', limit: 5, period: 20.seconds) do |req| if req.path == '/stats' - req.ip + req.remote_ip end end throttle('contact/ip', limit: 5, period: 20.seconds) do |req| if req.path == '/contact' && req.post? - req.ip + req.remote_ip end end + + Rack::Attack.safelist('allow from localhost') do |req| + IPService.ip_trusted?(req.remote_ip) + end end end diff --git a/config/initializers/rack_attack_request.rb b/config/initializers/rack_attack_request.rb new file mode 100644 index 000000000..fa72e9844 --- /dev/null +++ b/config/initializers/rack_attack_request.rb @@ -0,0 +1,7 @@ +class Rack::Attack + class Request < ::Rack::Request + def remote_ip + @remote_ip ||= (env['action_dispatch.remote_ip'] || ip).to_s + end + end +end diff --git a/spec/middlewares/rack_attack_spec.rb b/spec/middlewares/rack_attack_spec.rb new file mode 100644 index 000000000..9b3506e03 --- /dev/null +++ b/spec/middlewares/rack_attack_spec.rb @@ -0,0 +1,51 @@ +require "rails_helper" + +describe Rack::Attack, type: :request do + let(:limit) { 5 } + let(:period) { 20 } + let(:ip) { "1.2.3.4" } + + before(:each) do + setup_rack_attack_cache_store + avoid_test_overlaps_in_cache + end + + def setup_rack_attack_cache_store + Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + end + + def avoid_test_overlaps_in_cache + Rails.cache.clear + end + + context '/users/sign_in' do + before do + limit.times do + Rack::Attack.cache.count("/users/sign_in/ip:#{ip}", period) + end + end + + subject do + post "/users/sign_in", headers: { 'X-Forwarded-For': ip } + end + + it "throttle excessive requests by IP address" do + subject + + expect(response).to have_http_status(:too_many_requests) + end + + context 'when the ip is whitelisted' do + before do + allow(IPService).to receive(:ip_trusted?).and_return(true) + allow_any_instance_of(Users::SessionsController).to receive(:create).and_return(:ok) + end + + it "respects the whitelist" do + subject + + expect(response).not_to have_http_status(:too_many_requests) + end + end + end +end