Repository navigation
Give each auth route its own rate-limit bucket, and release 0.17.2 - #45
Merged
Merged
Conversation
Every Redis-backed limiter writes into one keyspace, and the key was the client IP alone, so all six auth routes counted into a single sorted set and the smallest cap won for all of them. With register at 3, any three auth requests, a token refresh or a failed login included, returned 429 on the next registration for the rest of the window. It also undid the separate bucket reauth was given in 0.17.0. The key now carries the route name. Existing counters reset once on deploy. Namespacing at the caller rather than inside a backend keeps custom backends working unchanged, and keeps these buckets clear of any limiter an application passes to RateLimitMiddleware. Auditing the two backends against each other for the same class of problem turned up two more: The Redis challenge store raised on a non-positive TTL where the in-memory one simply wrote an already-expired entry, so PASSKEY_CHALLENGE_TTL=0 answered 500 from a route that needs no account. Both now store nothing and return None. No numeric setting had bounds. MAX_LOGIN_ATTEMPTS=0 locked every account on its first failed login; ACCESS_TOKEN_EXPIRE_MINUTES=0 issued dead tokens. These are rejected at construction now. The three settings that document a meaningful zero keep it. The regression tests run against both backends, since the memory one cannot show any of this.
Bumps the version and moves the Unreleased changelog entries under 0.17.2.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two commits: the fixes, then the version bump. One merge instead of two, since the release contains nothing else.
The reported bug
Every Redis-backed limiter writes into one keyspace and the key was the client IP alone, so all six auth routes counted into a single sorted set and the smallest cap won for all of them. With
registerat 3, any three auth requests — a token refresh, a failed login — returned429on the next registration attempt for the rest of the window. It also undid the separate bucketreauthwas given in 0.17.0.Reproduced before changing anything:
The key now carries the route name. Namespacing at the caller rather than inside a backend keeps custom backends registered through
register_rate_limiter_backendworking unchanged, and keeps these buckets clear of any limiter an application passes toRateLimitMiddleware, which shares the same prefix.Two more of the same kind
The last three bugs all came from the same place: the suite runs the memory backend, production auto-selects Redis whenever
REDIS_URLis set. So I diffed the two backends across all four protection components on identical call sequences.PASSKEY_CHALLENGE_TTL=0meant500fromPOST /passkeys/authenticate/begin, a route that needs no account. Both now store nothing and answerNone.MAX_LOGIN_ATTEMPTS=0locked every account on its first failed login;ACCESS_TOKEN_EXPIRE_MINUTES=0issued dead tokens. Rejected at construction now, naming the setting.JWT_LEEWAY_SECONDS,PASSWORD_MAX_LENGTHandREAUTH_MAX_AGE_SECONDSkeep their documented zero.After the fixes the backends agree on every case checked.
Upgrade notes
Checks
ruff, ruff format, mypy --strict, pytest (602 passed), mkdocs build --strict. The new guards are mutation-tested, and the rate-limit test fails on Redis and passes on memory when the fix is reverted, which is exactly why this escaped.