From 76292c8f80f6938a458b62a45fcf62a4f9601248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Fri, 5 Jun 2026 09:33:42 +0200 Subject: [PATCH] knotty: add did document handling --- crates/knotty-knot/src/main.rs | 22 ++++++++++---- crates/knotty-knot/src/router.rs | 10 +++---- crates/knotty-knot/src/state.rs | 51 ++++++++++++++++++++++++-------- 3 files changed, 61 insertions(+), 22 deletions(-) diff --git a/crates/knotty-knot/src/main.rs b/crates/knotty-knot/src/main.rs index d1681dc..257161f 100644 --- a/crates/knotty-knot/src/main.rs +++ b/crates/knotty-knot/src/main.rs @@ -7,7 +7,8 @@ mod xrpc; use std::path::PathBuf; use clap::{Args, Parser, Subcommand}; -use miette::IntoDiagnostic; +use jacquard::types::string::Did; +use miette::{Context, IntoDiagnostic}; use state::KnotState; #[derive(Parser, Debug, Clone)] @@ -75,13 +76,24 @@ async fn main() -> miette::Result<()> { Commands::Serve(args) => { let keypair = keypair::Keypair::load(&args.key_path)?; + let did_str = format!("did:web:{}", args.hostname); + let did = Did::new(&did_str) + .into_diagnostic() + .wrap_err("building did:web with hostname")?; + + let owner = Did::new(&args.owner) + .into_diagnostic() + .wrap_err("building owner did")?; + + let hostname = args.hostname; + let listen_addr = args.listen_addr.clone(); - let state = KnotState::new(args); + let state = KnotState::new(&did, &keypair, &owner, &hostname); tracing::info!( - hostname = %state.config.hostname, - owner = %state.config.owner, - did = %state.service_did, + hostname = %state.hostname, + owner = %state.owner, + did = %state.did_doc.id, "starting knotty-knot", ); diff --git a/crates/knotty-knot/src/router.rs b/crates/knotty-knot/src/router.rs index a313cc9..06c1441 100644 --- a/crates/knotty-knot/src/router.rs +++ b/crates/knotty-knot/src/router.rs @@ -1,13 +1,13 @@ use axum::Router; +use jacquard_axum::did_web; use tower_http::trace::TraceLayer; use crate::state::KnotState; pub fn router(state: KnotState) -> Router { - Router::new() - // TODO: build DidDocument from keypair/config and uncomment - // .merge(jacquard_axum::did_web::did_web_router(did_doc)) - .merge(crate::xrpc::router()) - .layer(TraceLayer::new_for_http()) + let did_doc = state.did_doc.clone(); + crate::xrpc::router() .with_state(state) + .merge(did_web::did_web_router(did_doc)) + .layer(TraceLayer::new_for_http()) } diff --git a/crates/knotty-knot/src/state.rs b/crates/knotty-knot/src/state.rs index ba1cc5f..6bfeece 100644 --- a/crates/knotty-knot/src/state.rs +++ b/crates/knotty-knot/src/state.rs @@ -1,25 +1,30 @@ -use jacquard::identity::{slingshot_resolver_default, JacquardResolver}; +use jacquard::identity::{JacquardResolver, slingshot_resolver_default}; +use jacquard::types::did_doc::{DidDocument, Service, VerificationMethod, default_context}; use jacquard::types::string::Did; +use jacquard::types::value::Data; +use jacquard::{CowStr, IntoStatic}; use jacquard_axum::service_auth::ServiceAuth; -use crate::ServeArgs; +use crate::keypair; #[derive(Clone)] pub struct KnotState { - pub config: ServeArgs, pub resolver: JacquardResolver, - pub service_did: Did<'static>, + pub did_doc: DidDocument<'static>, + pub owner: Did<'static>, + pub hostname: &'static str, } impl KnotState { - pub fn new(config: ServeArgs) -> Self { - let resolver = slingshot_resolver_default(); - let did_str: &'static str = format!("did:web:{}", config.hostname).leak(); - let service_did = Did::new_static(did_str).expect("invalid hostname for did:web"); + pub fn new(did: &Did<'_>, keypair: &keypair::Keypair, owner: &Did<'_>, hostname: &str) -> Self { + let service_url = format!("https://{}", did); + let did_doc = build_did_doc(&did, &service_url, &keypair.public_key_multibase()); + Self { - config, - resolver, - service_did, + resolver: slingshot_resolver_default(), + owner: owner.clone().into_static(), + did_doc, + hostname: hostname.to_owned().leak(), } } } @@ -28,7 +33,7 @@ impl ServiceAuth for KnotState { type Resolver = JacquardResolver; fn service_did(&self) -> &Did<'_> { - &self.service_did + &self.did_doc.id } fn resolver(&self) -> &Self::Resolver { @@ -39,3 +44,25 @@ impl ServiceAuth for KnotState { true } } + +fn build_did_doc(did: &str, service_url: &str, public_key_multibase: &str) -> DidDocument<'static> { + DidDocument { + context: default_context(), + id: Did::new_owned(did).expect("hostname yields valid did:web"), + also_known_as: None, + verification_method: Some(vec![VerificationMethod { + id: CowStr::copy_from_str(&format!("{did}#atproto")), + r#type: CowStr::new_static("Multikey"), + controller: Some(CowStr::copy_from_str(did)), + public_key_multibase: Some(CowStr::copy_from_str(public_key_multibase)), + extra_data: Default::default(), + }]), + service: Some(vec![Service { + id: CowStr::new_static("#atproto_pds"), + r#type: CowStr::new_static("AtprotoPersonalDataServer"), + service_endpoint: Some(Data::from(service_url.to_string())), + extra_data: Default::default(), + }]), + extra_data: Default::default(), + } +} -- 2.51.2