rack-attack/lib/rack/attack/cache.rb
Aaron Suggs 074e8e5aa4 Add 1 second buffer to expiry to correct throttles
Fixes #69.

There was a race condition when `Time.now.to_i` changes between when
`epoch_time` is computed in line 18, and the cache request is made (and
the `key` is expired).

I.e., a throttle check starts at t0, but doesn’t reach the cache until
t1, the cache will have expired the throttle count. The request will
likely be allowed, even if the request exceeded the limit.

This has the effect of keeping keys in cache about 1 second longer than
strictly necessary. But the extra cache space seems like a good
trade-off for correct throttling.
2014-09-02 14:30:57 -04:00

46 lines
1.2 KiB
Ruby

module Rack
class Attack
class Cache
attr_accessor :prefix
def initialize
self.store = ::Rails.cache if defined?(::Rails.cache)
@prefix = 'rack::attack'
end
attr_reader :store
def store=(store)
@store = StoreProxy.build(store)
end
def count(unprefixed_key, period)
epoch_time = Time.now.to_i
# Add 1 to expires_in to avoid timing error: http://git.io/i1PHXA
expires_in = period - (epoch_time % period) + 1
key = "#{prefix}:#{(epoch_time/period).to_i}:#{unprefixed_key}"
do_count(key, expires_in)
end
def read(unprefixed_key)
store.read("#{prefix}:#{unprefixed_key}")
end
def write(unprefixed_key, value, expires_in)
store.write("#{prefix}:#{unprefixed_key}", value, :expires_in => expires_in)
end
private
def do_count(key, expires_in)
result = store.increment(key, 1, :expires_in => expires_in)
# NB: Some stores return nil when incrementing uninitialized values
if result.nil?
store.write(key, 1, :expires_in => expires_in)
end
result || 1
end
end
end
end