Skip to content

Tech-debt batch 15: users — atomic refresh rotation, hashed tokens, user-deletion cleanup (#625 #626 #627) - #648

Merged
antosubash merged 6 commits into
mainfrom
fix/debt-batch-15
Oct 24, 2026
Merged

antosubash merged 6 commits into
mainfrom
fix/debt-batch-15

Conversation

@antosubash

Copy link
Copy Markdown
Owner

Summary

  • users: concurrent refreshes of one refresh token both succeed; no reuse detection #625: refresh tokens are consumed atomically (UPDATE … RETURNING). Reusing a spent token revokes its whole family (RefreshToken.family_id) and returns 401, and the revocation commits. Migration a625f0b7c912 adds a users branch label.
  • users: refresh/access tokens stored in plaintext and never pruned #626: access and refresh tokens are stored as SHA-256 digests (users.token_storage). Logins and refreshes prune spent rows (100 per call, keeping reuse evidence for live families). Cookie and API logout now delete the access-token row server-side. Behaviour change: migration a626b1c8d023 deletes existing token rows, so clients log in once more.
  • users: deleting a user leaves tenant memberships, shares and grants; user events published pre-commit #627: UserDeleted, UserDisabled and RoleAssigned publish durably, or inline in the request transaction without background_tasks; ordinary subscribers run only after commit. tenants, sharing and permissions purge the deleted user's memberships, shares and direct grants; a tenant left with no owner is suspended. RoleAssigned fires only for newly added roles. UserDeleted moves to simple_module_core.user_events (still re-exported from users.contracts.events). Fixes set_roles returning an empty roles list when the user was already loaded in the session.

Closes #625
Closes #626
Closes #627

Verification

  • Browser and API smoke at /users/login on port 8215 (QA ship-round-1), server log clean.
  • Local CI: make lint, make test-py (6183 passed), make test-js (770 passed) and make migrations-roundtrip-pg are all green.
  • Verification report: https://claude.ai/artifact/JRnoGf15TCinteRiP7PrgB

QA Report

  • Full QA cycle completed (1 iteration), depth shallow
  • 0 bugs found, 0 fixed
  • Scenarios: cookie login after migration, logout revokes the old cookie, token refresh rotation with family revoke on reuse, user deletion removes the tenant membership (via the outbox worker)
  • Full QA report: https://claude.ai/artifact/U8aeLzUecYx8FcuXGFRWht

Report links are Claude Artifacts and start private — the author can share them
from claude.ai/code/artifacts if reviewers need access.

Test plan

  • After upgrade, sign in once; replaying a pre-logout cookie returns 401
  • Replaying a used refresh token returns 401 and kills its successor
  • Deleting a user removes their tenant memberships once the outbox drains
  • CI is green

…revokes its family (#625)

UPDATE ... WHERE revoked_at IS NULL RETURNING picks exactly one winner under
concurrency; presenting an already-revoked token revokes every live token in
its family (new family_id column, migration a625f0b7c912, branch label users).

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…he cookie token on logout (#626)

SHA-256 digests replace raw bearer secrets (migration a626b1c8d023 clears existing
rows: one re-login). Logins/refreshes prune the user's expired/revoked tokens in
bounded batches, keeping reuse evidence for live families (#625). Logout now deletes
the presented access token instead of leaving it valid until expiry.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…ish user events after commit (#627)

UserDeleted, UserDisabled and RoleAssigned are published durably (or inline
in the request transaction without background_tasks); ordinary subscribers
run only after commit. tenants, sharing and permissions purge the deleted
user's memberships, shares and direct grants; a tenant left without an owner
is suspended. RoleAssigned fires only for newly added roles, and set_roles
now reloads roles with populate_existing so a user already in the identity
map no longer comes back with an empty roles list.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
UserDeleted moves to simple_module_core.user_events so tenants/sharing
subscribe without importing users; docs describe hashed tokens and the
users migration label.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Deploying simple-module-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: d9fbcdb
Status: ✅  Deploy successful!
Preview URL: https://7ba46e8c.simple-module-python.pages.dev
Branch Preview URL: https://fix-debt-batch-15.simple-module-python.pages.dev

View logs

@chatgpt-codex-connector

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 🔄 Running since 2026-10-24T17:46:38.221200Z d9fbcdb 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.

@antosubash
antosubash merged commit 63a12da into main Oct 24, 2026
17 checks passed
@antosubash
antosubash deleted the fix/debt-batch-15 branch October 24, 2026 17:59
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