Repository navigation
Issue our own session tokens instead of checking PHPSESSIDs per request - #75
Conversation
Clients swap a verified PHPSESSID for a token once (Login), then send only
the token. Checking it is a D1 lookup, so requests no longer wait on slow
fetches to xmoj, and we stop receiving the user's cookie on every call.
Tokens stand for the user, so they survive xmoj's daily logouts; they
expire after 30 days unused or on Logout.
Scrapes that must read xmoj as the user call RequireSession, which asks the
client to retry once with its PHPSESSID. GetStd caches scores so it rarely
needs one, and IfUserExist reads the public userinfo.php without a cookie.
The notification socket accepts a token, and can mint one for a client that
connects with its PHPSESSID and IssueToken=1. Legacy {SessionID, Username}
auth is unchanged for old clients.
Also fixes the auth retry loop, which tested Data.Success (always
undefined) and so ran CheckToken three times on every request.
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 GuideThe backend now exchanges a verified PHPSESSID for a random hashed session token and uses D1 lookups for subsequent API and WebSocket authentication, retaining legacy compatibility while requesting PHPSESSIDs only for necessary xmoj scrapes. It also adds score caching, token cleanup, corrected auth retry behavior, migration support, documentation, and extensive tests. Sequence diagram for session token exchange and API authenticationsequenceDiagram
actor Client
participant API as Backend API
participant XMOJ
participant D1
Client->>API: CheckToken(SessionID, Username)
API->>XMOJ: CheckSessionID
XMOJ-->>API: Verified user
Client->>API: Login
API->>D1: IssueSessionToken
D1-->>API: Store SHA3(token) and user_id
API-->>Client: Token and ExpireTime
Client->>API: CheckToken(Token)
API->>D1: ResolveSessionToken
D1-->>API: Username
API-->>Client: Authenticated request
Sequence diagram for on-demand PHPSESSID during an xmoj scrapesequenceDiagram
actor Client
participant API as Backend API
participant D1
participant XMOJ
Client->>API: Request with Token
API->>D1: ResolveSessionToken
D1-->>API: Username
API-->>Client: SessionRequired
Client->>API: Retry with Token and SessionID
API->>D1: ResolveSessionToken
D1-->>API: Username
API->>XMOJ: CheckSessionID for token user
XMOJ-->>API: Verified PHPSESSID
API->>XMOJ: UploadStd or GetStd scrape
XMOJ-->>API: Scrape result
API-->>Client: Result
Entity relationship diagram for session and score cachingerDiagram
SESSION_TOKEN {
string token_hash PK
string user_id
integer create_time
integer last_used
}
PROBLEM_SCORE {
string user_id PK
integer problem_id PK
integer score
}
SESSION_TOKEN }o--|| USER : authenticates
PROBLEM_SCORE }o--|| USER : stores_best_score_for
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
All reported issues were addressed across 8 files
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
…fe score cache - Only mint a token over the WebSocket for a real Upgrade request. - Never forward a client-supplied issuedToken to the Durable Object. - Caching a score never fails the request; losing the insert race raises the cached score instead of erroring on the primary key. - Index session_token.last_used for the daily cleanup. - Drop ExpireTime from Login: the lifetime slides, so it went stale. - Tests match token lookups on the bound hash. 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 7 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
If our conditional update changes nothing, a concurrent request already cached a score at least as high, from a newer scrape. Return that rather than our stale one, so an eligible user isn't refused. 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 2 files (changes from recent commits).
Requires human review: Introduces backend-issued session tokens replacing per-request PHPSESSID checks, with new D1 tables, WebSocket token auth, and an auth retry-loop fix.
Turn on auto-fix | Re-trigger cubic
What does this PR aim to accomplish?:
At the moment every request carries the user's raw xmoj PHPSESSID, and we verify it against xmoj. That causes three problems:
CheckToken, and every notification WebSocket connect, waits on one.This PR has the backend issue its own session token. Clients send the PHPSESSID once in exchange, and after that send only the token.
How does this PR accomplish the above?:
Source/SessionToken.ts, migration0006)Logintakes the existing legacy auth (phpsessidcache, else profile.php) and returns a 256-bit random token. No expiry time is returned, because the lifetime slides with use.SHA3(token) → user_idinsession_token.last_usedis written at most once a day), or whenLogoutis called.CheckToken{Token}. The username always comes from the token row.{SessionID, Username}still works unchanged for old script versions.RequireSession(). With no cookie it returns{SessionRequired: true}, and the client retries once with{Token, SessionID}.UploadStdandGetStd.problem_scorecaches each user's best score. Scores only go up, soGetStdstops scraping (and stops needing a session) once a score clears the threshold.IfUserExistreadsuserinfo.phpwithout a cookie, since that page is public, and counts token holders as existing users.?Token=is a D1 lookup: no xmoj fetch, and no PHPSESSID in the URL.?SessionID=…&IssueToken=1is for clients without a token yet. The worker mints one and the DO returns it in theconnectedmessage.?SessionID=behaves exactly as before.Process()testedData["Success"], which is always undefined. Every request therefore ranCheckTokenthree times. Now a legacy check runs once and retries only on failure, and a token check never retries.Deploy order: run
wrangler d1 migrations apply xmoj-bbs --remotebefore deploying. Merge and deploy this before the userscript PR, which needsLogin.Testing
npm test: 134 pass, 26 of them new. They cover:last_usedrefreshSessionRequiredIfUserExistsending no cookieLoginstoring only the hash, andLogoutIssueToken, and legacyissuedTokenbeing droppedwrangler deploy --dry-runbundles, and the migration applies to a local D1.wrangler dev, with a fake PHPSESSID seeded into the localphpsessidcache, and checked:GetStdsession retryLoginwhen the WS can't mintwrangler devwith a real xmoj account (zhuchenrui2) and the userscript's own auth code. Checked:GetStdasking for the cookie and scraping livestatus.phpGetStdserved fromproblem_scorewith no cookieLogoutDoes 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
Summary by Sourcery
Replace per-request PHPSESSID verification with backend-issued session tokens while preserving legacy authentication and reducing authenticated XMOJ requests.
New Features:
Bug Fixes:
Enhancements:
Deployment:
Documentation:
Tests: