diff --git a/crates/adapters/activitypub-base/src/service.rs b/crates/adapters/activitypub-base/src/service.rs index 857d942..4f64a84 100644 --- a/crates/adapters/activitypub-base/src/service.rs +++ b/crates/adapters/activitypub-base/src/service.rs @@ -1336,13 +1336,52 @@ impl domain::ports::FederationActionPort for ActivityPubService { &self, handle: &str, ) -> Result { - let data = self.federation_config.to_request_data(); + use activitypub_federation::fetch::object_id::ObjectId; + let normalized = handle.trim_start_matches('@'); - let actor: crate::actors::DbActor = webfinger_resolve_actor(normalized, &data) + let at = normalized.rfind('@').ok_or_else(|| { + domain::errors::DomainError::InvalidInput("handle must be user@domain".into()) + })?; + let (user, domain_str) = (&normalized[..at], &normalized[at + 1..]); + + // Fetch WebFinger over HTTPS directly — the library's webfinger_resolve_actor + // tries HTTP first in debug mode, which fails on servers without HTTP→HTTPS redirect. + 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 + .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))? + .json() + .await + .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))?; + + 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(domain::errors::DomainError::NotFound)?; + + let self_url = url::Url::parse(self_href) + .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))?; + + let data = self.federation_config.to_request_data(); + let actor: crate::actors::DbActor = ObjectId::from(self_url) + .dereference(&data) .await .map_err(|e: crate::error::Error| { domain::errors::DomainError::ExternalService(e.to_string()) })?; + Ok(domain::models::remote_actor::RemoteActor { url: actor.ap_id.to_string(), handle: actor.username.clone(),