# Koble announcer til crew-panelet Announcer har ingen brukerdatabase og skal ikke ha det. Den stoler på crew-panelet og leser identitet og roller ut av det panelet sier. Dette dokumentet er kontrakten: hva panelet må levere, og i hvilken rekkefølge det er verdt å bygge det. Panelet har i dag verken JWT eller OIDC. **Bygg JWT først** – det er noen titalls linjer i panelet og dekker behovet. OIDC kan komme etterpå uten at noe i announcer må skrives om; begge veier ender i samme kode. --- ## Trinn 1: JWT (anbefalt start) Panelet signerer et kortlevd token som sier hvem brukeren er og hvilke roller han har. Announcer verifiserer signaturen og stoler på innholdet. ### Hva tokenet skal inneholde ```json { "iss": "https://crew.example.no", "aud": "announcer", "sub": "142", "name": "Kari Nordmann", "roles": ["crew"], "exp": 1760000000 } ``` | Claim | Krav | Merknad | |---|---|---| | `iss` | må stemme med `auth.jwt.issuer` | sjekkes bare hvis den er satt i config | | `aud` | må stemme med `auth.jwt.audience` | bruk `"announcer"` | | `sub` | påkrevd i praksis | stabil bruker-id; havner i loggen som `jwt:` | | `exp` | **påkrevd** | announcer avviser tokens uten utløpstid | | `name` | valgfri | vises i køen. Faller tilbake på `preferred_username`, `email`, så `sub` | | `roles` | valgfri | se under | `roles` kan være en liste (`["crew"]`), en streng (`"crew"`), mellomroms- eller kommadelt (`"crew admin"`), og den kan ligge nestet – da peker du på den med `role_claim = "realm_access.roles"`. Mangler den helt, får brukeren `auth.jwt.default_role`, eller ingen rettigheter hvis den ikke er satt. Rollenavnene betyr ingenting for announcer i seg selv; `[roles]` i `config.toml` bestemmer hva de får lov til. ### Signering To varianter. Begge virker, den andre er bedre: **HS256 med delt hemmelighet** – enklest. Samme streng i panelet og i announcer. Ulempen er at announcer dermed har en nøkkel som *kan lage* gyldige tokens. ```toml [auth.jwt] issuer = "https://crew.example.no" audience = "announcer" # settes som ANNOUNCER_JWT_SECRET i systemd-enheten, ikke her ``` **RS256 med JWKS** – panelet signerer med sin private nøkkel og publiserer den offentlige på `/.well-known/jwks.json`. Announcer henter den og cacher den en time. Da kan announcer bare verifisere, aldri signere, og nøkkelrullering skjer av seg selv. ```toml [auth.jwt] issuer = "https://crew.example.no" audience = "announcer" jwks_url = "https://crew.example.no/.well-known/jwks.json" ``` ### Hvordan brukeren kommer inn To måter, og panelet kan bruke begge: **Lenke (det du vil ha for et crew-medlem).** Panelet lager en knapp «Åpne announcer» som peker på ``` http://:8090/auth/token?t= ``` Announcer verifiserer tokenet, setter en sesjonscookie og sender brukeren til forsida. Ett klikk, ingen PIN å huske. Lag tokenet kortlevd (`exp` et par minutter fram) – det er bare en billett inn; sesjonen varer så lenge tokenets `exp` sier, eller `session_seconds` hvis det er lengre. **Header (for skript og integrasjoner).** Send tokenet på hver forespørsel: ```bash curl -X POST http://announcer:8090/api/announce \ -H "Authorization: Bearer $JWT" \ -H 'Content-Type: application/json' \ -d '{"text":"Testmelding","voice":"finn"}' ``` ### Minimum i panelet (Python-eksempel) ```python import time, jwt # pyjwt def announcer_url(user) -> str: token = jwt.encode( { "iss": "https://crew.example.no", "aud": "announcer", "sub": str(user.id), "name": user.full_name, "roles": ["admin" if user.is_crew_lead else "crew"], "exp": time.time() + 120, }, ANNOUNCER_JWT_SECRET, algorithm="HS256", ) return f"http://announcer.lan:8090/auth/token?t={token}" ``` --- ## Trinn 2: OIDC Når panelet har fått `authorize`-, `token`- og gjerne `userinfo`-endepunkter, kan announcer bli en vanlig OIDC-klient. Da logger brukeren inn i panelet i nettleseren og trenger ingen lenke med token i. Announcer bruker autorisasjonskode med PKCE (S256). Dette må registreres i panelet: | | | |---|---| | `client_id` | f.eks. `announcer` | | `redirect_uri` | `http://:8090/auth/callback` – **eksakt**, inkludert port | | `grant_type` | `authorization_code` | | `scope` | `openid profile email` (eller bare `openid`) | | PKCE | `S256` | Announcer leser `{issuer}/.well-known/openid-configuration` ved første innlogging. Har ikke panelet discovery, settes endepunktene direkte: ```toml [auth.oidc] issuer = "https://crew.example.no" client_id = "announcer" authorization_endpoint = "https://crew.example.no/oauth/authorize" token_endpoint = "https://crew.example.no/oauth/token" jwks_url = "https://crew.example.no/.well-known/jwks.json" ``` `client_secret` settes som `ANNOUNCER_OIDC_CLIENT_SECRET`. Rollene leses ut av `id_token` på samme måte som over. Står announcer bak en omvendt proxy, sett `server.public_url` så `redirect_uri` blir riktig, og `server.behind_tls = true`. --- ## Roller og rettigheter Fem handlinger. `config.toml` kobler rollenavn til dem: | Handling | Hva den gir | |---|---| | `view` | se køen og hva som spilles nå | | `preview` | forhåndslytte (koster en syntese) | | `announce` | sende ut på anlegget | | `stop` | stoppe det som spilles | | `clear` | tømme køen (og se IP-adresser i køen) | ```toml [roles] anonymous = [] # ["view"] for en statusskjerm crew = ["view", "preview", "announce"] admin = ["*"] ``` Endrer du `[roles]` slår det inn ved neste forespørsel – ingen må logge inn på nytt. (Nye `[auth.*]`-verdier krever omstart.) ## Hva announcer svarer | Kode | Betyr | Hva klienten skal gjøre | |---|---|---| | 401 | vi vet ikke hvem du er | logg inn | | 403 | vi vet hvem du er, rollen din får ikke dette | ikke prøv igjen | `GET /api/config` svarer alltid 200 og forteller i `auth`-blokka hvilke metoder som er aktive, hvem du er og hva du får gjøre. Det er den sida bruker for å vise eller skjule knapper. ## Loggen Hvert utrop havner i `logs/announcements.jsonl`, én JSON-linje per hendelse: ```json {"ts":1760000000.12,"action":"announce","subject":"jwt:142","name":"Kari Nordmann", "method":"jwt","ip":"10.20.70.55","id":7,"text":"Pizzaen er kommet","voice":"finn"} ``` `action` er `announce`, `played`, `stop` eller `clear`. Fila rullerer til `.jsonl.1` ved 5 MB.