diff --git a/crates/adapters/activitypub-base/src/service.rs b/crates/adapters/activitypub-base/src/service.rs index d444b58..b7dc1c1 100644 --- a/crates/adapters/activitypub-base/src/service.rs +++ b/crates/adapters/activitypub-base/src/service.rs @@ -1,9 +1,7 @@ use std::sync::Arc; use activitypub_federation::{ - activity_sending::SendActivityTask, - fetch::{object_id::ObjectId, webfinger::webfinger_resolve_actor}, - protocol::context::WithContext, + activity_sending::SendActivityTask, fetch::object_id::ObjectId, protocol::context::WithContext, traits::Actor, }; use axum::{Router, routing::get, routing::post}; @@ -342,6 +340,48 @@ impl ActivityPubService { Ok(()) } + /// Resolve a `@user@domain` handle to a `DbActor` over HTTPS directly. + /// The library's `webfinger_resolve_actor` tries HTTP first in debug mode, which breaks + /// on servers that don't redirect HTTP → HTTPS. + async fn webfinger_https( + handle: &str, + data: &activitypub_federation::config::Data, + ) -> anyhow::Result { + let normalized = handle.trim_start_matches('@'); + let at = normalized + .rfind('@') + .ok_or_else(|| anyhow::anyhow!("handle must be user@domain"))?; + let (user, domain_str) = (&normalized[..at], &normalized[at + 1..]); + let wf_url = format!( + "https://{}/.well-known/webfinger?resource=acct:{}@{}", + domain_str, user, domain_str + ); + let wf: serde_json::Value = reqwest::Client::new() + .get(&wf_url) + .header("Accept", "application/jrd+json, application/json") + .send() + .await? + .json() + .await?; + let self_href = wf["links"] + .as_array() + .and_then(|links| { + links.iter().find(|l| { + l["rel"].as_str() == Some("self") + && l["type"].as_str() == Some("application/activity+json") + }) + }) + .and_then(|l| l["href"].as_str()) + .ok_or_else(|| anyhow::anyhow!("no self link in WebFinger response"))? + .to_owned(); + let self_url = url::Url::parse(&self_href)?; + let actor: DbActor = ObjectId::from(self_url) + .dereference(data) + .await + .map_err(|e| anyhow::anyhow!("{e}"))?; + Ok(actor) + } + pub async fn follow(&self, local_user_id: uuid::Uuid, handle: &str) -> anyhow::Result<()> { let data = self.federation_config.to_request_data(); @@ -351,9 +391,7 @@ impl ActivityPubService { return self.follow_local(local_user_id, parts[0], &data).await; } - let remote_actor: DbActor = webfinger_resolve_actor(handle, &data) - .await - .map_err(|e| anyhow::anyhow!("{e}"))?; + let remote_actor: DbActor = Self::webfinger_https(handle, &data).await?; let local_actor = get_local_actor(local_user_id, &data) .await @@ -1424,9 +1462,9 @@ impl domain::ports::FederationActionPort for ActivityPubService { handle: &str, ) -> Result<(), domain::errors::DomainError> { let data = self.federation_config.to_request_data(); - let remote_actor: DbActor = webfinger_resolve_actor(handle, &data).await.map_err(|e| { - domain::errors::DomainError::ExternalService(anyhow::anyhow!("{e}").to_string()) - })?; + let remote_actor: DbActor = Self::webfinger_https(handle, &data) + .await + .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))?; let actor_url = remote_actor.ap_id.to_string(); self.unfollow(local_user_id.as_uuid(), &actor_url) .await