2 Commits

Author SHA1 Message Date
699258f830 feat: add targeted tracing logs for actor lookup and verification 2026-05-27 22:55:39 +02:00
9412a9739a fix: allow www. apex equivalence in actor domain verification
Threads serves actors at threads.net but their id field uses www.threads.net.
Extract apex_domain() helper and fall back to apex comparison when the
strict verify_domains_match check fails.
2026-05-27 22:49:30 +02:00
4 changed files with 30 additions and 5 deletions

2
Cargo.lock generated
View File

@@ -1368,7 +1368,7 @@ dependencies = [
[[package]] [[package]]
name = "k-ap" name = "k-ap"
version = "0.1.4" version = "0.1.6"
dependencies = [ dependencies = [
"activitypub_federation", "activitypub_federation",
"anyhow", "anyhow",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "k-ap" name = "k-ap"
version = "0.1.5" version = "0.1.7"
edition = "2024" edition = "2024"
description = "Generic ActivityPub protocol layer" description = "Generic ActivityPub protocol layer"
license = "MIT" license = "MIT"

View File

@@ -193,6 +193,11 @@ pub async fn get_local_actor(
}) })
} }
fn apex_domain(url: &Url) -> String {
let host = url.host_str().unwrap_or("");
host.strip_prefix("www.").unwrap_or(host).to_owned()
}
#[async_trait::async_trait] #[async_trait::async_trait]
impl Object for DbActor { impl Object for DbActor {
type DataType = FederationData; type DataType = FederationData;
@@ -319,11 +324,26 @@ impl Object for DbActor {
expected_domain: &Url, expected_domain: &Url,
_data: &Data<Self::DataType>, _data: &Data<Self::DataType>,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
verify_domains_match(json.id.inner(), expected_domain)?; if verify_domains_match(json.id.inner(), expected_domain).is_ok() {
Ok(()) return Ok(());
}
if apex_domain(json.id.inner()) == apex_domain(expected_domain) {
tracing::debug!(
actor_id = %json.id.inner(),
expected = %expected_domain,
"domain verified via www-apex equivalence"
);
return Ok(());
}
verify_domains_match(json.id.inner(), expected_domain).map_err(Error::from)
} }
async fn from_json(json: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Self::Error> { async fn from_json(json: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Self::Error> {
tracing::debug!(
actor_id = %json.id.inner(),
username = %json.preferred_username,
"ingesting remote actor"
);
let shared_inbox_url = json.endpoints.as_ref().map(|e| e.shared_inbox.to_string()); let shared_inbox_url = json.endpoints.as_ref().map(|e| e.shared_inbox.to_string());
let actor = RemoteActor { let actor = RemoteActor {
url: json.id.inner().to_string(), url: json.id.inner().to_string(),

View File

@@ -337,10 +337,13 @@ impl ActivityPubService {
&self, &self,
handle: &str, handle: &str,
) -> anyhow::Result<crate::user::LookedUpActor> { ) -> anyhow::Result<crate::user::LookedUpActor> {
tracing::info!(handle, "looking up remote actor");
let data = self.federation_config.to_request_data(); let data = self.federation_config.to_request_data();
let actor = Self::webfinger_https(handle, &data).await?; let actor = Self::webfinger_https(handle, &data).await
.inspect_err(|e| tracing::warn!(handle, error = %e, "actor lookup failed"))?;
let domain = actor.ap_id.host_str().unwrap_or("").to_string(); let domain = actor.ap_id.host_str().unwrap_or("").to_string();
let handle = format!("{}@{}", actor.username, domain); let handle = format!("{}@{}", actor.username, domain);
tracing::info!(handle, ap_url = %actor.ap_id, "remote actor resolved");
Ok(crate::user::LookedUpActor { Ok(crate::user::LookedUpActor {
handle, handle,
display_name: actor.display_name, display_name: actor.display_name,
@@ -597,6 +600,7 @@ impl ActivityPubService {
"https://{}/.well-known/webfinger?resource=acct:{}@{}", "https://{}/.well-known/webfinger?resource=acct:{}@{}",
domain_str, user, domain_str domain_str, user, domain_str
); );
tracing::debug!(handle, wf_url, "resolving webfinger");
let wf: serde_json::Value = reqwest::Client::new() let wf: serde_json::Value = reqwest::Client::new()
.get(&wf_url) .get(&wf_url)
.header("Accept", "application/jrd+json, application/json") .header("Accept", "application/jrd+json, application/json")
@@ -615,6 +619,7 @@ impl ActivityPubService {
.and_then(|l| l["href"].as_str()) .and_then(|l| l["href"].as_str())
.ok_or_else(|| anyhow::anyhow!("no self link in WebFinger response"))? .ok_or_else(|| anyhow::anyhow!("no self link in WebFinger response"))?
.to_owned(); .to_owned();
tracing::debug!(handle, self_href, "webfinger resolved, fetching actor with signature");
let self_url = url::Url::parse(&self_href)?; let self_url = url::Url::parse(&self_href)?;
let actor: DbActor = ObjectId::from(self_url) let actor: DbActor = ObjectId::from(self_url)
.dereference(data) .dereference(data)