1 use crate::LemmyContext;
2 use actix_web::{error::ErrorBadRequest, web::Query, *};
4 use lemmy_api_structs::blocking;
5 use lemmy_db::{community::Community, user::User_};
9 WEBFINGER_COMMUNITY_REGEX,
12 use serde::{Deserialize, Serialize};
14 #[derive(Deserialize)]
19 #[derive(Serialize, Deserialize, Debug)]
20 pub struct WebFingerResponse {
22 pub aliases: Vec<String>,
23 pub links: Vec<WebFingerLink>,
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>,
36 pub fn config(cfg: &mut web::ServiceConfig) {
37 if Settings::get().federation.enabled {
39 ".well-known/webfinger",
40 web::get().to(get_webfinger_response),
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
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(
53 context: web::Data<LemmyContext>,
54 ) -> Result<HttpResponse, Error> {
55 let community_regex_parsed = WEBFINGER_COMMUNITY_REGEX
56 .captures(&info.resource)
60 let user_regex_parsed = WEBFINGER_USER_REGEX
61 .captures(&info.resource)
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)
72 .map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?
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)
81 .map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?
84 return Err(ErrorBadRequest(LemmyError::from(anyhow!("not_found"))));
87 let json = WebFingerResponse {
88 subject: info.resource.to_owned(),
89 aliases: vec![url.to_owned()],
92 rel: Some("http://webfinger.net/rel/profile-page".to_string()),
93 type_: Some("text/html".to_string()),
94 href: Some(url.to_owned()),
98 rel: Some("self".to_string()),
99 type_: Some("application/activity+json".to_string()),
102 }, // TODO: this also needs to return the subscribe link once that's implemented
104 // "rel": "http://ostatus.org/schema/1.0/subscribe",
105 // "template": "https://my_instance.com/authorize_interaction?uri={uri}"
110 Ok(HttpResponse::Ok().json(json))