axum/
axum.rs

1use std::net::{Ipv4Addr, SocketAddr};
2
3use axum::Router;
4use axum::routing::get;
5use axum_server::tls_rustls::RustlsConfig;
6use color_eyre::Result;
7use color_eyre::eyre::Context;
8use http::StatusCode;
9use tower_http::trace::TraceLayer;
10use tracing::info;
11use tracing::level_filters::LevelFilter;
12use webfinger_rs::{Link, Rel, WELL_KNOWN_PATH, WebFingerRequest, WebFingerResponse};
13
14const SUBJECT: &str = "acct:carol@localhost";
15
16#[tokio::main]
17async fn main() -> Result<()> {
18    color_eyre::install()?;
19    tracing_subscriber::fmt()
20        .with_max_level(LevelFilter::DEBUG)
21        .init();
22
23    let addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 3000);
24    let router = Router::new()
25        .route(WELL_KNOWN_PATH, get(webfinger))
26        .route_layer(TraceLayer::new_for_http())
27        .into_make_service();
28    let config = tls_config().await?;
29
30    info!("Listening at https://{addr:?}{WELL_KNOWN_PATH}?resource={SUBJECT}");
31    axum_server::bind_rustls(addr, config).serve(router).await?;
32
33    Ok(())
34}
35
36/// Generate a self-signed certificate for localhost
37async fn tls_config() -> Result<RustlsConfig> {
38    let self_signed_cert = rcgen::generate_simple_self_signed(vec!["localhost".to_string()])
39        .wrap_err("failed to generate self signed certificat for localhost")?;
40    let cert = self_signed_cert.cert.der().to_vec();
41    let key = self_signed_cert.key_pair.serialize_der();
42    RustlsConfig::from_der(vec![cert], key)
43        .await
44        .wrap_err("failed to create tls config")
45}
46
47async fn webfinger(request: WebFingerRequest) -> axum::response::Result<WebFingerResponse> {
48    info!("fetching webfinger resource: {:?}", request);
49    let subject = request.resource.to_string();
50    if subject != SUBJECT {
51        let message = format!("{subject} does not exist");
52        return Err((StatusCode::NOT_FOUND, message).into());
53    }
54    let rel = Rel::new("http://webfinger.net/rel/profile-page");
55    let response = if request.rels.is_empty() || request.rels.contains(&rel) {
56        let link = Link::builder(rel).href(format!("https://example.com/profile/{subject}"));
57        WebFingerResponse::builder(subject).link(link).build()
58    } else {
59        WebFingerResponse::builder(subject).build()
60    };
61    Ok(response)
62}