fix(desktop): WS ticket auth for the live feed; desktop logs never reached the server
Build & push images / images (push) Successful in 2m51s
Release desktop / bundle (push) Successful in 41m19s

The v0.1.4 Origin fix cleared only the first of two gates in /api/ws's
preHandler. The second, req.jwtVerify(), reads the HttpOnly cookie — which
tauri-plugin-websocket (a bare tungstenite client, no cookie jar) can never
send. Every desktop handshake 401'd and use-live-feed reconnected every 10s
(confirmed in the park-2 server log).

- routes/ws.ts: POST /api/ws/ticket (cookie + CSRF auth) mints a 30s,
  single-use, in-memory ticket; the WS preHandler accepts it via an
  x-ws-ticket header after the Origin check, then the same report:read
  role check. Browser cookie path unchanged; JWT stays out of JS.
- platform-ws.ts: fetch a ticket before connect, send it with the Origin
  header; connect failures now go through logClient (rate-limited).
- logger.ts: flush read the CSRF token from document.cookie, null on
  desktop, so every desktop POST /api/logs 403'd and was dropped silently —
  no desktop client log had ever reached app_logs. Stash moved to a
  dependency-free lib/desktop-csrf.ts shared by api.ts and logger.ts.
- backend-config.ts: ConnectScreen probe uses the unauthenticated /health
  (now also returns app: "parking-system") instead of accepting any 401.
- README: local-AppImage release gate — tauri dev runs at
  http://localhost:5173, not tauri://localhost, so none of these
  origin-dependent bugs reproduce there.
- wiki: new section + log entry; four citation corrections.

Requires the server image with this commit deployed before the new desktop
build connects (the ticket endpoint must exist).

Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
This commit is contained in:
2026-09-04 12:09:30 +02:00
parent 70e1e9939f
commit 8fa66c9911
11 changed files with 279 additions and 56 deletions
+19
View File
@@ -2841,3 +2841,22 @@ value in the login/me JSON body; the desktop client stashes it in memory and ech
reading document.cookie. assertCsrf() itself is untouched — the cookie is still what's verified,
and reqwest was already sending it correctly; this only fixes how the desktop client *learns* the
value. Full detail (including the exact CSP/capability tradeoffs) on [[desktop-shell-tauri]].
## [2026-09-04] fix | Desktop live feed: WS handshake can't carry the cookie → single-use ticket; desktop logs never reached app_logs
Retrospective of the 2026-09-03/04 desktop run (six releases in 26 h) found the v0.1.4 Origin fix
cleared only gate one of two in routes/ws.ts: gate two is req.jwtVerify() reading the HttpOnly
cookie, and tauri-plugin-websocket has no cookie jar at all — so every desktop handshake 401'd and
use-live-feed reconnected every 10 s (confirmed in the park-2 server log). Fixed with a 30-second,
single-use, in-memory WS ticket minted by POST /api/ws/ticket over normal cookie+CSRF auth and
presented in an x-ws-ticket header; Origin check still runs first, browser path unchanged, JWT
stays out of JS. Second finding: logger.ts read the CSRF cookie via document.cookie, null on
desktop, so every desktop POST /api/logs 403'd and was dropped silently — no desktop client log had
EVER reached app_logs, which is why "no logs whatsoever" kept happening and why yesterday's
logClient fix couldn't help. Stash moved to lib/desktop-csrf.ts, shared by api.ts and logger.ts;
WS connect failures now go through logClient (rate-limited). Third: the ConnectScreen probe now
uses the unauthenticated /health (extended with app: "parking-system") instead of accepting any
401. Also corrected four wiki citations (WebKit 171934 scope, tauri#11518 is closed, the HTTP
plugin does set Origin itself, the http-scope "quirk" is URLPattern default-port semantics) and
added a local-AppImage pre-tag gate to the desktop README, since tauri dev cannot reproduce any
of these origin-dependent bugs. Full detail on [[desktop-shell-tauri]].