Repository navigation
Add LogoutAll: sign a user out of the backend on every device - #76
Conversation
Revokes every session token the user holds, drops their cached PHPSESSID checks (so old clients re-verify with xmoj too), and closes their open notification sockets with code 4001. Devices with a live xmoj session just exchange it for a new token; a token on its own is locked out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dqWu3YgeVRGwBt5pXDq4A
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Reviewer's GuideImplements Sequence diagram for LogoutAll across devicessequenceDiagram
participant Client as Authenticated client
participant Process
participant Database
participant Notifications as NotificationManager
participant Devices as Other connected devices
Client->>Process: LogoutAll({})
Process->>Database: RevokeAllSessionTokens(userId)
Process->>Database: Delete phpsessid rows for userId
Process->>Notifications: POST /disconnect with X-Notification-Token
Notifications->>Devices: close sockets with code 4001
Notifications-->>Process: OK
Process-->>Client: LogoutAll success
Sequence diagram for device re-authentication after LogoutAllsequenceDiagram
participant Device
participant Process
participant Database
participant XMOJ
Device->>Process: Next request with old token
Process->>Database: ResolveSessionToken(oldToken)
Database-->>Process: Token not found
Process-->>Device: Re-authentication required
Device->>XMOJ: Prove live XMOJ session
XMOJ-->>Device: Verified PHPSESSID
Device->>Process: Login with PHPSESSID
Process->>Database: IssueSessionToken(userId)
Database-->>Process: New token
Process-->>Device: New backend token
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8883f7495c
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
All reported issues were addressed across 6 files
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
Our compatibility date predates automatic close replies, and webSocketClose never called close() back, so the handshake was left half-done. Cloudflare's docs require the reply on old dates to avoid 1006 closes. Checked in Chrome: a LogoutAll now reaches clients as a clean 4001 "Logged out". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dqWu3YgeVRGwBt5pXDq4A
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
…e code - The worker forwards the token's hash and the Durable Object re-checks it when admitting a socket. The socket is registered before the D1 check, so /disconnect either closes it or ran first and the token is gone; a handshake racing LogoutAll can no longer stay subscribed. - webSocketClose answers codes close() rejects (1001 when a page closes, and others) with 1000 instead of leaving the handshake unanswered. - /disconnect returns 400 for a body it can't read instead of failing silently. - CLAUDE.md: the socket close is best-effort. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dqWu3YgeVRGwBt5pXDq4A
There was a problem hiding this comment.
0 issues found across 5 files (changes from recent commits).
Requires human review: Adds LogoutAll revoking all tokens, cached PHPSESSIDs, and notification sockets, plus WebSocket close/race fixes. The decisive tradeoffs are the new auth/data-handling policy and API surface.
Turn on auto-fix | Re-trigger cubic
fetch had grown to handle /notify, /disconnect and the socket handshake with its token re-check in one body. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dqWu3YgeVRGwBt5pXDq4A
There was a problem hiding this comment.
0 issues found across 1 file (changes from recent commits).
Requires human review: Adds LogoutAll revoking all tokens, cached PHPSESSIDs and open notification sockets, plus WebSocket close/race fixes. Decisive tradeoffs are the new public API surface and authentication/data-handling policy.
Turn on auto-fix | Re-trigger cubic
What does this PR aim to accomplish?:
Adds "log out everywhere".
Logoutfrom #75 only revokes the current device's token. If a token leaks, or the user just wants to sign out every browser, there was no way to kill all of them.How does this PR accomplish the above?:
New endpoint
LogoutAll. It accepts any auth, token or legacy, and:session_tokenrow for the user (RevokeAllSessionTokensinSource/SessionToken.ts)phpsessidcache rows, so old script versions also have to re-verify with xmoj/disconnectchannel on the Durable Object. It is guarded by the sameX-Notification-Tokenas/notify, and closes with code4001, which tells the client its token is gone. A failure here doesn't fail the request.What it does and doesn't do:
Close-handshake fix. This is an existing bug, which this feature exposed. Our
compatibility_date(2023-09-17) predates the runtime answering close frames automatically, andwebSocketClosenever calledclose()back. Cloudflare's docs say you must do that on old dates, or clients see1006closes. The handler now replies with the client's code. I leftcompatibility_datealone: in Chrome the handler fix alone delivers a clean4001 "Logged out", and bumping three years of runtime defaults wasn't needed for this.No migration is needed.
Testing
npm test: 140 pass, 5 of them new:LogoutAlldeletes exactly the user's tokens andphpsessidrows, and calls/disconnectwith the internal tokenLogoutAllstill succeeds if the DO is unreachable/disconnectcloses all of that user's sockets with4001and leaves other users connected/disconnectwithout the internal token returns 401webSocketClosereplies with the client's code, and swaps the reserved 1005/1006 for 1000wrangler deploy --dry-runbundles.wrangler dev: device B ran the userscript's real auth and socket code in Chrome, while device A and a "thief" holding only a copy of B's token ran in Node.LogoutAllclosed B's socket with a clean4001, and B dropped its token.TokenInvalid.The userscript side (a 注销所有设备 menu entry) is in XMOJ-Script-dev/XMOJ-Script#1064.
Does this PR need a documentation update?:
No.
CLAUDE.mdis updated in this PR.By submitting this pull request, I confirm the following:
devfor XMOJ-Script,masterfor everything else).git rebase)🤖 Generated with Claude Code
https://claude.ai/code/session_017dqWu3YgeVRGwBt5pXDq4A