1use std::net::{Ipv4Addr, SocketAddr};
2
3use actix_web::{App, HttpServer, web};
4use color_eyre::Result;
5use color_eyre::eyre::{Context, eyre};
6use rustls::ServerConfig;
7use tracing::info;
8use tracing_subscriber::EnvFilter;
9use webfinger_rs::{Link, Rel, WELL_KNOWN_PATH, WebFingerRequest, WebFingerResponse};
10
11const AVATAR_HREF: &str = "https://localhost:3000/media/carol.png";
12const AVATAR_REL: &str = "http://webfinger.net/rel/avatar";
13const HOST: &str = "localhost:3000";
14const PROFILE_HREF: &str = "https://localhost:3000/users/carol";
15const PROFILE_PAGE_REL: &str = "http://webfinger.net/rel/profile-page";
16const ROLE_PROPERTY: &str = "https://example.com/ns/account-role";
17const SUBJECT: &str = "acct:carol@localhost";
18
19#[actix_web::main]
20async fn main() -> Result<()> {
21 color_eyre::install()?;
22 let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
23 tracing_subscriber::fmt().with_env_filter(env_filter).init();
24
25 let addrs = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 3000);
26 let config = tls_config()?;
27 let server = HttpServer::new(|| App::new().route(WELL_KNOWN_PATH, web::get().to(webfinger)))
28 .bind_rustls_0_23(addrs, config)?;
29 let unfiltered_request = WebFingerRequest::builder(SUBJECT)?.host(HOST).build();
30 let profile_request = WebFingerRequest::builder(SUBJECT)?
31 .host(HOST)
32 .rel(PROFILE_PAGE_REL)
33 .build();
34 let avatar_request = WebFingerRequest::builder(SUBJECT)?
35 .host(HOST)
36 .rel(AVATAR_REL)
37 .build();
38
39 info!("Listening at https://{addrs}{WELL_KNOWN_PATH}");
40 info!(
41 "Unfiltered query: {}",
42 http::Uri::try_from(&unfiltered_request)?
43 );
44 info!(
45 "Profile-page query: {}",
46 http::Uri::try_from(&profile_request)?
47 );
48 info!("Avatar query: {}", http::Uri::try_from(&avatar_request)?);
49 server.run().await?;
50 Ok(())
51}
52
53fn tls_config() -> Result<ServerConfig> {
54 let self_signed_cert = rcgen::generate_simple_self_signed(vec!["localhost".to_string()])
55 .wrap_err("failed to generate self signed certificate for localhost")?;
56 let cert_chain = self_signed_cert.cert.into();
57 let key_der = self_signed_cert
58 .signing_key
59 .serialize_der()
60 .try_into()
61 .map_err(|s: &str| eyre!(s))?;
62 ServerConfig::builder()
63 .with_no_client_auth()
64 .with_single_cert(vec![cert_chain], key_der)
65 .wrap_err("failed to create tls config")
66}
67
68async fn webfinger(request: WebFingerRequest) -> actix_web::Result<WebFingerResponse> {
69 info!("fetching webfinger resource: {:?}", request);
70 let subject = request.resource.to_string();
71 if subject != SUBJECT {
72 let message = format!("{subject} does not exist");
73 return Err(actix_web::error::ErrorNotFound(message))?;
74 }
75 let mut links = Vec::new();
76
77 let profile_rel = Rel::new(PROFILE_PAGE_REL);
78 if request.rels.is_empty() || request.rels.contains(&profile_rel) {
79 links.push(
80 Link::builder(profile_rel)
81 .href(PROFILE_HREF)
82 .title("en", "Carol's profile")
83 .build(),
84 );
85 }
86
87 let avatar_rel = Rel::new(AVATAR_REL);
88 if request.rels.is_empty() || request.rels.contains(&avatar_rel) {
89 links.push(
90 Link::builder(avatar_rel)
91 .href(AVATAR_HREF)
92 .r#type("image/png")
93 .build(),
94 );
95 }
96
97 let response = WebFingerResponse::builder(subject)
98 .alias(PROFILE_HREF)
99 .property(ROLE_PROPERTY, "maintainer")
100 .links(links)
101 .build();
102 Ok(response)
103}