diff --git a/crates/adapters/activitypub/src/instance_actor.rs b/crates/adapters/activitypub/src/instance_actor.rs new file mode 100644 index 0000000..77192f0 --- /dev/null +++ b/crates/adapters/activitypub/src/instance_actor.rs @@ -0,0 +1,57 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use k_ap::{ApActorType, ApUser, ApUserRepository}; + +pub const INSTANCE_ACTOR_ID: uuid::Uuid = + uuid::Uuid::from_bytes([0, 0, 0, 0, 0, 0, 0x40, 0, 0x80, 0, 0, 0, 0, 0, 0, 0]); + +pub struct InstanceActorUserRepo { + inner: Arc, + base_url: String, +} + +impl InstanceActorUserRepo { + pub fn new(inner: Arc, base_url: String) -> Self { + Self { inner, base_url } + } +} + +fn instance_ap_user(base_url: &str) -> ApUser { + ApUser { + id: INSTANCE_ACTOR_ID, + username: "instance".to_string(), + display_name: None, + bio: None, + avatar_url: None, + banner_url: None, + also_known_as: vec![], + profile_url: url::Url::parse(base_url).ok(), + attachment: vec![], + manually_approves_followers: false, + discoverable: false, + actor_type: ApActorType::Service, + featured_url: None, + } +} + +#[async_trait] +impl ApUserRepository for InstanceActorUserRepo { + async fn find_by_id(&self, id: uuid::Uuid) -> anyhow::Result> { + if id == INSTANCE_ACTOR_ID { + return Ok(Some(instance_ap_user(&self.base_url))); + } + self.inner.find_by_id(id).await + } + + async fn find_by_username(&self, username: &str) -> anyhow::Result> { + if username == "instance" { + return Ok(Some(instance_ap_user(&self.base_url))); + } + self.inner.find_by_username(username).await + } + + async fn count_users(&self) -> anyhow::Result { + self.inner.count_users().await + } +} diff --git a/crates/adapters/activitypub/src/lib.rs b/crates/adapters/activitypub/src/lib.rs index 9aa10d0..fe32db1 100644 --- a/crates/adapters/activitypub/src/lib.rs +++ b/crates/adapters/activitypub/src/lib.rs @@ -1,4 +1,5 @@ pub mod handler; +pub mod instance_actor; pub mod note; pub mod port; pub mod service; @@ -33,17 +34,22 @@ pub struct ApServiceConfig { pub async fn build_ap_service( cfg: ApServiceConfig, ) -> (Arc, Arc) { + let user_repo = Arc::new(instance_actor::InstanceActorUserRepo::new( + cfg.user_repo, + cfg.base_url.clone(), + )); let mut builder = ActivityPubService::builder(cfg.base_url) .activity_repo(cfg.activity_repo) .follow_repo(cfg.follow_repo) .actor_repo(cfg.actor_repo) .blocklist_repo(cfg.blocklist_repo) - .user_repo(cfg.user_repo) + .user_repo(user_repo) .content_reader(cfg.ap_handler.clone()) .object_handler(cfg.ap_handler) .allow_registration(cfg.allow_registration) .software_name("thoughts") - .debug(cfg.debug); + .debug(cfg.debug) + .signed_fetch_actor_id(instance_actor::INSTANCE_ACTOR_ID); if let Some(publisher) = cfg.event_publisher { builder = builder.event_publisher(publisher); } diff --git a/crates/adapters/activitypub/src/service.rs b/crates/adapters/activitypub/src/service.rs index 374e2fe..aaa7d69 100644 --- a/crates/adapters/activitypub/src/service.rs +++ b/crates/adapters/activitypub/src/service.rs @@ -136,8 +136,6 @@ fn k_ap_actor_to_domain(a: k_ap::RemoteActor) -> DomainRemoteActor { } } -// TODO: these fetches are unsigned — fails on instances with authorized-fetch (Secure Mode). -// Fix requires exposing k-ap's signed HTTP client. async fn resolve_actor_profiles_from_urls( urls: Vec, ) -> Vec {