Skip to main content

actix/
actix.rs

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}