1use std::net::{Ipv4Addr, SocketAddr};
2
3use actix_web::{get, App, HttpServer};
4use color_eyre::{
5 eyre::{eyre, Context},
6 Result,
7};
8use rustls::ServerConfig;
9use tracing::{info, level_filters::LevelFilter};
10use webfinger_rs::{Link, Rel, WebFingerRequest, WebFingerResponse, WELL_KNOWN_PATH};
11
12const SUBJECT: &str = "acct:carol@localhost";
13
14#[actix_web::main]
15async fn main() -> Result<()> {
16 color_eyre::install()?;
17 let env_filter = tracing_subscriber::EnvFilter::builder()
18 .with_default_directive(LevelFilter::INFO.into())
19 .from_env_lossy();
20 tracing_subscriber::fmt().with_env_filter(env_filter).init();
21
22 let addrs = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 3000);
23 let config = tls_config()?;
24 let server =
25 HttpServer::new(|| App::new().service(webfinger)).bind_rustls_0_23(addrs, config)?;
26 info!("Listening at https://{addrs}{WELL_KNOWN_PATH}?resource={SUBJECT}");
27 server.run().await?;
28 Ok(())
29}
30
31fn tls_config() -> Result<ServerConfig> {
32 let self_signed_cert = rcgen::generate_simple_self_signed(vec!["localhost".to_string()])
33 .wrap_err("failed to generate self signed certificat for localhost")?;
34 let cert_chain = self_signed_cert.cert.into();
35 let key_der = self_signed_cert
36 .key_pair
37 .serialize_der()
38 .try_into()
39 .map_err(|s: &str| eyre!(s))?;
40 ServerConfig::builder()
42 .with_no_client_auth()
43 .with_single_cert(vec![cert_chain], key_der)
44 .wrap_err("failed to create tls config")
45}
46
47#[get("/.well-known/webfinger")]
48async fn webfinger(request: WebFingerRequest) -> actix_web::Result<WebFingerResponse> {
49 info!("fetching webfinger resource: {:?}", request);
50 let subject = request.resource.to_string();
51 if subject != SUBJECT {
52 let message = format!("{subject} does not exist");
53 return Err(actix_web::error::ErrorNotFound(message))?;
54 }
55 let rel = Rel::new("http://webfinger.net/rel/profile-page");
56 let response = if request.rels.is_empty() || request.rels.contains(&rel) {
57 let link = Link::builder(rel).href(format!("https://example.com/profile/{subject}"));
58 WebFingerResponse::builder(subject).link(link).build()
59 } else {
60 WebFingerResponse::builder(subject).build()
61 };
62 Ok(response)
63}