KemalIdentity::FailOpenRateLimiter
Inherits KemalIdentity::RateLimiter < Reference < Object
Turns "the store did not answer" into "carry on", for one call site.
Why this is a wrapper and not a setting
blueprints/maturity-validation-scenarios.md (OPS-01) requires the fail-open or fail-closed
choice to be made per endpoint, and it genuinely differs: refusing every login while
Redis is down is a self-inflicted outage, while running the login path unmetered is exactly
what an attacker gets by overwhelming whatever stores the counts. Neither answer is right
everywhere.
A flag on the limiter would settle it once for the whole application. A wrapper settles it once per limiter, and every service already takes its own — so per-endpoint falls out of wiring that exists rather than out of a new parameter:
shared = MyRedisRateLimiter.new(redis)
KemalIdentity.configure(
rate_limiter: shared, # login stays fail-closed
# ...
)
# ...while something less sensitive prefers to stay up:
notifications = MyThrottledMailer.new(KemalIdentity::FailOpenRateLimiter.new(shared))
The default is fail-closed and this is opt-in, because every call site in this shard is an authentication path: a login, a password reset, and three ways of proving a second factor. Silence is the wrong answer for all five.
Constructors
Instance methods
Counts one attempt against key and says whether it may proceed.
Called before any I/O and before any hashing. A denial must be cheap, or the limiter becomes the very lever it exists to remove.
Must not raise for a storage failure. A limiter whose Redis is unreachable returns
Verdict.unavailable and lets the application's configured policy decide, because the
answer differs per endpoint: a login should refuse rather than run unmetered, while a
less sensitive action may prefer to stay up. An exception here would make that choice for
everybody, and would surface as a 500 rather than as either policy.
Clears the count for key, after a successful authentication.
Idempotent, and safe for a key that was never consumed. Must not raise, including when the store is unavailable: a reset that does not happen leaves somebody throttled slightly longer than they earned, which is not worth failing a successful login over.