]> Untitled Git - lemmy.git/blob - crates/apub/src/fetcher/webfinger.rs
Moving settings to Database. (#2492)
[lemmy.git] / crates / apub / src / fetcher / webfinger.rs
1 use crate::{local_instance, ActorType};
2 use activitypub_federation::{core::object_id::ObjectId, traits::ApubObject};
3 use anyhow::anyhow;
4 use itertools::Itertools;
5 use lemmy_api_common::utils::blocking;
6 use lemmy_db_schema::{newtypes::DbUrl, source::local_site::LocalSite};
7 use lemmy_utils::error::LemmyError;
8 use lemmy_websocket::LemmyContext;
9 use serde::{Deserialize, Serialize};
10 use tracing::debug;
11 use url::Url;
12
13 #[derive(Serialize, Deserialize, Debug)]
14 pub struct WebfingerLink {
15   pub rel: Option<String>,
16   #[serde(rename = "type")]
17   pub kind: Option<String>,
18   pub href: Option<Url>,
19 }
20
21 #[derive(Serialize, Deserialize, Debug)]
22 pub struct WebfingerResponse {
23   pub subject: String,
24   pub links: Vec<WebfingerLink>,
25 }
26
27 /// Turns a person id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
28 /// using webfinger.
29 #[tracing::instrument(skip_all)]
30 pub(crate) async fn webfinger_resolve_actor<Kind>(
31   identifier: &str,
32   local_only: bool,
33   context: &LemmyContext,
34   request_counter: &mut i32,
35 ) -> Result<DbUrl, LemmyError>
36 where
37   Kind: ApubObject<DataType = LemmyContext, Error = LemmyError> + ActorType + Send + 'static,
38   for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
39 {
40   let protocol = context.settings().get_protocol_string();
41   let (_, domain) = identifier
42     .splitn(2, '@')
43     .collect_tuple()
44     .ok_or_else(|| LemmyError::from_message("Invalid webfinger query, missing domain"))?;
45   let fetch_url = format!(
46     "{}://{}/.well-known/webfinger?resource=acct:{}",
47     protocol, domain, identifier
48   );
49   debug!("Fetching webfinger url: {}", &fetch_url);
50
51   let local_site = blocking(context.pool(), LocalSite::read).await?;
52   let http_fetch_retry_limit = local_site
53     .as_ref()
54     .map(|l| l.federation_http_fetch_retry_limit)
55     .unwrap_or(25);
56
57   *request_counter += 1;
58   if *request_counter > http_fetch_retry_limit {
59     return Err(LemmyError::from_message("Request retry limit reached"));
60   }
61
62   let response = context.client().get(&fetch_url).send().await?;
63
64   let res: WebfingerResponse = response.json().await.map_err(LemmyError::from)?;
65
66   let links: Vec<Url> = res
67     .links
68     .iter()
69     .filter(|link| {
70       if let Some(type_) = &link.kind {
71         type_.starts_with("application/")
72       } else {
73         false
74       }
75     })
76     .filter_map(|l| l.href.clone())
77     .collect();
78   for l in links {
79     let object_id = ObjectId::<Kind>::new(l);
80     let object = if local_only {
81       object_id.dereference_local(context).await
82     } else {
83       object_id
84         .dereference(context, local_instance(context), request_counter)
85         .await
86     };
87     if object.is_ok() {
88       return object.map(|o| o.actor_id().into());
89     }
90   }
91   let err = anyhow!("Failed to resolve actor for {}", identifier);
92   Err(LemmyError::from_error_message(err, "failed_to_resolve"))
93 }