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