diff --git a/Gemfile b/Gemfile index 42fdb78704..fd7dcf6575 100644 --- a/Gemfile +++ b/Gemfile @@ -63,6 +63,7 @@ gem 'graphql-formatter' gem 'nokogiri', '1.16.5' gem 'puma' gem 'rack-attack' +gem 'rack-cloudflare' gem 'rack-cors', '1.0.6', require: 'rack/cors' gem 'sidekiq', '5.2.10' gem 'sidekiq-cloudwatchmetrics' diff --git a/config/initializers/cloudflare.rb b/config/initializers/cloudflare.rb new file mode 100644 index 0000000000..48924f1113 --- /dev/null +++ b/config/initializers/cloudflare.rb @@ -0,0 +1,3 @@ +if Rails.env.production? + Rails.application.config.middleware.insert_before Rack::Attack, Rack::Cloudflare +end diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index dd5d86639f..03522c872d 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -1,7 +1,7 @@ class Rack::Attack redis = Redis.new(REDIS_CONFIG) + # Throttle all graphql requests by IP address - throttle('api/graphql', limit: proc { CheckConfig.get('api_rate_limit', 100, :integer) }, period: 60.seconds) do |req| req.ip if req.path == '/api/graphql' end @@ -15,13 +15,17 @@ class Rack::Attack track('track excessive logins/ip') do |req| if req.path == '/api/users/sign_in' && req.post? ip = req.ip - # Increment the counter for the IP and check if it should be blocked - count = redis.incr("track:#{ip}") - redis.expire("track:#{ip}", 3600) # Set the expiration time to 1 hour + begin + # Increment the counter for the IP and check if it should be blocked + count = redis.incr("track:#{ip}") + redis.expire("track:#{ip}", 3600) # Set the expiration time to 1 hour - # Add IP to blocklist if count exceeds the threshold - if count.to_i >= CheckConfig.get('login_block_limit', 100, :integer) - redis.set("block:#{ip}", true) # No expiration + # Add IP to blocklist if count exceeds the threshold + if count.to_i >= CheckConfig.get('login_block_limit', 100, :integer) + redis.set("block:#{ip}", true) # No expiration + end + rescue => e + Rails.logger.error("Rack::Attack Error: #{e.message}") end ip diff --git a/test/lib/check_rack_attack_test.rb b/test/lib/check_rack_attack_test.rb index eb294d866a..a467dcab4a 100644 --- a/test/lib/check_rack_attack_test.rb +++ b/test/lib/check_rack_attack_test.rb @@ -30,4 +30,32 @@ class ThrottlingTest < ActionDispatch::IntegrationTest assert_response :forbidden end end + + test "should handle requests via Cloudflare correctly in production" do + original_env = Rails.env + Rails.env = 'production' + + stub_configs({ 'api_rate_limit' => 3, 'login_block_limit' => 2 }) do + # Test throttling for /api/graphql via Cloudflare + 3.times do + post api_graphql_path, headers: { 'CF-Connecting-IP' => '1.2.3.4' } + assert_response :unauthorized + end + + post api_graphql_path, headers: { 'CF-Connecting-IP' => '1.2.3.4' } + assert_response :too_many_requests + + # Test blocking for /api/users/sign_in via Cloudflare + user_params = { api_user: { email: 'user@example.com', password: random_complex_password } } + + 2.times do + post api_user_session_path, params: user_params, as: :json, headers: { 'CF-Connecting-IP' => '1.2.3.4' } + end + + post api_user_session_path, params: user_params, as: :json, headers: { 'CF-Connecting-IP' => '1.2.3.4' } + assert_response :forbidden + end + + Rails.env = original_env + end end