# Konsept: Den Asynkrone Gjesten **Filsti:** `docs/concepts/den_asynkrone_gjesten.md` ## 1. Konsept Mange interessante gjester har ikke tid til å stille i studio. Den Asynkrone Gjesten lar redaksjonen sende en unik lenke til en gjest som kan svare på spørsmål via tale — fra mobilen, når det passer dem. Svarene lander direkte i redaksjonens arbeidsflyt, transkriberes automatisk, og kan brukes i podcasten. ## 2. Brukeropplevelse ### 2.1 Redaksjonens side 1. Redaksjonen oppretter en "Gjestesesjon" knyttet til et Tema. 2. Legger inn spørsmål (tekst) som gjesten skal svare på. 3. Systemet genererer en unik, tidsbegrenset URL. 4. URL-en sendes til gjesten via e-post, SMS eller chat. 5. Gjestens svar (lydmeldinger) dukker opp i Tema-chatten som `voice_memo`-meldinger, automatisk transkribert. 6. Redaksjonen triagerer svarene — kan tagge, klippe inn i episode, eller bruke som research. ### 2.2 Gjestens side 1. Gjesten åpner lenken i mobilnettleseren. Ingen app, ingen konto, ingen registrering. 2. Ser en enkel, ren flate: podcast-logo, spørsmålene fra redaksjonen, og en opptaksknapp per spørsmål. 3. Trykker record, snakker, trykker stopp. Kan lytte tilbake og ta om igjen. 4. Ved innsending lastes lydfilene opp og gjesten ser en bekreftelse. 5. Lenken utløper etter gitt tid eller antall besøk. ### 2.3 Minimal friksjon - Ingen Authentik-innlogging — tilgang via signert token - Ingen app — ren PWA/nettleser - Ingen redigering — gjesten snakker bare - Responsivt, mobil-first design ## 3. Komponenter | Feature | Rolle | |---|---| | Lydmeldinger | Opptakskomponent gjenbrukes (se `docs/features/lydmeldinger.md`) | | Chat (channels) | Svarene lander i en channel knyttet til Temaet | | Live transkripsjon | Whisper transkriberer via jobbkø (se `docs/features/live_transkripsjon.md`) | | Podcastfabrikken | Lydklipp kan trekkes inn som segment (se `docs/concepts/podcastfabrikken.md`) | ## 4. Autentisering: Gjeste-tokens ### 4.1 Datamodell ```sql guest_tokens ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), node_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE, -- Samlings- eller tema-node guest_name TEXT NOT NULL, -- Visningsnavn ("Erna Solberg") questions JSONB NOT NULL, -- [{ "sort": 1, "text": "Hva tenker du om...?" }] token TEXT UNIQUE NOT NULL, -- Kryptografisk sikker, URL-safe token expires_at TIMESTAMPTZ NOT NULL, max_recordings SMALLINT DEFAULT 10, -- Maks antall opptak recordings_count SMALLINT DEFAULT 0, created_by TEXT NOT NULL REFERENCES users(authentik_id), created_at TIMESTAMPTZ NOT NULL DEFAULT now() ) ``` ### 4.2 Sikkerhet - Token er kryptografisk tilfeldig (256-bit, URL-safe base64). - SvelteKit validerer token ved hvert request: sjekker expiry, recordings_count < max_recordings, og node-tilhørighet. - Gjestens meldinger merkes med `author_id = NULL` og `metadata.guest_name` + `metadata.guest_token_id` for sporbarhet. - Ingen tilgang til andre noder eller funksjoner. - Tokenet kan revokeres manuelt av redaksjonen. ### 4.2b Sikkerhetsdybde (mot token-lekkasje og misbruk) Et lekket gjeste-token gir direkte filopplasting uten autentisering — dette er høyrisiko. Følgende tiltak begrenser skadepotensialet: | Tiltak | Implementering | Formål | |---|---|---| | **Rate limiting per token** | SvelteKit middleware: maks 1 opplasting per 30 sek per token | Forhindrer spam/flooding | | **Filtype-validering** | SvelteKit: kun `audio/*` MIME-typer aksepteres, filstørrelse maks 50 MB | Blokkerer malware-opplasting | | **Malware-scanning** | ClamAV sidecar-container scanner opplastede filer før de lagres | Fanger kjent malware | | **Auto-revoke** | Token deaktiveres automatisk når `recordings_count >= max_recordings` | Begrenser eksponering | | **IP-logging** | Logger klient-IP per opplasting i `guest_token_usage`-tabell | Sporbarhet ved misbruk | | **Geo-begrensning** (valgfritt) | Caddy-nivå: blokker requests fra uventede geolokasjoner | Reduserer angrepsflate | **ClamAV Docker-oppsett:** ```yaml clamav: image: clamav/clamav:latest restart: unless-stopped volumes: - /srv/synops/media:/scan:ro networks: - sidelinja-net ``` SvelteKit kaller ClamAV via `clamdscan` (socket) etter filopplasting, før filen flyttes til endelig plassering. Infiserte filer slettes umiddelbart og tokenet flagges for manuell gjennomgang. **Fremtidig hardening — prosess-isolasjon:** Ved økt eksponering (mange aktive guest-tokens, offentlige lenker) bør opplastede filer prosesseres i en isolert kontekst per token. Mulige tilnærminger: - Firejail/bubblewrap-sandbox for Whisper-prosessering av gjeste-audio - Dedikert temp-mappe per token som slettes etter prosessering - Docker sidecar-container for uautentisert filopplasting med egne cgroups Dette er komplementært til ClamAV (som fanger kjent malware) — sandboxing beskytter mot ukjente angrep. Implementeres når gjeste-tokens eksponeres bredere enn redaksjonell bruk. ### 4.3 Flyt (teknisk) ``` Gjest åpner URL med token → SvelteKit validerer token → Viser spørsmål + opptaksknapp → Gjest tar opp svar → SvelteKit streamer lydfil til CAS (content-addressable store) → Oppretter message (voice_memo) i channelen → Oppretter whisper_transcribe-jobb i jobbkøen → Inkrementerer recordings_count → Redaksjonen ser svaret i Tema-chatten ``` ## 5. Dataklassifisering | Data | Kategori | Detaljer | |---|---|---| | Gjestens lydopptak | Kritisk (backup) | Unikt innhold | | Guest tokens | Flyktig (TTL) | Utløper automatisk, slett expired tokens periodisk | | Spørsmål (JSONB) | Kritisk (PG) | Redaksjonelt innhold | ## 6. Instruks for Claude Code * `guest_tokens`-tabellen er **ikke** en node i grafen — den er ren tilgangsstyring. * Gjeste-UI er en egen SvelteKit-rute (`/guest/[token]`) med minimal layout (ingen navbar). * Gjenbruk lydmeldinger-komponenten — ikke bygg en egen opptaksflyt. * Meldinger fra gjester har `author_id = NULL`. Frontend må håndtere dette gracefully (vis `guest_name` i stedet). * Tokenet skal **aldri** gi tilgang til å lese andre meldinger i channelen — gjesten kan kun skrive. * Tilgang er styrt via node_access. Token bærer node_id eksplisitt.