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