102 lines
3.2 KiB
Rust
102 lines
3.2 KiB
Rust
use anyhow::{bail, Context, Result};
|
|
use atrium_api::did_doc::DidDocument;
|
|
use bytes::Buf;
|
|
use http_body_util::BodyExt;
|
|
use hyper::{client::conn::http1, Request, StatusCode, Uri};
|
|
use hyper_util::rt::TokioIo;
|
|
use rustls::pki_types::ServerName;
|
|
use serde::{Deserialize, Serialize};
|
|
use tokio::net::TcpStream;
|
|
|
|
use crate::{
|
|
http::{body_empty, HttpBody},
|
|
tls::open_tls_stream,
|
|
RelayServer,
|
|
};
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct User {
|
|
pub did: String,
|
|
pub pds: Option<String>,
|
|
#[serde(default)]
|
|
pub handle: Option<String>,
|
|
}
|
|
|
|
pub async fn fetch_user(server: &RelayServer, did: &str) -> Result<User> {
|
|
tracing::debug!(%did, "fetching user");
|
|
if did.starts_with("did:plc:") {
|
|
// TODO: configurable plc resolver location
|
|
let domain = "plc.directory";
|
|
|
|
let tcp_stream = TcpStream::connect((domain, 443)).await?;
|
|
let domain_tls: ServerName<'_> = ServerName::try_from(domain.to_string())?;
|
|
let tls_stream = open_tls_stream(tcp_stream, domain_tls).await?;
|
|
let io = TokioIo::new(tls_stream);
|
|
|
|
let req = Request::builder()
|
|
.method("GET")
|
|
.uri(format!("https://{domain}/{did}"))
|
|
.header("Host", domain.to_string())
|
|
.body(body_empty())?;
|
|
|
|
let (mut sender, conn) = http1::handshake::<_, HttpBody>(io).await?;
|
|
tokio::task::spawn(async move {
|
|
if let Err(err) = conn.await {
|
|
println!("Connection failed: {:?}", err);
|
|
}
|
|
});
|
|
|
|
let res = sender
|
|
.send_request(req)
|
|
.await
|
|
.context("Failed to send plc request")?;
|
|
if res.status() != StatusCode::OK {
|
|
bail!("plc directory returned non-200 status");
|
|
}
|
|
|
|
let body = res.collect().await?.aggregate();
|
|
let did_doc = serde_json::from_reader::<_, DidDocument>(body.reader())
|
|
.context("Failed to parse plc DID doc as JSON")?;
|
|
|
|
let pds_endpoint = did_doc.get_pds_endpoint();
|
|
let pds_uri: Option<Uri> = pds_endpoint.as_deref().unwrap_or_default().parse().ok();
|
|
let pds = pds_uri
|
|
.as_ref()
|
|
.and_then(|u| u.authority())
|
|
.map(|a| a.host())
|
|
.map(|s| s.to_string());
|
|
|
|
let handle = did_doc
|
|
.also_known_as
|
|
.and_then(|v| v.into_iter().next())
|
|
.and_then(|s| s.strip_prefix("at://").map(str::to_string));
|
|
let did = did_doc.id;
|
|
|
|
// TODO: check if handle resolves to did and fill none otherwise
|
|
|
|
let user = User { pds, did, handle };
|
|
|
|
store_user(server, &user)?;
|
|
|
|
Ok(user)
|
|
} else if did.starts_with("did:web:") {
|
|
todo!("resolve did web")
|
|
} else {
|
|
bail!("unknown did type {did}");
|
|
}
|
|
}
|
|
|
|
pub async fn lookup_user(server: &RelayServer, did: &str) -> Result<User> {
|
|
if let Some(cached_user) = server.db_users.get(did)? {
|
|
let cached_user = serde_ipld_dagcbor::from_slice::<User>(&cached_user)?;
|
|
return Ok(cached_user);
|
|
}
|
|
|
|
return fetch_user(server, did).await;
|
|
}
|
|
|
|
pub fn store_user(server: &RelayServer, user: &User) -> Result<()> {
|
|
let data = serde_ipld_dagcbor::to_vec(&user)?;
|
|
server.db_users.insert(&user.did, data)?;
|
|
Ok(())
|
|
}
|