实现账号绑定、持久登录与跨设备对局恢复
CI / test (ubuntu-latest, node 22) (push) Failing after 33m10s
CI / test (ubuntu-latest, node 24) (push) Failing after 34m41s
CI / tooling (lint, imports, typecheck) (push) Failing after 36m11s
CI / test (windows-latest, node 22) (push) Canceled after 0s
CI / test (windows-latest, node 24) (push) Canceled after 0s

This commit is contained in:
herryyun committed 2026-10-14 23:22:50 +08:00
1 parent 488db49f11
commit 7c25c89992
62 files changed
+6855 -1005

No files matched your search

+65
View File
@@ -341,3 +341,68 @@ every vertex on a slab tile at its top or its foot; 战场#03 and #05(上半) ha
`test/render/board3d-scene.test.js` (an official scene on 战场#03 keeps the slabs in the board material and nothing else of
the procedural board; dropping the scene restores the board; act2 m02 adds no slab mesh). Screenshots of the five
affected stages before / after / metal board were compared by hand. Golden: unchanged (client only).
### 31.12 Account binding before entry (fork integration, 2026-10-14)
Owner request: integrate the working account module at 博士代号, automatically use the bound role's nickname, retain
Skland QR for official/Bilibili channels, and keep later upstream merges small. The callsign label and nickname are
hidden until binding, then shown as plain text without an input; 绑定账号
replaces 开始 until a named role is obtained, and returns after unbinding or an empty role list. Boot and local-match
recovery recheck the binding before accepting a cached entry. The dialog, styles, entry policy and session resolver
live in the module's own directories, with small title/boot/recovery hooks. Game-entry requests use the
existing reconnect token and a reserved namespace; the independent diagnostic routes require an administrator key
and cannot read or mutate these rows. No new WebSocket messages or simulation rules. Multiple roles select the default, retain a
still-valid choice on refresh/reopen, and immediately update the callsign when switched. No manual callsign entry.
The official account-page icons are referenced remotely; authorization redirects remain provider-operated.
The nickname retains its person icon. Opening the scan tab automatically fetches a QR: replacement is every 10 s,
switching to 60 s from the first status 101 (waiting for phone confirmation), with manual refresh retained. Refresh
keeps the old QR visible until the new image is encoded, then replaces its source in place without a blank frame. Requests
are serial and check confirmation before replacement; switching tabs or hiding the page pauses timers, and closing
the dialog or binding stops them. The provider's 101 is exposed as `awaitingConfirmation` without changing `waiting`.
Persistent login uses a 30-day HS256 JWT in an HttpOnly, SameSite=Strict cookie (Secure over HTTPS), with signing
key and revocable login/role records in the same SQLite database. New tabs and restarted servers restore the
selected role without another provider login. The JWT contains no provider credentials. Account APIs derive
ownership on the server, reject cross-site requests and foreign role selection, and default-close diagnostic access.
The optional WebSocket account policy resolves hello and every room/game request to the owned role; a copied game
reconnect token alone cannot claim it. UID plus server region determines stable game identity, allowing duplicate
nicknames; optional `gameId` and name changes do not change that identity. Unbind clears credentials and revokes
old JWTs/open-socket authority; late provider replies cannot restore a cancelled binding. Static/media/pack paths
deny the configured database and its WAL/SHM files, including aliases and Windows casing. These changes follow the
owner's explicit persistence/access-control request; rooms/matches remain memory-only.
Reference: [skland.md](../skland.md). Tests cover token rejection/player isolation, nickname selection, authenticated
requests and the real title/modal in a browser with a stub account provider (QR, SMS and password/CAPTCHA continuation,
landscape-phone layout, hidden/plain-text callsign with person icon, empty roles, unbinding, fresh-tab JWT restoration
and cached-entry checks). Server tests cover forged JWT/claims, foreign roles, logout/replay, restart/expiry,
protected database files, cancelled provider replies and same-name/different-UID/region identities.
Deterministic timer tests cover automatic QR generation, exact 10/60 s boundaries, pause/resume, late responses,
manual refresh versus confirmation and retry after failure. Golden: unchanged
(no simulation changes).
### 31.13 Account-scoped cross-device recovery (fork integration, 2026-10-14)
Owner request: continue implementing recovery with the account across devices. The title now offers 恢复账号对局
from authenticated server state, replacing the visible local-token recovery shortcuts. A device with no local save
can sign in independently, select the same UID + provider region, inspect its retained seat and continue it. Login,
cookie restoration and lookup alone do not disconnect another device; Start or recovery confirmation explicitly
obtains the role's reconnect token. Only a successful welcome enters the UI, rotates the token and replaces the old
socket. Old-device reloads and duplicated tabs cannot silently take the role back; the connection banner can still
explicitly continue through checked account entry, and anonymous welcomes return to the title. The UI handles empty, loading,
stale/error and refresh states and warns about taking over a live connection.
The server checks both credentials and the selected role, never a supplied player/name/UID/region. Public account
ids encode 128 digest bits as base64url after a_ (24 characters): the previous full hexadecimal digest exceeded
player-id and normal-field limits, rejecting watch/kick/battle-result messages. No wire limit needs to change. Recovery selectors
belong to a particular room/match; stale or foreign selectors fail without minting a fresh game. Looking up a seat
does not extend its lifetime. The normal lobby cleanup runs before an expired stable role id is reused, preventing
an orphaned old match. The same Match/PlayerState is resumed, including funds, shop and lineup; no simulation rule,
protocol message or generated data changes. Solo 24-hour/co-op 10-minute disconnect windows and waiting-room grace
are unchanged. Matches remain memory-only: this is not save recovery after a server restart, nor synchronization of
local preferences/statistics. These are owner-requested account policies, not deviations from official gameplay.
Normative updates: design/network.md §8 and its recovery note, design/client.md required-account entry, META.md §1.3;
references: skland.md §5 and ARCHITECTURE.md / DEPLOY.md. Tests: test/skland/recovery.test.js exercises actual Match
objects through independent HTTP/WS clients; test/skland/recovery.browser.test.js runs two isolated browser contexts
with a stub provider, plus old-device and duplicated-tab checks and desktop/phone layout. Existing account, local
identity, i18n and docs checks are rerun. Golden moves: none (no match/sim rules changed).