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