class

KemalIdentity::FixedWindowRateLimiter

Inherits KemalIdentity::RateLimiter < Reference < Object

A fixed-window counter, in memory.

Usable, and honest about its limits:

  • Per process. Two application processes have two independent counters, so the effective limit is limit × processes. Behind a load balancer that is usually not what was intended, and a shared store behind this contract is the answer.
  • Fixed window, not sliding. The window opens on the first attempt against a key rather than on a calendar boundary, and up to 2 × limit attempts can land within seconds of each other by filling a window that is about to elapse and then filling the next one as it opens. For a login endpoint that is an acceptable trade for an implementation a reader can hold in their head; a sliding window belongs in an adapter with somewhere durable to keep the timestamps. There is a spec demonstrating the burst, so nobody has to take this paragraph on trust.

Both are documented rather than smoothed over, because a limiter that quietly allows more than its configured limit is worse than one that says so.

Constants

DEFAULT_MAX_KEYS = 100000

Bounds memory. An attacker can otherwise mint keys — one per login guessed — until the process runs out of memory, which would turn the defence into the vulnerability.

Constructors

new(limit : Int32, window : Time::Span, clock : Clock = SystemClock.new, max_keys : Int32 = DEFAULT_MAX_KEYS)
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
limit
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
size

How many keys are being tracked. For a spec, and for an application that wants to see whether it is near the cap.

Source
window
Source

Nested types