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