Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm

working with bsky_appview...

Changed files
+25 -16
pocket
+5 -5
pocket/src/server.rs
··· 108 108 tag = "ApiTags::Pocket" 109 109 )] 110 110 async fn app_bsky_get_prefs(&self, XrpcAuth(auth): XrpcAuth) -> GetBskyPrefsResponse { 111 - let did = match self 111 + let (did, aud) = match self 112 112 .verifier 113 113 .verify("app.bsky.actor.getPreferences", &auth.token) 114 114 .await ··· 116 116 Ok(d) => d, 117 117 Err(e) => return GetBskyPrefsResponse::BadRequest(xrpc_error("boooo", e.to_string())), 118 118 }; 119 - log::info!("verified did: {did}"); 119 + log::info!("verified did: {did}/{aud}"); 120 120 // TODO: fetch from storage 121 121 GetBskyPrefsResponse::Ok(Json(GetBskyPrefsResponseObject::example())) 122 122 } ··· 134 134 XrpcAuth(auth): XrpcAuth, 135 135 Json(prefs): Json<Value>, 136 136 ) -> PutBskyPrefsResponse { 137 - let did = match self 137 + let (did, aud) = match self 138 138 .verifier 139 139 .verify("app.bsky.actor.getPreferences", &auth.token) 140 140 .await ··· 142 142 Ok(d) => d, 143 143 Err(e) => return PutBskyPrefsResponse::BadRequest(xrpc_error("boooo", e.to_string())), 144 144 }; 145 - log::info!("verified did: {did}"); 145 + log::info!("verified did: {did}/{aud}"); 146 146 log::warn!("received prefs: {prefs:?}"); 147 147 // TODO: put prefs into storage 148 148 PutBskyPrefsResponse::Ok(PlainText("hiiiiii".to_string())) ··· 184 184 } 185 185 186 186 pub async fn serve(domain: &str) -> () { 187 - let verifier = TokenVerifier::new(domain); 187 + let verifier = TokenVerifier::default(); 188 188 let api_service = OpenApiService::new(Xrpc { verifier }, "Pocket", env!("CARGO_PKG_VERSION")) 189 189 .server(domain) 190 190 .url_prefix("/xrpc")
+20 -11
pocket/src/token.rs
··· 21 21 } 22 22 23 23 pub struct TokenVerifier { 24 - domain: String, 25 24 client: reqwest::Client, 26 25 } 27 26 28 27 impl TokenVerifier { 29 - pub fn new(domain: &str) -> Self { 28 + pub fn new() -> Self { 30 29 let client = reqwest::Client::builder() 31 30 .user_agent(format!( 32 31 "microcosm pocket v{} (dev: @bad-example.com)", ··· 36 35 .timeout(Duration::from_secs(12)) // slingshot timeout is 10s 37 36 .build() 38 37 .unwrap(); 39 - Self { 40 - client, 41 - domain: domain.to_string(), 42 - } 38 + Self { client } 43 39 } 44 40 45 - pub async fn verify(&self, expected_lxm: &str, token: &str) -> Result<String, VerifyError> { 41 + pub async fn verify( 42 + &self, 43 + expected_lxm: &str, 44 + token: &str, 45 + ) -> Result<(String, String), VerifyError> { 46 46 let untrusted = UntrustedToken::new(token).unwrap(); 47 47 48 48 // danger! unfortunately we need to decode the DID from the jwt body before we have a public key to verify the jwt with ··· 118 118 let Some(aud) = claims.custom.get("aud") else { 119 119 return Err(VerifyError::VerificationFailed("missing aud")); 120 120 }; 121 - if *aud != format!("did:web:{}#bsky_appview", self.domain) { 122 - return Err(VerifyError::VerificationFailed("wrong aud")); 123 - } 121 + let Some(aud) = aud.strip_prefix("did:web:") else { 122 + return Err(VerifyError::VerificationFailed("expected a did:web aud")); 123 + }; 124 + let Some((aud, _)) = aud.split_once("#") else { 125 + return Err(VerifyError::VerificationFailed("aud missing #fragment")); 126 + }; 124 127 let Some(lxm) = claims.custom.get("lxm") else { 125 128 return Err(VerifyError::VerificationFailed("missing lxm")); 126 129 }; ··· 128 131 return Err(VerifyError::VerificationFailed("wrong lxm")); 129 132 } 130 133 131 - Ok(did.to_string()) 134 + Ok((did.to_string(), aud.to_string())) 135 + } 136 + } 137 + 138 + impl Default for TokenVerifier { 139 + fn default() -> Self { 140 + Self::new() 132 141 } 133 142 }