diff --git a/config/initializers/rack-attack.rb b/config/initializers/rack-attack.rb index 6c23e151..9dfe3746 100644 --- a/config/initializers/rack-attack.rb +++ b/config/initializers/rack-attack.rb @@ -1,15 +1,59 @@ class Rack::Attack -end + Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new -Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new - -# Throttle requests to 5 requests per second per ip -Rack::Attack.throttle('load_url_title/req/ip', :limit => 5, :period => 1.second) do |req| - # If the return value is truthy, the cache key for the return value - # is incremented and compared with the limit. In this case: - # "rack::attack:#{Time.now.to_i/1.second}:load_url_title/req/ip:#{req.ip}" + # Throttle all requests by IP (60rpm) # - # If falsy, the cache key is neither incremented nor checked. + # Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}" + throttle('req/ip', :limit => 300, :period => 5.minutes) do |req| + req.ip # unless req.path.start_with?('/assets') + end - req.ip if req.path === 'hacks/load_url_title' + # Throttle POST requests to /login by IP address + # + # Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}" + throttle('logins/ip', :limit => 5, :period => 20.seconds) do |req| + if req.path == '/login' && req.post? + req.ip + end + end + + # Throttle POST requests to /login by email param + # + # Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{req.email}" + # + # Note: This creates a problem where a malicious user could intentionally + # throttle logins for another user and force their login requests to be + # denied, but that's not very common and shouldn't happen to you. (Knock + # on wood!) + throttle("logins/email", :limit => 5, :period => 20.seconds) do |req| + if req.path == '/login' && req.post? + # return the email if present, nil otherwise + req.params['email'].presence + end + end + + throttle('load_url_title/req/ip', :limit => 5, :period => 1.second) do |req| + # If the return value is truthy, the cache key for the return value + # is incremented and compared with the limit. In this case: + # "rack::attack:#{Time.now.to_i/1.second}:load_url_title/req/ip:#{req.ip}" + # + # If falsy, the cache key is neither incremented nor checked. + + req.ip if req.path == 'hacks/load_url_title' + end + + self.throttled_response = lambda do |env| + now = Time.now + match_data = env['rack.attack.match_data'] + period = match_data[:period] + limit = match_data[:limit] + + headers = { + 'X-RateLimit-Limit' => limit.to_s, + 'X-RateLimit-Remaining' => '0', + 'X-RateLimit-Reset' => (now + (period - now.to_i % period)).to_s + } + + [429, headers, ['']] + end end