Skip to content

Tech-debt batch 21: password reset revokes all credentials; admin create/invite after commit (#686 #692) - #699

Merged
antosubash merged 4 commits into
mainfrom
tech-debt/b21-users-revocation
Oct 30, 2026
Merged

antosubash merged 4 commits into
mainfrom
tech-debt/b21-users-revocation

Conversation

@antosubash

Copy link
Copy Markdown
Owner

Tech-debt batch 21 (from the 2026-10-28 scan).

Closes #686
Closes #692

What changed

  • Password reset signs the account out everywhere (users: password reset leaves sessions, access tokens and refresh tokens valid #686). A reset left browser sessions, bearer access tokens and refresh tokens valid. fastapi-users commits the new hash inside user_db.update, then calls on_after_reset_password, so revoking in that hook would leave a window, or a permanent state if the second write failed, where the new password is durable but old credentials still work. ResetRevocationMixin (users/reset_revocation.py) revokes inside _update, so the new password and the revocation land in one commit. It then evicts the cached session counters once the commit is durable. A failed eviction is logged rather than failing the completed reset.
  • Password change, "sign out everywhere" and reset now share users/credentials.py (revoke_all_credentials / revoke_credential_rows / announce_revocation).
  • Admin create/invite after commit (users admin: single create/invite publish events and send the invite email before commit #692, rest of users: deleting a user leaves tenant memberships, shares and grants; user events published pre-commit #627). UserCreated/UserInvited go through publish_admin_event. The invite email and the request-less verification email are sent from on-commit callbacks, so a failed commit sends neither.
  • POST /api/users/admin/invite with an existing email returned 500 (unhandled UserAlreadyExists, found in QA). It now returns 409, like create-user.

Tests

  • test_users_reset_credentials.py:
    • after a reset, the old refresh token, old bearer token and old session are all rejected;
    • a failing post-reset hook still leaves everything revoked (red on the old code);
    • a rejected reset revokes nothing.
  • test_users_admin_create_commit.py:
    • a forced commit failure sends no mail and delivers no events (durable and inline);
    • a duplicate invite returns 409 and sends no mail.

Verification

  • make lint ✓ · make test-js ✓ · make test-py ✓ (6346 passed, 39 skipped)
  • Code review: 2 passes, clean after the atomicity fix.
  • Browser QA (playwright-cli, local stack): 8 scenarios.
    • Reset kills session, bearer and refresh tokens; the new password works; a weak password or bad token revokes nothing.
    • Admin create and invite work, each mail sent once and after success.
    • One bug found (duplicate invite returned 500), fixed and re-tested live.

https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

…invite side effects (#686, #692)

A password reset left browser sessions, bearer access tokens and refresh
tokens valid. on_after_reset_password now revokes them through the same
revoke_all_credentials helper the password change and "sign out
everywhere" use.

Admin create/invite published UserCreated/UserInvited and sent the invite
and verification email before the commit; they now go through
publish_admin_event and on-commit callbacks.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
fastapi-users commits the new hash inside user_db.update and only then
calls on_after_reset_password, so revoking there left a window (and, if
that second write failed, a permanent state) where the new password was
durable but old sessions and tokens still worked. ResetRevocationMixin
revokes inside _update so both land in one commit, then evicts the
cached counters once it is durable.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
… the reset (#686)

Adds a regression test that a rejected reset password revokes nothing.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
POST /api/users/admin/invite let UserAlreadyExists escape as a 500 (found
in QA); it now matches admin create-user.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review ✅ Completed 2026-10-30T06:59:01.544195Z b26df00 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying simple-module-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: b26df00
Status: ✅  Deploy successful!
Preview URL: https://53ccef96.simple-module-python.pages.dev
Branch Preview URL: https://tech-debt-b21-users-revocati.simple-module-python.pages.dev

View logs

@antosubash
antosubash merged commit 72da755 into main Oct 30, 2026
17 checks passed
@antosubash
antosubash deleted the tech-debt/b21-users-revocation branch October 30, 2026 07:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant