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
60async 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}