Nystart basert på arkitektonisk innsikt fra Sidelinja v1. Koden er ny, visjon og primitiver er validert gjennom tidligere arbeid. Inneholder: - Komplett arkitekturdokumentasjon (docs/arkitektur.md) - 6 vedtatte retninger (docs/retninger/) - Alle concepts, features, proposals og erfaringer fra v1 - Server-oppsett og drift (docs/setup/) - LiteLLM-konfigurasjon (API-nøkler via env) - Editor.svelte referanse fra v1 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
125 lines
6.4 KiB
Markdown
125 lines
6.4 KiB
Markdown
# 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(),
|
|
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
channel_id UUID NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
|
|
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 workspace-tilhørighet.
|
|
- Gjestens meldinger merkes med `author_id = NULL` og `metadata.guest_name` + `metadata.guest_token_id` for sporbarhet.
|
|
- Ingen tilgang til andre channels, workspaces 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/sidelinja/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 media/{workspace_slug}/voice/
|
|
→ 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, ingen workspace-switcher).
|
|
* 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.
|
|
* Alt er workspace-scopet. Token bærer workspace_id eksplisitt.
|