class

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

new(inner : RateLimiter)
Source

Instance methods

consume(key : String) : Verdict

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.

Source
inner
Source
reset(key : String) : Nil

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.

Source