feat(bridge): protocol 3 — /link, /unlink, and the first command the site sends #3
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/phase-6-identity"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
R1's identity flow, in the shape the UO bridge already proved: the player asks in game, the plugin mints a one-time code and hands it to them, and the website confirms it back through the sidecar. The site is the author of record; this plugin holds no link at all.
/linkis chat, not console, and the reply is private (D20)[ChatCommand("link")]. Both frameworks consume a/command rather than broadcasting it andSendReplyaddresses one player, so neither the request nor the code reaches anybody else's chat. That is load-bearing rather than polish: a code read off a stream is a code somebody else can spend.Pending codes live in memory and nowhere else (D21, D22)
Matching
module-uo, whose shape was read out ofBridgeAccountLink.csrather than guessed: six characters from an alphabet with no O, 0, I or 1, a five-minute TTL, a thirty-second cooldown per player, one outstanding code each, and a purge timer — because an unconfirmed code is never looked up, so nothing else would ever remove one.A plugin reload drops them. Phase 7b's config editor will do that routinely, and the cost is a player typing
/linkagain, which is cheaper than an unconfirmed credential living in a second process.The code is deliberately NOT in
account.link.requestedThat event exists so an operator can see linking being used. The code travels through the player, which is what makes typing it into a signed-in browser proof that they are the one who asked.
/unlinkemits rather than deletesThere is nothing here to delete. It is the escape hatch that makes the site's refusal to move an already-linked Steam id safe (D23): a player who linked the wrong account walks to a computer they are already sitting at. The authority on that path is the Steam account itself.
link.confirmA code is consumed on the first lookup whether or not it turns out to be expired, so a code cannot be probed twice — which is why the removal happens before the expiry check rather than after it.
unknownandexpiredstay separate in the reply (an operator wants to know whether codes are being guessed or going stale) and the website says the same sentence for both.The checker grew a surface
checkPlugin.jsnow checks chat commands, because they bind by reflection exactly like a hook and fail the same silent way. It asserts the signature, the void return and the absence of duplicates, and — like the hook parser before it — a test reads the real plugin so a regex that matches nothing cannot pass. Verified by breaking it five ways.Where this has been
Uploaded to the
rust-oxiderig and booted:[Runic Gateway] protocol 3, serverId 'rust-oxide', sidecar 192.168.0.101:7800, compiled in 0ms, plugin loaded. It could not reach the sidecar on the development machine — no inbound firewall rule for TCP 7800 on that host — so the code-from-the-game path is not walked here. It is written down instead, as the new identity walk indocs/rust-link/PLAYER_WALK.md, beside phase 3's player walk and for the same reason: a code reaches a player and nobody else, so no console can read one.Protocol bumped in both declaration sites this repo owns (
ProtocolVersionandoverlay.toml).AI disclosure
🤖 Generated with Claude Code
https://claude.ai/code/session_01PMH6bw1jXMgbyF3ZWGEzSM