Skip to main content

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 AVATAR_HREF: &str = "https://localhost:3000/media/carol.png";
15const AVATAR_REL: &str = "http://webfinger.net/rel/avatar";
16const HOST: &str = "localhost:3000";
17const PROFILE_HREF: &str = "https://localhost:3000/users/carol";
18const PROFILE_PAGE_REL: &str = "http://webfinger.net/rel/profile-page";
19const ROLE_PROPERTY: &str = "https://example.com/ns/account-role";
20const SUBJECT: &str = "acct:carol@localhost";
21
22#[tokio::main]
23async fn main() -> Result<()> {
24    color_eyre::install()?;
25    tracing_subscriber::fmt()
26        .with_max_level(LevelFilter::DEBUG)
27        .init();
28
29    let addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 3000);
30    let router = Router::new()
31        .route(WELL_KNOWN_PATH, get(webfinger))
32        .route_layer(TraceLayer::new_for_http())
33        .into_make_service();
34    let config = tls_config().await?;
35    let unfiltered_request = WebFingerRequest::builder(SUBJECT)?.host(HOST).build();
36    let profile_request = WebFingerRequest::builder(SUBJECT)?
37        .host(HOST)
38        .rel(PROFILE_PAGE_REL)
39        .build();
40    let avatar_request = WebFingerRequest::builder(SUBJECT)?
41        .host(HOST)
42        .rel(AVATAR_REL)
43        .build();
44
45    info!("Listening at https://{addr}{WELL_KNOWN_PATH}");
46    info!(
47        "Unfiltered query: {}",
48        http::Uri::try_from(&unfiltered_request)?
49    );
50    info!(
51        "Profile-page query: {}",
52        http::Uri::try_from(&profile_request)?
53    );
54    info!("Avatar query: {}", http::Uri::try_from(&avatar_request)?);
55    axum_server::bind_rustls(addr, config).serve(router).await?;
56
57    Ok(())
58}
59
60/// Generate a self-signed certificate for localhost
61async fn tls_config() -> Result<RustlsConfig> {
62    let self_signed_cert = rcgen::generate_simple_self_signed(vec!["localhost".to_string()])
63        .wrap_err("failed to generate self signed certificate for localhost")?;
64    let cert = self_signed_cert.cert.der().to_vec();
65    let key = self_signed_cert.signing_key.serialize_der();
66    RustlsConfig::from_der(vec![cert], key)
67        .await
68        .wrap_err("failed to create tls config")
69}
70
71async fn webfinger(request: WebFingerRequest) -> axum::response::Result<WebFingerResponse> {
72    info!("fetching webfinger resource: {:?}", request);
73    let subject = request.resource.to_string();
74    if subject != SUBJECT {
75        let message = format!("{subject} does not exist");
76        return Err((StatusCode::NOT_FOUND, message).into());
77    }
78    let mut links = Vec::new();
79
80    let profile_rel = Rel::new(PROFILE_PAGE_REL);
81    if request.rels.is_empty() || request.rels.contains(&profile_rel) {
82        links.push(
83            Link::builder(profile_rel)
84                .href(PROFILE_HREF)
85                .title("en", "Carol's profile")
86                .build(),
87        );
88    }
89
90    let avatar_rel = Rel::new(AVATAR_REL);
91    if request.rels.is_empty() || request.rels.contains(&avatar_rel) {
92        links.push(
93            Link::builder(avatar_rel)
94                .href(AVATAR_HREF)
95                .r#type("image/png")
96                .build(),
97        );
98    }
99
100    let response = WebFingerResponse::builder(subject)
101        .alias(PROFILE_HREF)
102        .property(ROLE_PROPERTY, "maintainer")
103        .links(links)
104        .build();
105    Ok(response)
106}