feat(app): CORS for appen, en billet til live-synk, og /ping siger hvad den er

Andet trin mod iOS-appen. Appen henter sin flade fra capacitor://localhost og
taler med den server, ejeren har skrevet ind, så hver anmodning er cross-origin.

- CORS på /api/v1 for appens origin og ingen andres. Ingen
  Allow-Credentials: appen bærer sin session som bearer, aldrig som cookie.
  Alle andre origins får ingen headere, præcis som før.
- POST /ws-ticket giver en engangsbillet, der lever 30 sekunder, og
  /ws?ticket=… åbner socket'en med den. En browser kan ikke sætte en header
  på et WebSocket-håndtryk, og tokenet selv i URL'en ville ligge i hver
  proxy-log i månedsvis. En socket med billet springer origin-tjekket over,
  fordi den ikke bærer nogen cookie, som en fremmed side kunne låne.
- Et håndtryk fra appens origin uden billet afvises. Prøven fandt hvorfor:
  biblioteket sammenligner kun værtsnavne, og capacitor://localhost har samme
  værtsnavn som en instans på http://localhost.
- /ping svarer med product, edition og api, så appen kan kende en Verdande
  fra en routers login-side, før nogen er logget ind. Ingen version: /version
  er bag et login med vilje.

Fem plantede fejl, hver fanget af sin egen prøve: CORS til enhver origin,
genbrugelig billet, billet uden udløb, app-origin på en cookie, og
Allow-Credentials.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kristianwind
2026-10-23 14:03:01 +02:00
co-authored by Claude Opus 5.5
parent a8db761a4b
commit a71af41d9e
6 changed files with 430 additions and 4 deletions
+2
View File
@@ -18,6 +18,8 @@ type ctxKey int
const (
ctxUser ctxKey = iota
ctxSession
// ctxTicket marks a socket opened with a one-use ticket rather than a cookie.
ctxTicket
)
// sessionCookie is prefixed with __Host- when served over HTTPS. That prefix is