Skip to content

fix(auth): surface refresh-token revocation failure instead of silently reporting success - #50

Merged
nazarli-shabnam merged 1 commit into
mainfrom
fix/refresh-token-revocation-fail-open
Jul 10, 2026
Merged

fix(auth): surface refresh-token revocation failure instead of silently reporting success#50
nazarli-shabnam merged 1 commit into
mainfrom
fix/refresh-token-revocation-fail-open

Conversation

@nazarli-shabnam

Copy link
Copy Markdown
Member

Summary

revoke_all_user_refresh_tokens() caught any Redis exception and returned 0, indistinguishable from "this user genuinely had no active refresh tokens." Its only caller, password-reset confirm, relies on it to guarantee that any refresh token an attacker might hold becomes invalid the moment a user resets their password specifically to lock out unauthorized access. On a Redis hiccup, the endpoint still returned 200 ok while silently leaving those tokens valid -- undermining the entire point of revoking on reset.

Changes

  • token_store.py: revoke_all_user_refresh_tokens() return type is now int | None. None means "could not complete the revocation" and callers must not treat it as "0 tokens existed." Failure logs now include the user_id for easier correlation, at error level (already was).
  • router.py: password_reset_confirm_endpoint raises 503 (not 200) when revocation returns None. The password change itself is not rolled back -- it already succeeded, and reverting it on a Redis hiccup would be worse UX for no security benefit -- but the response now honestly reflects that the "revoke other sessions" guarantee couldn't be confirmed, with a message telling the user to sign out other devices manually.

Test plan

  • Added api/app/tests/test_token_store_revocation.py with a fake Redis, covering all three cases directly: 0 tokens existed, N tokens revoked, and a Redis failure returning None (explicitly asserted != 0, the exact distinction that was previously lost)
  • ruff check ., mypy ., pytest -- all clean (48/48 backend tests)
  • Verified the unaffected happy path end-to-end against a live docker-compose stack (isolating a genuine partial-Redis-failure via black-box HTTP isn't practical here, since consume_password_reset also needs Redis for the reset-token lookup itself -- the unit tests are the precise verification for the failure path): issued a real refresh token for a test user, requested+confirmed a password reset through the actual HTTP endpoints, and confirmed both that the password changed (bcrypt verify) and that the refresh token's Redis key was actually deleted afterward.

Fixes #27

…ly reporting success

revoke_all_user_refresh_tokens() caught any Redis exception and
returned 0, indistinguishable from "this user genuinely had no active
refresh tokens." Its only caller, password-reset confirm, relies on it
to guarantee that any refresh token an attacker might hold becomes
invalid the moment a user resets their password. On a Redis hiccup,
the endpoint still returned 200 "ok" while silently leaving those
tokens valid -- undermining the entire point of revoking on reset.

- token_store.py: return type is now int | None. None means "could not
  complete the revocation," which callers must not treat as "0 tokens
  existed." Logs now include the user_id for easier correlation.
- router.py: password_reset_confirm_endpoint raises 503 (not 200) when
  revocation returns None. The password change itself is not rolled
  back (it already succeeded and reverting it would be worse UX), but
  the response now honestly reflects that the "revoke other sessions"
  guarantee couldn't be confirmed.

Added tests/test_token_store_revocation.py with a fake Redis, covering
all three cases directly: 0 tokens existed, N tokens revoked, and a
Redis failure returning None (explicitly asserted != 0, the exact
distinction that was previously lost).

Verified the unaffected happy path end-to-end against a live
docker-compose stack: issued a real refresh token for a test user,
requested+confirmed a password reset through the actual HTTP
endpoints, and confirmed both that the password changed (bcrypt
verify) and that the refresh token's Redis key was actually deleted.

Fixes #27
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@nazarli-shabnam, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 23 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fd9818f1-d436-4518-9ab9-20038377a9c7

📥 Commits

Reviewing files that changed from the base of the PR and between 012352d and 3baceb5.

📒 Files selected for processing (3)
  • api/app/app/core/token_store.py
  • api/app/app/modules/auth/router.py
  • api/app/tests/test_token_store_revocation.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/refresh-token-revocation-fail-open

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nazarli-shabnam nazarli-shabnam self-assigned this Jul 10, 2026
@nazarli-shabnam nazarli-shabnam added bug Something isn't working API test labels Jul 10, 2026
@nazarli-shabnam nazarli-shabnam added this to the Enhancement Deadline milestone Jul 10, 2026
@nazarli-shabnam
nazarli-shabnam merged commit b057a0d into main Jul 10, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API bug Something isn't working test

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refresh-token revocation silently fails open on Redis errors, undermining password-reset session invalidation

1 participant