fix(server): resolve clan-tag ownership before rejoin identity updates - #4860
fix(server): resolve clan-tag ownership before rejoin identity updates#4860evanpelle wants to merge 1 commit into
Conversation
A pre-start page refresh restored a reserved clan tag the server had already dropped at admission: the Worker passed the join_verify pair straight into rejoinClient before the resolveClanTag block, and join_verify only censors — it has no membership data. Reported in the wild by a clan whose pending applicant wore their tag in game a1Vnq3sk. Reorder the join path so the account fetch and clan-tag ownership resolution run before any identity-carrying rejoin. Skipped-verify reconnects (post-start, or unchanged identity) keep their zero-API fast path, so mass reconnects at game start stay off the API. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WalkthroughChangesJoin and reconnect identity
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant Worker
participant AccountAPI
participant GameServer
Client->>Worker: Send join request
Worker->>AccountAPI: Load account when required
AccountAPI-->>Worker: Return authorization result
Worker->>GameServer: Rejoin with screened identity and resolved clan tag
GameServer-->>Client: Replace WebSocket
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/server/Worker.ts`:
- Line 562: Update the unauthorized join handling in Worker.ts so the close
reason passed to ws.close() uses translateText() instead of a hardcoded
“Unauthorized” string, and add the matching English resource entry in
resources/lang/en.json.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 40da452d-218d-4341-bd5a-12ce6da1a703
📒 Files selected for processing (2)
src/server/GameServer.tssrc/server/Worker.ts
| const loadAccount = async (): Promise<boolean> => { | ||
| if (claims === null) { | ||
| if (allowedFlares !== undefined) { | ||
| log.warn("Unauthorized: Anonymous user attempted to join game"); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Localize the close reason.
ws.close() sends "Unauthorized" to the client. Route this text through translateText() and add its English resource entry.
As per coding guidelines, “All user-visible text must go through translateText() and have a corresponding entry in resources/lang/en.json.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/server/Worker.ts` at line 562, Update the unauthorized join handling in
Worker.ts so the close reason passed to ws.close() uses translateText() instead
of a hardcoded “Unauthorized” string, and add the matching English resource
entry in resources/lang/en.json.
Source: Coding guidelines
Problem
A pre-start page refresh restored a reserved clan tag the server had already dropped at admission. Reported in the wild: a clan's pending applicant (not yet a member) played game
a1Vnq3skwearing the clan's tag.Mechanics of the bypass:
resolveClanTagdrops the reserved tag because the player's/users/@meclanslist doesn't include it (a pending request lives in the separateclanRequestsfield).planJoinVerifyreturnsverify— butjoin_verifyonly censors (it receives no account identity, so it cannot check membership) and echoes the tag back approved.rejoinClientand returned — before ever reaching theresolveClanTagblock, which only ran on the fresh-join path.rejoinClientapplies the tag with no ownership check.No exploit knowledge needed: set a tag you don't own, join a lobby, hit F5. (This gap was flagged as a pre-existing follow-up during the #4680 review.)
Fix
Reorder the join path so the account fetch and clan-tag ownership resolution run before any identity-carrying rejoin:
claims/getUserMe/allowed-flares block becomes aloadAccount()closure.Behavior notes:
getUserMecall as before, just earlier.getUserMecall (they already made ajoin_verifycall), and now go through the allowed-flares gate and close on agetUserMefailure — consistent with how a fresh join handles the same conditions.rejoinClientdoc comment to state the strengthened caller contract.Testing
npx vitest tests/server tests/Privilege.test.ts --run— 355 tests pass. TheresolveClanTagmatrix intests/Privilege.test.tsalready covers the pending-member case (reserved tag, not inownedClanTags→ dropped); this PR is pure ordering in the Worker's inline ws handler, which has no test harness (same as the existing Turnstile gating there).npx tsc --noEmitandnpm run lintclean.🤖 Generated with Claude Code