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:
co-authored by
Claude Opus 5
parent
f7b78fd5db
commit
6fd046417a
@@ -29,11 +29,20 @@
|
||||
use at_crypto::ecdsa::P256Keypair;
|
||||
use at_crypto::jwt::{issue_jwt, JwtClaims};
|
||||
|
||||
/// Audience the PDS stamps into access tokens. Not validated by
|
||||
/// `verify_jwt` today (`validate_aud = false`), but minting a token
|
||||
/// that differs from the real thing would make this helper a poor
|
||||
/// stand-in for the client.
|
||||
const APPVIEW_AUD: &str = "did:web:appview.maarcadetweet.local";
|
||||
/// Audience the PDS stamps into access tokens — and, since the
|
||||
/// audience check landed, the value the AppView insists on: its own
|
||||
/// service DID, derived from `APPVIEW_PUBLIC_URL`. A token minted with
|
||||
/// anything else is rejected as `TokenInvalid`, which is exactly what
|
||||
/// we want a wrong value here to look like.
|
||||
///
|
||||
/// Derived the same way `AppConfig::appview_did()` does it, from the
|
||||
/// same environment variable, so this helper can't drift from the
|
||||
/// service it's impersonating the PDS for.
|
||||
fn appview_aud() -> String {
|
||||
let url = std::env::var("APPVIEW_PUBLIC_URL")
|
||||
.unwrap_or_else(|_| "http://127.0.0.1:2584".to_string());
|
||||
at_shared::config::did_web_from_url(&url)
|
||||
}
|
||||
|
||||
/// The scope the AppView insists on. A token with any other scope —
|
||||
/// `com.atproto.refresh`, say — is rejected with `TokenInvalid`.
|
||||
@@ -156,7 +165,7 @@ pub fn mint_access_jwt(
|
||||
&JwtClaims {
|
||||
iss: "did:web:test".into(),
|
||||
sub: did.to_string(),
|
||||
aud: APPVIEW_AUD.into(),
|
||||
aud: appview_aud(),
|
||||
iat: now - 1,
|
||||
exp: now + ttl_secs,
|
||||
jti: None,
|
||||
|
||||
Reference in New Issue
Block a user