From dfcec6b3b03559a3aa72035ee2fab0e698b1e19a Mon Sep 17 00:00:00 2001 From: vegard Date: Fri, 20 Mar 2026 02:30:41 +0000 Subject: [PATCH] Kontekst-velger: Hjem og Administrasjon som separate lenker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dropdown viser begge arbeidsflater med absolutte URLer (ws.synops.no og adm.synops.no). Navigasjon mellom subdomener fungerer uten å miste sesjon. Erfaringsnotat: multi-subdomain med SvelteKit — ORIGIN-fellen, cookie-domene, CSRF, OIDC redirect URIs, sjekkliste for nye subdomener. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/erfaringer/multi_subdomain.md | 125 ++++++++++++++++++ .../src/lib/components/ContextHeader.svelte | 26 +++- 2 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 docs/erfaringer/multi_subdomain.md diff --git a/docs/erfaringer/multi_subdomain.md b/docs/erfaringer/multi_subdomain.md new file mode 100644 index 0000000..b714eda --- /dev/null +++ b/docs/erfaringer/multi_subdomain.md @@ -0,0 +1,125 @@ +# Erfaring: Multi-subdomain med SvelteKit (mars 2026) + +## Kontekst + +Innføring av `adm.synops.no` som admin-domene ved siden av +`ws.synops.no` (app) avslørte flere arkitekturelle blindsoner. + +## Problemer vi møtte + +### 1. ORIGIN låser hostname + +**Symptom:** `event.url.hostname` returnerte alltid `ws.synops.no` +uansett hvilken Host-header som kom inn. + +**Årsak:** `ORIGIN=https://ws.synops.no` i `.env`. SvelteKit +adapter-node bruker ORIGIN for å konstruere `event.url` — den +*overskriver* Host-headeren. + +**Løsning:** Fjern ORIGIN. `AUTH_TRUST_HOST=true` lar SvelteKit +lese hostname fra selve HTTP Host-headeren. + +**Læring:** ORIGIN er ment for single-origin deployments. +Multi-subdomain krever at SvelteKit leser Host dynamisk. + +### 2. Session-cookie bundet til ett subdomain + +**Symptom:** Login på `ws.synops.no` ga ikke tilgang til +`adm.synops.no`. Brukeren ble bedt om å logge inn igjen. + +**Årsak:** Session-cookie var satt med `domain=ws.synops.no` +(default). Cookien var ikke tilgjengelig for `adm.synops.no`. + +**Løsning:** Sett cookie-domene til `.synops.no` i auth.ts. +Alle subdomener deler sesjonen. + +**Læring:** Wildcard cookie-domene (`.synops.no`) er nødvendig +når flere subdomener trenger samme autentisering. Det er trygt +så lenge alle subdomener er under vår kontroll. + +### 3. CSRF cross-origin blokkering + +**Symptom:** `Cross-site POST form submissions are forbidden` +ved login-callback fra Authentik. + +**Årsak:** SvelteKit sin innebygde CSRF-sjekk sammenligner +request origin mot ORIGIN-variabelen. POST fra `adm.synops.no` +til OIDC-callback ble blokkert. + +**Løsning:** `csrf: { checkOrigin: false }` i svelte.config.js. +Trygt fordi OIDC bruker PKCE + state som CSRF-beskyttelse. + +**Læring:** SvelteKit sin CSRF-sjekk er for streng for +multi-origin. Deaktiver den når du har egen CSRF-mekanisme. + +### 4. Authentik redirect URI + +**Symptom:** OIDC-callback feilet fordi `adm.synops.no` ikke +var registrert som gyldig redirect URI. + +**Årsak:** Bare `ws.synops.no` var registrert i Authentik. + +**Løsning:** Legg til `adm.synops.no/auth/callback/authentik` +i Authentik sin provider-konfig. + +**Læring:** Hvert subdomain trenger egen redirect URI i OIDC. + +### 5. TLS-sertifikat + +**Ikke et problem:** Caddy henter automatisk sertifikat for +nye domener via Let's Encrypt ACME. `adm.synops.no` fikk +sertifikat i løpet av sekunder ved første request. + +**Læring:** Caddy sin auto-TLS er utmerket for nye subdomener. +Bare legg til i Caddyfile og restart. + +## Arkitekturprinsipper vi trekker ut + +### 1. Ikke hardkod hostnames i konfigfiler + +ORIGIN, cookie-domene, redirect URIs — alt som binder til +et spesifikt hostname gjør multi-subdomain vanskelig. +Foretrekk dynamisk host-deteksjon (`AUTH_TRUST_HOST=true`). + +### 2. Cookies på toppdomenet for relaterte subdomener + +Når flere subdomener trenger samme sesjon, sett cookie på +`.synops.no`. CSRF-token kan forbli host-bound (`__Host-` +prefix) for ekstra sikkerhet. + +### 3. Samme SvelteKit-instans for alle subdomener + +Ikke kjør separate SvelteKit-instanser per subdomain. Én +instans som leser hostname og tilpasser seg. Enklere deploy, +delt kodebase, felles sesjon. + +### 4. Caddy gjør routing, SvelteKit gjør logikk + +Caddy ruter domene → SvelteKit. SvelteKit sjekker hostname +og tilpasser innhold. Caddy trenger ingen hostname-logikk +utover reverse proxy. + +## Hva ble gjort riktig + +- **Caddy-konfig:** Enkelt å legge til nytt subdomain +- **Delt SvelteKit:** Ingen duplisert kode +- **Cookie-wildcard:** Riktig for relaterte subdomener +- **AUTH_TRUST_HOST:** Fungerer godt uten ORIGIN + +## Hva vi tenderer til å glemme + +- **ORIGIN-variabelen** — den overstyrer alt. Sjekk den først. +- **Cookie-domene** — default er gjeldende hostname, ikke parent. +- **OIDC redirect URIs** — må oppdateres for hvert nytt domene. +- **Be bruker slette cookies** — gammel cookie fra ett domene + kræsjer med ny cookie fra wildcard-domenet. + +## Sjekkliste for nytt subdomain + +1. DNS A-record (Hetzner DNS Console) +2. Caddy-blokk i Caddyfile (auto-TLS) +3. Authentik redirect URI +4. Verifiser at ORIGIN *ikke* er satt (eller er kompatibel) +5. Verifiser cookie-domene (`.synops.no`) +6. Test: login → callback → riktig side +7. Be bruker slette cookies hvis problemer diff --git a/frontend/src/lib/components/ContextHeader.svelte b/frontend/src/lib/components/ContextHeader.svelte index 1e886da..0bc01e1 100644 --- a/frontend/src/lib/components/ContextHeader.svelte +++ b/frontend/src/lib/components/ContextHeader.svelte @@ -393,18 +393,30 @@ />
- + {#if !searchQuery.trim()} - + + { selectorOpen = false; }} + > + Administrasjon + {#if isPersonalWorkspace && homeLabel === 'Administrasjon'} + + {/if} + {/if}