A better Rust ATProto crate
1

Configure Feed

Select the types of activity you want to include in your feed.

at main 4.4 kB View raw
1//! Helper for serving did:web DID documents 2//! 3//! did:web DIDs resolve to HTTPS endpoints serving DID documents. This module 4//! provides a router that serves your service's DID document at `/.well-known/did.json`. 5//! 6//! # Example 7//! 8//! ```no_run 9//! use axum::Router; 10//! use jacquard_axum::did_web::did_web_router; 11//! use jacquard_common::types::did_doc::DidDocument; 12//! 13//! #[tokio::main] 14//! async fn main() { 15//! // Your DID document (typically loaded from config or generated) 16//! let did_doc: DidDocument = serde_json::from_str(r#"{ 17//! "id": "did:web:feedgen.example.com", 18//! "verificationMethod": [{ 19//! "id": "did:web:feedgen.example.com#atproto", 20//! "type": "Multikey", 21//! "controller": "did:web:feedgen.example.com", 22//! "publicKeyMultibase": "zQ3sh..." 23//! }] 24//! }"#).unwrap(); 25//! 26//! let app = Router::new() 27//! .merge(did_web_router(did_doc)); 28//! 29//! let listener = tokio::net::TcpListener::bind("0.0.0.0:443") 30//! .await 31//! .unwrap(); 32//! axum::serve(listener, app).await.unwrap(); 33//! } 34//! ``` 35 36use axum::{ 37 Json, Router, 38 http::{HeaderValue, StatusCode, header}, 39 response::IntoResponse, 40 routing::get, 41}; 42use jacquard::deps::smol_str::SmolStr; 43use jacquard_common::types::did_doc::DidDocument; 44 45/// Create a router that serves a DID document at `/.well-known/did.json` 46/// 47/// Returns a Router that can be merged into your main application router. 48/// The DID document is cloned on each request. 49/// 50/// # Example 51/// 52/// ```no_run 53/// use axum::Router; 54/// use jacquard_axum::did_web::did_web_router; 55/// use jacquard_common::types::did_doc::DidDocument; 56/// 57/// # async fn example(did_doc: DidDocument) { 58/// let app = Router::new() 59/// .merge(did_web_router(did_doc)); 60/// # } 61/// ``` 62pub fn did_web_router(did_doc: DidDocument<SmolStr>) -> Router { 63 Router::new().route( 64 "/.well-known/did.json", 65 get(move || async move { 66 ( 67 StatusCode::OK, 68 [( 69 header::CONTENT_TYPE, 70 HeaderValue::from_static("application/did+json"), 71 )], 72 Json(did_doc.clone()), 73 ) 74 .into_response() 75 }), 76 ) 77} 78 79#[cfg(test)] 80mod tests { 81 use super::*; 82 use axum::body::{Body, to_bytes}; 83 use axum::http::{Request, StatusCode, header}; 84 use jacquard::deps::smol_str::SmolStr; 85 use jacquard::types::string::Did; 86 use jacquard_common::types::did_doc::DidDocument; 87 use tower::ServiceExt; 88 89 // A minimal but spec-shaped DID document used by the did:web router tests. 90 fn sample_did_document() -> DidDocument<SmolStr> { 91 DidDocument { 92 context: vec![SmolStr::new_static("https://www.w3.org/ns/did/v1")], 93 id: Did::new_static("did:web:example.com").unwrap(), 94 also_known_as: None, 95 verification_method: None, 96 service: Some(vec![]), 97 extra_data: std::collections::BTreeMap::new(), 98 } 99 } 100 101 #[tokio::test] 102 async fn did_web_router_serves_document_at_well_known_path() { 103 let doc = sample_did_document(); 104 let expected = serde_json::to_value(&doc).unwrap(); 105 let app = did_web_router(doc); 106 107 let response = app 108 .oneshot( 109 Request::builder() 110 .uri("/.well-known/did.json") 111 .body(Body::empty()) 112 .unwrap(), 113 ) 114 .await 115 .unwrap(); 116 assert_eq!(response.status(), StatusCode::OK); 117 assert_eq!( 118 response.headers().get(header::CONTENT_TYPE).unwrap(), 119 "application/did+json" 120 ); 121 let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap(); 122 let body: serde_json::Value = serde_json::from_slice(&bytes).unwrap(); 123 assert_eq!(body, expected); 124 } 125 126 #[tokio::test] 127 async fn did_web_router_rejects_unknown_paths() { 128 let app = did_web_router(sample_did_document()); 129 130 let response = app 131 .oneshot( 132 Request::builder() 133 .uri("/elsewhere") 134 .body(Body::empty()) 135 .unwrap(), 136 ) 137 .await 138 .unwrap(); 139 assert_eq!(response.status(), StatusCode::NOT_FOUND); 140 } 141}