]> Untitled Git - lemmy.git/blob - crates/apub/src/fetcher/webfinger.rs
428c8d59a41c8ebffb40b722a989180f8638f3ca
[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;
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   *request_counter += 1;
51   if *request_counter > context.settings().federation.http_fetch_retry_limit {
52     return Err(LemmyError::from_message("Request retry limit reached"));
53   }
54
55   let response = context.client().get(&fetch_url).send().await?;
56
57   let res: WebfingerResponse = response.json().await.map_err(LemmyError::from)?;
58
59   let links: Vec<Url> = res
60     .links
61     .iter()
62     .filter(|link| {
63       if let Some(type_) = &link.kind {
64         type_.starts_with("application/")
65       } else {
66         false
67       }
68     })
69     .filter_map(|l| l.href.clone())
70     .collect();
71   for l in links {
72     let object_id = ObjectId::<Kind>::new(l);
73     let object = if local_only {
74       object_id.dereference_local(context).await
75     } else {
76       object_id
77         .dereference(context, local_instance(context), request_counter)
78         .await
79     };
80     if object.is_ok() {
81       return object.map(|o| o.actor_id().into());
82     }
83   }
84   let err = anyhow!("Failed to resolve actor for {}", identifier);
85   Err(LemmyError::from_error_message(err, "failed_to_resolve"))
86 }