diff --git a/pubky/src/lib.rs b/pubky/src/lib.rs index 1c35ff8..48bcb81 100644 --- a/pubky/src/lib.rs +++ b/pubky/src/lib.rs @@ -20,5 +20,5 @@ pub use error::Error; pub struct PubkyClient { http: reqwest::Client, #[cfg(not(target_arch = "wasm32"))] - pkarr: PkarrClientAsync, + pub(crate) pkarr: PkarrClientAsync, } diff --git a/pubky/src/native/auth.rs b/pubky/src/native/auth.rs index 76ef81e..23622ad 100644 --- a/pubky/src/native/auth.rs +++ b/pubky/src/native/auth.rs @@ -33,7 +33,7 @@ impl PubkyClient { /// Check the current sesison for a given Pubky in its homeserver. /// - /// Returns an [Error::NotSignedIn] if so, or [ureq::Error] if + /// Returns an [Error::NotSignedIn] if so, or [reqwest::Error] if /// the response has any other `>=400` status code. pub async fn session(&self, pubky: &PublicKey) -> Result { let (homeserver, mut url) = self.resolve_pubky_homeserver(pubky).await?; diff --git a/pubky/src/native/pkarr.rs b/pubky/src/native/pkarr.rs index 5cced4b..3f11711 100644 --- a/pubky/src/native/pkarr.rs +++ b/pubky/src/native/pkarr.rs @@ -5,75 +5,21 @@ use pkarr::{ Keypair, PublicKey, SignedPacket, }; -use crate::shared::pkarr::{format_url, parse_pubky_svcb, prepare_packet_for_signup}; - use crate::{ error::{Error, Result}, PubkyClient, }; impl PubkyClient { - /// Publish the SVCB record for `_pubky.`. - pub(crate) async fn publish_pubky_homeserver( + pub(crate) async fn pkarr_resolve( &self, - keypair: &Keypair, - host: &str, - ) -> Result<()> { - let existing = self.pkarr.resolve(&keypair.public_key()).await?; - - let signed_packet = prepare_packet_for_signup(keypair, host, existing)?; - - self.pkarr.publish(&signed_packet).await?; - - Ok(()) + public_key: &PublicKey, + ) -> Result> { + Ok(self.pkarr.resolve(public_key).await?) } - /// Resolve the homeserver for a pubky. - pub(crate) async fn resolve_pubky_homeserver( - &self, - pubky: &PublicKey, - ) -> Result<(PublicKey, Url)> { - let target = format!("_pubky.{}", pubky); - - self.resolve_endpoint(&target) - .await - .map_err(|_| Error::Generic("Could not resolve homeserver".to_string())) - } - - /// Resolve a service's public_key and clearnet url from a Pubky domain - pub(crate) async fn resolve_endpoint(&self, target: &str) -> Result<(PublicKey, Url)> { - let original_target = target; - // TODO: cache the result of this function? - - let mut target = target.to_string(); - let mut homeserver_public_key = None; - let mut host = target.clone(); - - let mut step = 0; - - // PublicKey is very good at extracting the Pkarr TLD from a string. - while let Ok(public_key) = PublicKey::try_from(target.clone()) { - let response = self - .pkarr - .resolve(&public_key) - .await - .map_err(|e| Error::ResolveEndpoint(original_target.into()))?; - - let done = parse_pubky_svcb( - response, - &public_key, - &mut target, - &mut homeserver_public_key, - &mut host, - &mut step, - ); - - if done { - break; - } - } - - format_url(original_target, homeserver_public_key, host) + pub(crate) async fn pkarr_publish(&self, signed_packet: &SignedPacket) -> Result<()> { + Ok(self.pkarr.publish(signed_packet).await?) } } @@ -88,15 +34,6 @@ mod tests { }; use pubky_homeserver::Homeserver; - #[tokio::test] - async fn resolve_endpoint() { - let target = "oc9tdmh8c4pmy3tk946oqqfkhic18xdiytadspiy55qhbnja5w9o"; - - let client = PubkyClient::new(); - - client.resolve_endpoint(target).await.unwrap(); - } - #[tokio::test] async fn resolve_homeserver() { let testnet = Testnet::new(3); diff --git a/pubky/src/shared/pkarr.rs b/pubky/src/shared/pkarr.rs index a5d35fc..d06b25c 100644 --- a/pubky/src/shared/pkarr.rs +++ b/pubky/src/shared/pkarr.rs @@ -5,101 +5,130 @@ use pkarr::{ Keypair, PublicKey, SignedPacket, }; -use crate::error::{Error, Result}; +use crate::{ + error::{Error, Result}, + PubkyClient, +}; const MAX_RECURSIVE_PUBKY_HOMESERVER_RESOLUTION: u8 = 3; -pub fn prepare_packet_for_signup( - keypair: &Keypair, - host: &str, - existing: Option, -) -> Result { - let mut packet = Packet::new_reply(0); +impl PubkyClient { + /// Publish the SVCB record for `_pubky.`. + pub(crate) async fn publish_pubky_homeserver( + &self, + keypair: &Keypair, + host: &str, + ) -> Result<()> { + let existing = self.pkarr_resolve(&keypair.public_key()).await?; - if let Some(existing) = existing { - for answer in existing.packet().answers.iter().cloned() { - if !answer.name.to_string().starts_with("_pubky") { - packet.answers.push(answer.into_owned()) + let mut packet = Packet::new_reply(0); + + if let Some(existing) = existing { + for answer in existing.packet().answers.iter().cloned() { + if !answer.name.to_string().starts_with("_pubky") { + packet.answers.push(answer.into_owned()) + } } } + + let svcb = SVCB::new(0, host.try_into()?); + + packet.answers.push(pkarr::dns::ResourceRecord::new( + "_pubky".try_into().unwrap(), + pkarr::dns::CLASS::IN, + 60 * 60, + pkarr::dns::rdata::RData::SVCB(svcb), + )); + + let signed_packet = SignedPacket::from_packet(keypair, &packet)?; + + self.pkarr_publish(&signed_packet).await?; + + Ok(()) } - let svcb = SVCB::new(0, host.try_into()?); + /// Resolve the homeserver for a pubky. + pub(crate) async fn resolve_pubky_homeserver( + &self, + pubky: &PublicKey, + ) -> Result<(PublicKey, Url)> { + let target = format!("_pubky.{}", pubky); - packet.answers.push(pkarr::dns::ResourceRecord::new( - "_pubky".try_into().unwrap(), - pkarr::dns::CLASS::IN, - 60 * 60, - pkarr::dns::rdata::RData::SVCB(svcb), - )); + self.resolve_endpoint(&target) + .await + .map_err(|_| Error::Generic("Could not resolve homeserver".to_string())) + } - Ok(SignedPacket::from_packet(keypair, &packet)?) -} + /// Resolve a service's public_key and clearnet url from a Pubky domain + pub(crate) async fn resolve_endpoint(&self, target: &str) -> Result<(PublicKey, Url)> { + let original_target = target; + // TODO: cache the result of this function? -pub fn parse_pubky_svcb( - signed_packet: Option, - public_key: &PublicKey, - target: &mut String, - homeserver_public_key: &mut Option, - host: &mut String, - step: &mut u8, -) -> bool { - *step += 1; + let mut target = target.to_string(); + let mut homeserver_public_key = None; + let mut host = target.clone(); - let mut prior = None; + let mut step = 0; - if let Some(signed_packet) = signed_packet { - for answer in signed_packet.resource_records(target) { - if let pkarr::dns::rdata::RData::SVCB(svcb) = &answer.rdata { - if svcb.priority == 0 { - prior = Some(svcb) - } else if let Some(sofar) = prior { - if svcb.priority >= sofar.priority { - prior = Some(svcb) + // PublicKey is very good at extracting the Pkarr TLD from a string. + while let Ok(public_key) = PublicKey::try_from(target.clone()) { + step += 1; + + let response = self + .pkarr_resolve(&public_key) + .await + .map_err(|e| Error::ResolveEndpoint(original_target.into()))?; + + let mut prior = None; + + if let Some(signed_packet) = response { + for answer in signed_packet.resource_records(&target) { + if let pkarr::dns::rdata::RData::SVCB(svcb) = &answer.rdata { + if svcb.priority == 0 { + prior = Some(svcb) + } else if let Some(sofar) = prior { + if svcb.priority >= sofar.priority { + prior = Some(svcb) + } + // TODO return random if priority is the same + } else { + prior = Some(svcb) + } } - // TODO return random if priority is the same - } else { - prior = Some(svcb) + } + + if let Some(svcb) = prior { + homeserver_public_key = Some(public_key.clone()); + target = svcb.target.to_string(); + + if let Some(port) = svcb.get_param(pkarr::dns::rdata::SVCB::PORT) { + if port.len() < 2 { + // TODO: debug! Error encoding port! + } + let port = u16::from_be_bytes([port[0], port[1]]); + + host = format!("{target}:{port}"); + } else { + host.clone_from(&target); + }; + + if step >= MAX_RECURSIVE_PUBKY_HOMESERVER_RESOLUTION { + continue; + }; } } } - if let Some(svcb) = prior { - *homeserver_public_key = Some(public_key.clone()); - *target = svcb.target.to_string(); - - if let Some(port) = svcb.get_param(pkarr::dns::rdata::SVCB::PORT) { - if port.len() < 2 { - // TODO: debug! Error encoding port! - } - let port = u16::from_be_bytes([port[0], port[1]]); - - *host = format!("{target}:{port}"); + if let Some(homeserver) = homeserver_public_key { + let url = if host.starts_with("localhost") { + format!("http://{host}") } else { - host.clone_from(target); + format!("https://{host}") }; - return *step >= MAX_RECURSIVE_PUBKY_HOMESERVER_RESOLUTION; + return Ok((homeserver, Url::parse(&url)?)); } + + Err(Error::ResolveEndpoint(original_target.into())) } - - true -} - -pub fn format_url( - original_target: &str, - homeserver_public_key: Option, - host: String, -) -> Result<(PublicKey, Url)> { - if let Some(homeserver) = homeserver_public_key { - let url = if host.starts_with("localhost") { - format!("http://{host}") - } else { - format!("https://{host}") - }; - - return Ok((homeserver, Url::parse(&url)?)); - } - - Err(Error::ResolveEndpoint(original_target.into())) } diff --git a/pubky/src/wasm/pkarr.rs b/pubky/src/wasm/pkarr.rs index 4af3e7d..91b85c2 100644 --- a/pubky/src/wasm/pkarr.rs +++ b/pubky/src/wasm/pkarr.rs @@ -8,86 +8,17 @@ pub use pkarr::{ }; use crate::error::{Error, Result}; -use crate::shared::pkarr::{format_url, parse_pubky_svcb, prepare_packet_for_signup}; use crate::PubkyClient; const TEST_RELAY: &str = "http://localhost:15411/pkarr"; -#[macro_export] -macro_rules! log { - ($($arg:expr),*) => { - web_sys::console::debug_1(&format!($($arg),*).into()); - }; -} - impl PubkyClient { - /// Publish the SVCB record for `_pubky.`. - pub(crate) async fn publish_pubky_homeserver( - &self, - keypair: &Keypair, - host: &str, - ) -> Result<()> { - // let existing = self.pkarr.resolve(&keypair.public_key()).await?; - let existing = self.pkarr_resolve(&keypair.public_key()).await?; - - let signed_packet = prepare_packet_for_signup(keypair, host, existing)?; - - // self.pkarr.publish(&signed_packet).await?; - self.pkarr_publish(&signed_packet).await?; - - Ok(()) - } - - /// Resolve the homeserver for a pubky. - pub(crate) async fn resolve_pubky_homeserver( - &self, - pubky: &PublicKey, - ) -> Result<(PublicKey, Url)> { - let target = format!("_pubky.{}", pubky); - - self.resolve_endpoint(&target) - .await - .map_err(|_| Error::Generic("Could not resolve homeserver".to_string())) - } - - /// Resolve a service's public_key and clearnet url from a Pubky domain - pub(crate) async fn resolve_endpoint(&self, target: &str) -> Result<(PublicKey, Url)> { - let original_target = target; - // TODO: cache the result of this function? - - let mut target = target.to_string(); - let mut homeserver_public_key = None; - let mut host = target.clone(); - - let mut step = 0; - - // PublicKey is very good at extracting the Pkarr TLD from a string. - while let Ok(public_key) = PublicKey::try_from(target.clone()) { - let response = self - .pkarr_resolve(&public_key) - .await - .map_err(|e| Error::ResolveEndpoint(original_target.into()))?; - - let done = parse_pubky_svcb( - response, - &public_key, - &mut target, - &mut homeserver_public_key, - &mut host, - &mut step, - ); - - if done { - break; - } - } - - format_url(original_target, homeserver_public_key, host) - } - //TODO: Allow multiple relays in parallel //TODO: migrate to pkarr::PkarrRelayClient - async fn pkarr_resolve(&self, public_key: &PublicKey) -> Result> { + pub(crate) async fn pkarr_resolve( + &self, + public_key: &PublicKey, + ) -> Result> { let res = self .http .get(format!("{TEST_RELAY}/{}", public_key)) @@ -106,7 +37,7 @@ impl PubkyClient { Ok(Some(existing)) } - async fn pkarr_publish(&self, signed_packet: &SignedPacket) -> Result<()> { + pub(crate) async fn pkarr_publish(&self, signed_packet: &SignedPacket) -> Result<()> { self.http .put(format!("{TEST_RELAY}/{}", signed_packet.public_key())) .body(signed_packet.to_relay_payload())