]> Untitled Git - lemmy.git/blob - src/routes/webfinger.rs
Isomorphic docker (#1124)
[lemmy.git] / src / routes / webfinger.rs
1 use crate::LemmyContext;
2 use actix_web::{error::ErrorBadRequest, web::Query, *};
3 use anyhow::anyhow;
4 use lemmy_api_structs::blocking;
5 use lemmy_db::{community::Community, user::User_};
6 use lemmy_utils::{
7   settings::Settings,
8   LemmyError,
9   WEBFINGER_COMMUNITY_REGEX,
10   WEBFINGER_USER_REGEX,
11 };
12 use serde::{Deserialize, Serialize};
13
14 #[derive(Deserialize)]
15 pub struct Params {
16   resource: String,
17 }
18
19 #[derive(Serialize, Deserialize, Debug)]
20 pub struct WebFingerResponse {
21   pub subject: String,
22   pub aliases: Vec<String>,
23   pub links: Vec<WebFingerLink>,
24 }
25
26 #[derive(Serialize, Deserialize, Debug)]
27 pub struct WebFingerLink {
28   pub rel: Option<String>,
29   #[serde(rename(serialize = "type", deserialize = "type"))]
30   pub type_: Option<String>,
31   pub href: Option<String>,
32   #[serde(skip_serializing_if = "Option::is_none")]
33   pub template: Option<String>,
34 }
35
36 pub fn config(cfg: &mut web::ServiceConfig) {
37   if Settings::get().federation.enabled {
38     cfg.route(
39       ".well-known/webfinger",
40       web::get().to(get_webfinger_response),
41     );
42   }
43 }
44
45 /// Responds to webfinger requests of the following format. There isn't any real documentation for
46 /// this, but it described in this blog post:
47 /// https://mastodon.social/.well-known/webfinger?resource=acct:gargron@mastodon.social
48 ///
49 /// You can also view the webfinger response that Mastodon sends:
50 /// https://radical.town/.well-known/webfinger?resource=acct:felix@radical.town
51 async fn get_webfinger_response(
52   info: Query<Params>,
53   context: web::Data<LemmyContext>,
54 ) -> Result<HttpResponse, Error> {
55   let community_regex_parsed = WEBFINGER_COMMUNITY_REGEX
56     .captures(&info.resource)
57     .map(|c| c.get(1))
58     .flatten();
59
60   let user_regex_parsed = WEBFINGER_USER_REGEX
61     .captures(&info.resource)
62     .map(|c| c.get(1))
63     .flatten();
64
65   let url = if let Some(community_name) = community_regex_parsed {
66     let community_name = community_name.as_str().to_owned();
67     // Make sure the requested community exists.
68     blocking(context.pool(), move |conn| {
69       Community::read_from_name(conn, &community_name)
70     })
71     .await?
72     .map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?
73     .actor_id
74   } else if let Some(user_name) = user_regex_parsed {
75     let user_name = user_name.as_str().to_owned();
76     // Make sure the requested user exists.
77     blocking(context.pool(), move |conn| {
78       User_::read_from_name(conn, &user_name)
79     })
80     .await?
81     .map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?
82     .actor_id
83   } else {
84     return Err(ErrorBadRequest(LemmyError::from(anyhow!("not_found"))));
85   };
86
87   let json = WebFingerResponse {
88     subject: info.resource.to_owned(),
89     aliases: vec![url.to_owned()],
90     links: vec![
91       WebFingerLink {
92         rel: Some("http://webfinger.net/rel/profile-page".to_string()),
93         type_: Some("text/html".to_string()),
94         href: Some(url.to_owned()),
95         template: None,
96       },
97       WebFingerLink {
98         rel: Some("self".to_string()),
99         type_: Some("application/activity+json".to_string()),
100         href: Some(url),
101         template: None,
102       }, // TODO: this also needs to return the subscribe link once that's implemented
103          //{
104          //  "rel": "http://ostatus.org/schema/1.0/subscribe",
105          //  "template": "https://my_instance.com/authorize_interaction?uri={uri}"
106          //}
107     ],
108   };
109
110   Ok(HttpResponse::Ok().json(json))
111 }