mirror of
https://github.com/aljazceru/pubky-core.git
synced 2026-01-07 08:14:20 +01:00
feat(pubky): publish and resolve homeserver endpoint
This commit is contained in:
217
Cargo.lock
generated
217
Cargo.lock
generated
@@ -137,6 +137,12 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1ce0365f4d5fb6646220bb52fe547afd51796d90f914d4063cb0b032ebee088"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.6.0"
|
||||
@@ -231,6 +237,15 @@ version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
@@ -288,6 +303,27 @@ dependencies = [
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.10"
|
||||
@@ -334,12 +370,24 @@ version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"nanorand",
|
||||
"spin",
|
||||
]
|
||||
@@ -563,6 +611,16 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
@@ -590,6 +648,16 @@ version = "0.2.155"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "litrs"
|
||||
version = "0.4.1"
|
||||
@@ -798,9 +866,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkarr"
|
||||
version = "2.0.3"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89f9e12544b00f5561253bbd3cb72a85ff3bc398483dc1bf82bdf095c774136b"
|
||||
checksum = "4548c673cbf8c91b69f7a17d3a042710aa73cffe5e82351db5378f26c3be64d8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"document-features",
|
||||
@@ -847,6 +915,19 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pubky"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"pkarr",
|
||||
"pubky-common",
|
||||
"pubky_homeserver",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"ureq",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pubky-common"
|
||||
version = "0.1.0"
|
||||
@@ -866,7 +947,9 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"dirs-next",
|
||||
"pkarr",
|
||||
"pubky-common",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
@@ -921,6 +1004,17 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.5"
|
||||
@@ -965,6 +1059,21 @@ version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"libc",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
@@ -980,6 +1089,38 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.102.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.17"
|
||||
@@ -1238,6 +1379,21 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.38.0"
|
||||
@@ -1381,12 +1537,60 @@ version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"flate2",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"url",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
@@ -1481,6 +1685,15 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
members = ["pubky-*"]
|
||||
members = [ "pubky","pubky-*"]
|
||||
|
||||
# See: https://github.com/rust-lang/rust/issues/90148#issuecomment-949194352
|
||||
resolver = "2"
|
||||
|
||||
@@ -10,6 +10,6 @@ base32 = "0.5.0"
|
||||
blake3 = "1.5.1"
|
||||
ed25519-dalek = "2.1.1"
|
||||
once_cell = "1.19.0"
|
||||
pkarr = "2.0.3"
|
||||
pkarr = "2.1.0"
|
||||
rand = "0.8.5"
|
||||
thiserror = "1.0.60"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Client-server Authentication using signed timesteps
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use ed25519_dalek::ed25519::SignatureBytes;
|
||||
@@ -37,8 +39,9 @@ impl AuthnSignature {
|
||||
Self(bytes.into())
|
||||
}
|
||||
|
||||
pub fn for_token(keypair: &Keypair, audience: &PublicKey, token: &[u8]) -> Self {
|
||||
AuthnSignature::new(keypair, audience, Some(token))
|
||||
/// Sign a randomly generated nonce
|
||||
pub fn generate(keypair: &Keypair, audience: &PublicKey) -> Self {
|
||||
AuthnSignature::new(keypair, audience, None)
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
@@ -192,7 +195,7 @@ mod tests {
|
||||
|
||||
let verifier = AuthnVerifier::new(audience.clone());
|
||||
|
||||
let authn_signature = AuthnSignature::new(&keypair, &audience, None);
|
||||
let authn_signature = AuthnSignature::generate(&keypair, &audience);
|
||||
|
||||
verifier
|
||||
.verify(authn_signature.as_bytes(), &signer)
|
||||
|
||||
@@ -19,6 +19,7 @@ pub async fn publish_server_packet(
|
||||
// assuming any other domain will point to a reverse proxy
|
||||
// at the conventional ports.
|
||||
if domain == "localhost" {
|
||||
svcb.priority = 1;
|
||||
svcb.set_port(port);
|
||||
|
||||
// TODO: Add more parameteres like the signer key!
|
||||
@@ -26,9 +27,10 @@ pub async fn publish_server_packet(
|
||||
};
|
||||
|
||||
// TODO: announce A/AAAA records as well for Noise connections?
|
||||
// Or maybe Iroh's magic socket
|
||||
|
||||
packet.answers.push(pkarr::dns::ResourceRecord::new(
|
||||
"pubky".try_into().unwrap(),
|
||||
"@".try_into().unwrap(),
|
||||
pkarr::dns::CLASS::IN,
|
||||
60 * 60,
|
||||
pkarr::dns::rdata::RData::SVCB(svcb),
|
||||
|
||||
@@ -4,26 +4,31 @@ use anyhow::{Error, Result};
|
||||
use tokio::{net::TcpListener, signal, task::JoinSet};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use pkarr::{
|
||||
mainline::dht::{DhtSettings, Testnet},
|
||||
PkarrClient, PublicKey, Settings,
|
||||
};
|
||||
|
||||
use crate::{config::Config, pkarr::publish_server_packet};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Homeserver {
|
||||
pub(crate) config: Config,
|
||||
port: u16,
|
||||
tasks: JoinSet<std::io::Result<()>>,
|
||||
}
|
||||
|
||||
impl Homeserver {
|
||||
pub async fn start() -> Result<Self> {
|
||||
pub async fn start(config: Config) -> Result<Self> {
|
||||
let app = crate::routes::create_app();
|
||||
|
||||
let mut tasks = JoinSet::new();
|
||||
|
||||
let app = app.clone();
|
||||
|
||||
let listener = TcpListener::bind(SocketAddr::from((
|
||||
[0, 0, 0, 0],
|
||||
6287, // config.port()
|
||||
)))
|
||||
.await?;
|
||||
let listener = TcpListener::bind(SocketAddr::from(([0, 0, 0, 0], config.port()))).await?;
|
||||
|
||||
let bound_addr = listener.local_addr()?;
|
||||
let port = listener.local_addr()?.port();
|
||||
|
||||
// Spawn http server task
|
||||
tasks.spawn(
|
||||
@@ -35,9 +40,44 @@ impl Homeserver {
|
||||
.into_future(),
|
||||
);
|
||||
|
||||
info!("HTTP server listening on {bound_addr}");
|
||||
info!("Homeserver listening on http://localhost:{port}");
|
||||
|
||||
Ok(Self { tasks })
|
||||
let pkarr_client = PkarrClient::new(Settings {
|
||||
dht: DhtSettings {
|
||||
bootstrap: config.bootstsrap(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})?
|
||||
.as_async();
|
||||
|
||||
publish_server_packet(pkarr_client, config.keypair(), config.domain(), port).await?;
|
||||
|
||||
info!(
|
||||
"Homeserver listening on pubky://{}",
|
||||
config.keypair().public_key()
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
tasks,
|
||||
config,
|
||||
port,
|
||||
})
|
||||
}
|
||||
|
||||
/// Test version of [Homeserver::start], using mainline Testnet, and a temporary storage.
|
||||
pub async fn start_test(testnet: &Testnet) -> Result<Self> {
|
||||
Homeserver::start(Config::test(testnet)).await
|
||||
}
|
||||
|
||||
// === Getters ===
|
||||
|
||||
pub fn port(&self) -> u16 {
|
||||
self.port
|
||||
}
|
||||
|
||||
pub fn public_key(&self) -> PublicKey {
|
||||
self.config.keypair().public_key()
|
||||
}
|
||||
|
||||
// === Public Methods ===
|
||||
|
||||
16
pubky/Cargo.toml
Normal file
16
pubky/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "pubky"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
pubky-common = { version = "0.1.0", path = "../pubky-common" }
|
||||
|
||||
pkarr = "2.1.0"
|
||||
ureq = "2.10.0"
|
||||
thiserror = "1.0.62"
|
||||
url = "2.5.2"
|
||||
|
||||
[dev-dependencies]
|
||||
pubky_homeserver = { path = "../pubky-homeserver" }
|
||||
tokio = "1.37.0"
|
||||
20
pubky/src/error.rs
Normal file
20
pubky/src/error.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
//! Main Crate Error
|
||||
|
||||
use pkarr::dns::SimpleDnsError;
|
||||
|
||||
// Alias Result to be the crate Result.
|
||||
pub type Result<T, E = Error> = core::result::Result<T, E>;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
/// Pk common Error
|
||||
pub enum Error {
|
||||
/// For starter, to remove as code matures.
|
||||
#[error("Generic error: {0}")]
|
||||
Generic(String),
|
||||
|
||||
#[error(transparent)]
|
||||
Dns(#[from] SimpleDnsError),
|
||||
|
||||
#[error(transparent)]
|
||||
Pkarr(#[from] pkarr::Error),
|
||||
}
|
||||
228
pubky/src/lib.rs
Normal file
228
pubky/src/lib.rs
Normal file
@@ -0,0 +1,228 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
|
||||
use pkarr::{
|
||||
dns::{rdata::SVCB, Packet},
|
||||
mainline::{dht::DhtSettings, Testnet},
|
||||
Keypair, PkarrClient, PublicKey, Settings, SignedPacket,
|
||||
};
|
||||
use ureq::{Agent, Response};
|
||||
use url::Url;
|
||||
|
||||
use pubky_common::auth::AuthnSignature;
|
||||
|
||||
mod error;
|
||||
|
||||
use error::{Error, Result};
|
||||
|
||||
const MAX_RECURSIVE_PUBKY_HOMESERVER_RESOLUTION: u8 = 3;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
agent: Agent,
|
||||
pkarr: PkarrClient,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
agent: Agent::new(),
|
||||
pkarr: PkarrClient::new(Default::default()).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test(testnet: &Testnet) -> Self {
|
||||
Self {
|
||||
agent: Agent::new(),
|
||||
pkarr: PkarrClient::new(Settings {
|
||||
dht: DhtSettings {
|
||||
request_timeout: Some(Duration::from_millis(10)),
|
||||
bootstrap: Some(testnet.bootstrap.to_owned()),
|
||||
..DhtSettings::default()
|
||||
},
|
||||
..Settings::default()
|
||||
})
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
// === Public Methods ===
|
||||
|
||||
// === Private Methods ===
|
||||
|
||||
/// Publish the SVCB record for `_pubky.<public_key>`.
|
||||
fn publish_pubky_homeserver(&self, keypair: &Keypair, host: &str) -> Result<()> {
|
||||
let mut packet = Packet::new_reply(0);
|
||||
|
||||
if let Some(existing) = self.pkarr.resolve(&keypair.public_key())? {
|
||||
for answer in existing.packet().answers.iter().cloned() {
|
||||
if !answer.name.to_string().starts_with("_pubky") {
|
||||
packet.answers.push(answer.into_owned())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut 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)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolve the homeserver for a pubky.
|
||||
fn resolve_pubky_homeserver(&self, pubky: &PublicKey) -> Result<(PublicKey, String)> {
|
||||
// TODO: cache the result of this function?
|
||||
|
||||
let mut target = format!("_pubky.{}", pubky);
|
||||
let mut homeserver_public_key = None;
|
||||
let mut host = target.clone();
|
||||
|
||||
// PublicKey is very good at extracting the Pkarr TLD from a string.
|
||||
while let Ok(public_key) = PublicKey::try_from(target.clone()) {
|
||||
if let Some(signed_packet) = self.pkarr.resolve(&public_key)? {
|
||||
let mut prior = None;
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(svcb) = prior {
|
||||
homeserver_public_key = Some(public_key);
|
||||
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);
|
||||
};
|
||||
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(homeserver) = homeserver_public_key {
|
||||
return Ok((homeserver, host));
|
||||
}
|
||||
|
||||
Err(Error::Generic("Could not resolve homeserver".to_string()))
|
||||
}
|
||||
|
||||
fn fetch_direct(&self, method: HttpMethod, url: &str) -> Result<Response> {
|
||||
self.agent
|
||||
.request(method.into(), url)
|
||||
.call()
|
||||
.map_err(|_| Error::Generic("ureq error".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Client {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HttpMethod {
|
||||
GET,
|
||||
PUT,
|
||||
}
|
||||
|
||||
impl From<HttpMethod> for &str {
|
||||
fn from(value: HttpMethod) -> Self {
|
||||
match value {
|
||||
HttpMethod::GET => "GET",
|
||||
HttpMethod::PUT => "PUT",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use pkarr::{mainline::Testnet, Keypair};
|
||||
use pubky_homeserver::Homeserver;
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_homeserver() {
|
||||
let testnet = Testnet::new(3);
|
||||
let server = Homeserver::start_test(&testnet).await.unwrap();
|
||||
|
||||
// Publish an intermediate controller of the homeserver
|
||||
let pkarr_client = PkarrClient::new(Settings {
|
||||
dht: DhtSettings {
|
||||
bootstrap: Some(testnet.bootstrap.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.unwrap()
|
||||
.as_async();
|
||||
|
||||
let intermediate = Keypair::random();
|
||||
|
||||
let mut packet = Packet::new_reply(0);
|
||||
|
||||
let server_tld = server.public_key().to_string();
|
||||
|
||||
let mut svcb = SVCB::new(0, server_tld.as_str().try_into().unwrap());
|
||||
|
||||
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(&intermediate, &packet).unwrap();
|
||||
|
||||
pkarr_client.publish(&signed_packet).await.unwrap();
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let client = Client::test(&testnet);
|
||||
|
||||
let pubky = Keypair::random();
|
||||
|
||||
client
|
||||
.publish_pubky_homeserver(&pubky, &format!("pubky.{}", &intermediate.public_key()));
|
||||
|
||||
let (public_key, host) = client
|
||||
.resolve_pubky_homeserver(&pubky.public_key())
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(public_key, server.public_key());
|
||||
assert!(host.starts_with("localhost"));
|
||||
assert!(host.ends_with(&server.port().to_string()));
|
||||
})
|
||||
.await
|
||||
.expect("task failed")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user