Skip to content

feat: give sessions a key change ends their own reason - #80

Open
leviFrosty wants to merge 1 commit into
web-launchfrom
key-changed-reason
Open

leviFrosty wants to merge 1 commit into
web-launchfrom
key-changed-reason

Conversation

@leviFrosty

@leviFrosty leviFrosty commented Oct 31, 2026 •

Copy link
Copy Markdown
Owner

A key change now ends the account's other live sessions with their own reason, key_changed. Before, it used revoked, the same as signing a device out.

Until now a browser couldn't tell two cases apart:

  • A lost browser signed out first, then the key changed. This is the lost-device advice. The browser must erase its data on its next connect.
  • A browser still signed in when the key changed elsewhere. It keeps its data and signs back in with the new key (witness-work ADR 0039; witness-work #1239).

Both refreshes answered revoked.

  • The rekey batch already ends only sessions still live at the change. It now writes ended_reason = 'key_changed' for them. Sessions signed out or revoked before the change keep their reason.
  • A refused refresh answers reason: "key_changed" for those sessions, with changedAt (when the key changed, as key_retired gives it). REFRESH_REFUSALS and SESSION_END_REASONS gain it. ended_reason is plain text, so there's no migration.
  • Activity is unchanged (session.revoked with the changing session as actor).
  • Tests:
    • Unit: other live sessions refresh as key_changed with changedAt, and their rows say so. A session expired before the change refreshes as expired. Sessions signed out or revoked before the change keep their row and refresh as revoked.
    • e2e over HTTP: the rekey test's other session gets key_changed. A session revoked before the change gets revoked.
  • A session already expired at the change still refreshes as expired.
  • Docs: OpenAPI and AGENTS.md name both meanings of key_changed side by side. On a call, the access token is older than the session's own key change, so refresh. On a refresh, the session is over. The rekey description says other live sessions end with ended_reason: key_changed.

App side: witness-work #1239 keeps a browser's data on key_changed alone (no probe), and every revoked wipes. Phones treat any refused refresh the same way, by reopening, so the new reason needs nothing there.

Checks: unit tests (2,000 passed), e2e on a local worker (dev.mjs up --persist-to /tmp/ww-verify-w5kcr2, port 8792; 134 passed, 23 skipped), both with --maxWorkers=4, and pnpm typecheck.

Deploy order: deploy this before witness-work #1239's web build. Otherwise a browser signed out by a key change reads revoked and wipes. That's fail-safe, but it loses that browser's edits made offline.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant