feat(auth): Audience der Access-Tokens prüfen

`verify_jwt` setzt `validate_aud = false` — es kann den Aufrufer nicht
kennen. Also blieb `aud` bisher ungeprüft, obwohl die PDS es setzt.

Was die Prüfung bringt: die PDS signiert Tokens für *ihre* AppView.
Ohne Audience-Check wäre ein Token, das an einen anderen Dienst mit
derselben PDS-Vertrauensbeziehung geht, hier wiederverwendbar — und
umgekehrt. Es ist der Unterschied zwischen "die PDS bürgt für diesen
Nutzer" und "die PDS bürgt für diesen Nutzer *im Gespräch mit uns*".

Dafür musste der Wert erst einmal etwas sein, das beide Seiten
berechnen können: die PDS setzte ihn hart auf
did:web:appview.maarcadetweet.local. Jetzt leiten ihn beide über
AppConfig::appview_did() aus APPVIEW_PUBLIC_URL ab — dieselbe
did:web-Regel wie schon für pds_did().

Ein Mismatch ist TokenInvalid, nicht Forbidden: das ist der Code, auf
den der Client seine Token-Erneuerung stützt. Eine Instanz, die ihre
APPVIEW_PUBLIC_URL ändert, heilt sich damit beim nächsten Refresh
selbst, statt jeden angemeldeten Nutzer auszusperren.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HC9HLrUU1LNwkzp8nkDLX
This commit is contained in:
tomdebone
2026-09-10 06:25:51 +02:00
co-authored by Claude Opus 5
parent f7b78fd5db
commit 6fd046417a
4 changed files with 128 additions and 27 deletions
+13
View File
@@ -138,6 +138,19 @@ impl AppConfig {
did_web_from_url(&self.pds_public_url)
}
/// The AppView's own service DID, derived from `APPVIEW_PUBLIC_URL`.
///
/// Also one derivation, two consumers: the PDS stamps it into the
/// `aud` of every access token it issues, and the AppView checks
/// incoming tokens against it. A token minted for a *different*
/// AppView must not be usable here — that's the whole point of an
/// audience — so both sides have to agree on the spelling, and the
/// only way to guarantee that is to compute it the same way from
/// the same configuration.
pub fn appview_did(&self) -> String {
did_web_from_url(&self.appview_public_url)
}
/// Base URL the AppView uses to reach the PDS.
///
/// `PDS_INTERNAL_URL` when set (the cluster-internal hostname),