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

proxy: use service cache

+42 -48
+6
slingshot/src/error.rs
··· 100 100 UrlParseError(#[from] url::ParseError), 101 101 #[error(transparent)] 102 102 ReqwestError(#[from] reqwest::Error), 103 + #[error(transparent)] 104 + IdentityError(#[from] IdentityError), 105 + #[error("upstream service could not be resolved")] 106 + ServiceNotFound, 107 + #[error("upstream service was found but no services matched")] 108 + ServiceNotMatched, 103 109 }
+1 -1
slingshot/src/main.rs
··· 152 152 log::info!("identity service ready."); 153 153 154 154 let repo = Repo::new(identity.clone()); 155 - let proxy = Proxy::new(repo.clone()); 155 + let proxy = Proxy::new(repo.clone(), identity.clone()); 156 156 157 157 let identity_for_server = identity.clone(); 158 158 let server_shutdown = shutdown.clone();
+17 -45
slingshot/src/proxy.rs
··· 1 - use serde::Deserialize; 1 + use atrium_api::types::string::{Did, Nsid}; 2 2 use url::Url; 3 3 use std::{collections::HashMap, time::Duration}; 4 - use crate::{Repo, server::HydrationSource, error::ProxyError}; 4 + use crate::{Repo, Identity, server::HydrationSource, error::ProxyError}; 5 5 use reqwest::Client; 6 6 use serde_json::{Map, Value}; 7 7 ··· 71 71 #[derive(Clone)] 72 72 pub struct Proxy { 73 73 repo: Repo, 74 + identity: Identity, 74 75 client: Client, 75 76 } 76 77 77 78 impl Proxy { 78 - pub fn new(repo: Repo) -> Self { 79 + pub fn new(repo: Repo, identity: Identity) -> Self { 79 80 let client = Client::builder() 80 81 .user_agent(format!( 81 82 "microcosm slingshot v{} (contact: @bad-example.com)", ··· 85 86 .timeout(Duration::from_secs(6)) 86 87 .build() 87 88 .unwrap(); 88 - Self { repo, client } 89 + Self { repo, client, identity } 89 90 } 90 91 91 92 pub async fn proxy( 92 93 &self, 93 - xrpc: String, 94 - service: String, 94 + service_did: &Did, 95 + service_id: &str, 96 + xrpc: &Nsid, 95 97 params: Option<Map<String, Value>>, 96 98 ) -> Result<Value, ProxyError> { 97 99 98 - // hackin it to start 99 - 100 - // 1. assume did-web (TODO) and get the did doc 101 - #[derive(Debug, Deserialize)] 102 - struct ServiceDoc { 103 - id: String, 104 - service: Vec<ServiceItem>, 105 - } 106 - #[derive(Debug, Deserialize)] 107 - struct ServiceItem { 108 - id: String, 109 - #[expect(unused)] 110 - r#type: String, 111 - #[serde(rename = "serviceEndpoint")] 112 - service_endpoint: Url, 113 - } 114 - let dw = service.strip_prefix("did:web:").expect("a did web"); 115 - let (dw, service_id) = dw.split_once("#").expect("whatever"); 116 - let mut dw_url = Url::parse(&format!("https://{dw}"))?; 117 - dw_url.set_path("/.well-known/did.json"); 118 - let doc: ServiceDoc = self.client 119 - .get(dw_url) 120 - .send() 100 + let mut upstream: Url = self 101 + .identity 102 + .did_to_mini_service_doc(service_did) 121 103 .await? 122 - .error_for_status()? 123 - .json() 124 - .await?; 104 + .ok_or(ProxyError::ServiceNotFound)? 105 + .get(service_id, None) 106 + .ok_or(ProxyError::ServiceNotMatched)? 107 + .endpoint 108 + .parse()?; 125 109 126 - assert_eq!(doc.id, format!("did:web:{}", dw)); 127 - 128 - let mut upstream = None; 129 - for ServiceItem { id, service_endpoint, .. } in doc.service { 130 - let Some((_, id)) = id.split_once("#") else { continue; }; 131 - if id != service_id { continue; }; 132 - upstream = Some(service_endpoint); 133 - break; 134 - } 135 - 136 - // 2. proxy the request forward 137 - let mut upstream = upstream.expect("to find it"); 138 - upstream.set_path(&format!("/xrpc/{xrpc}")); // TODO: validate nsid 110 + upstream.set_path(&format!("/xrpc/{}", xrpc.as_str())); 139 111 140 112 if let Some(params) = params { 141 113 let mut query = upstream.query_pairs_mut();
+18 -2
slingshot/src/server.rs
··· 716 716 /// com.bad-example.identity.resolveService 717 717 /// 718 718 /// resolve an atproto service did + id to its http endpoint 719 + /// 720 + /// > [!important] 721 + /// > this endpoint is experimental and may change 719 722 #[oai( 720 723 path = "/com.bad-example.identity.resolveService", 721 724 method = "get", ··· 794 797 Some(map) 795 798 } else { None }; 796 799 800 + let Some((service_did, id_fragment)) = payload.atproto_proxy.rsplit_once("#") else { 801 + return ProxyHydrateResponse::BadRequest(xrpc_error("BadParameter", "atproto_proxy could not be understood")); 802 + }; 803 + 804 + let Ok(service_did) = service_did.parse() else { 805 + return ProxyHydrateResponse::BadRequest(xrpc_error("BadParameter", "atproto_proxy service did could not be parsed")); 806 + }; 807 + 808 + let Ok(xrpc) = payload.xrpc.parse() else { 809 + return ProxyHydrateResponse::BadRequest(xrpc_error("BadParameter", "invalid NSID for xrpc param")); 810 + }; 811 + 797 812 match self.proxy.proxy( 798 - payload.xrpc, 799 - payload.atproto_proxy, 813 + &service_did, 814 + &format!("#{id_fragment}"), 815 + &xrpc, 800 816 params, 801 817 ).await { 802 818 Ok(skeleton) => {