In v4.4.0, checking `defined?(ActiveSupport::Cache::MemCacheStore)`
could trigger an error loading dalli, which isn’t needed.
This fixes that bug, and prevents similar bugs by checking
`store.class.to_s` rather than `defined?(klass) && store.is_a?(klass)`.
Writing an automated test to ensure that dalli is truly optional is
difficult, but I was able to recreate the dalli load error in v4.4.0 by
running:
gem uninstall dalli
ruby -Ilib -ractive_support/all -ractive_support/cache/redis_store
-rrack/attack -e 'p Rack::Attack::StoreProxy.build(Redis::Store.new)'
Fixes#163
The issue
---
When using rack-attack with a rails app, developers expect the request
path to be normalized. In particular, trailing slashes are stripped so
a request path "/login/" becomes "/login" by the time you're in
ActionController.
Since Rack::Attack runs before ActionDispatch, the request path is not
yet normalized. This can cause throttles and blacklists to not work as
expected.
E.g., a throttle:
throttle('logins', ...) {|req| req.path == "/login" }
would not match a request to '/login/', though Rails would route
'/login/' to the same '/login' action.
The solution
---
This patch looks if ActionDispatch's request normalization is loaded,
and if so, uses it to normalize the path before processing throttles,
blacklists, etc.
If it's not loaded, the request path is not modified.
Credit
---
Thanks to Andres Riancho at Include Security for reporting this issue.
I need to filter requests on a period I need to get dynamically out of
information I have in the requests. Currently, I can work out the limit,
as it can be a `Proc`, however I can't do that with the period.
This PR adds support for that. Tried to do it in a way that doesn't
brake backwards compatibility, as periods are coerced to numbers during
`Rack::Throttle` initialization.
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.
For throttling, when the redis client throws an exception, the request
ends up getting rate limited. Modify this to be similar to how
ActiveSupport.MemCacheStore functions (the read, write and increment
methods do not raise exceptions)
An alternate to fail2ban that allows clients until they hit the
thresholds, then blocks them. Think of it like a throttle where you can
block for more than one period.