diff --git a/Cargo.toml b/Cargo.toml index 2a58253..45b4567 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "activitypub_federation" -version = "0.6.2" +version = "0.6.5" edition = "2021" description = "High-level Activitypub framework" keywords = ["activitypub", "activitystreams", "federation", "fediverse"] @@ -75,7 +75,8 @@ tokio = { version = "1.43.0", features = [ "time", ] } futures = "0.3.31" -moka = { version = "0.12.10", features = ["future"] } +moka = { version = "0.12.8", features = ["future"] } +either = "1.15.0" # Actix-web actix-web = { version = "4.9.0", default-features = false, optional = true } diff --git a/docs/07_fetching_data.md b/docs/07_fetching_data.md index ef19a6a..832f906 100644 --- a/docs/07_fetching_data.md +++ b/docs/07_fetching_data.md @@ -39,4 +39,4 @@ let user: DbUser = webfinger_resolve_actor("ruud@lemmy.world", &data).await?; # }).unwrap(); ``` -Note that webfinger queries don't contain a leading `@`. It is possible tha there are multiple Activitypub IDs returned for a single webfinger query in case of multiple actors with the same name (for example Lemmy permits group and person with the same name). In this case `webfinger_resolve_actor` automatically loops and returns the first item which can be dereferenced successfully to the given type. \ No newline at end of file +Note that webfinger queries don't contain a leading `@`. It is possible that there are multiple Activitypub IDs returned for a single webfinger query in case of multiple actors with the same name (for example Lemmy permits group and person with the same name). In this case `webfinger_resolve_actor` automatically loops and returns the first item which can be dereferenced successfully to the given type. diff --git a/src/http_signatures.rs b/src/http_signatures.rs index 95d0a12..aafce93 100644 --- a/src/http_signatures.rs +++ b/src/http_signatures.rs @@ -53,6 +53,10 @@ impl Keypair { } /// Generate a random asymmetric keypair for ActivityPub HTTP signatures. +/// +/// Note that this method is very slow in debug mode. To make it faster, follow +/// instructions in the RSA crate's readme. +/// pub fn generate_actor_keypair() -> Result { let mut rng = rand::thread_rng(); let rsa = RsaPrivateKey::new(&mut rng, 2048)?; diff --git a/src/traits/either.rs b/src/traits/either.rs new file mode 100644 index 0000000..9c3d238 --- /dev/null +++ b/src/traits/either.rs @@ -0,0 +1,124 @@ +use super::{Actor, Object}; +use crate::{config::Data, error::Error}; +use async_trait::async_trait; +use chrono::{DateTime, Utc}; +use either::Either; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; +use url::Url; + +#[doc(hidden)] +#[derive(Serialize, Deserialize)] +#[serde(untagged)] +pub enum UntaggedEither { + Left(L), + Right(R), +} + +impl Object for Either +where + T: Object + Object + Send, + R: Object + Object + Send, + ::Kind: Send + Sync, + ::Kind: Send + Sync, + D: Sync + Send + Clone, + E: From + Debug, +{ + type DataType = D; + type Kind = UntaggedEither; + type Error = E; + + fn last_refreshed_at(&self) -> Option> { + match self { + Either::Left(l) => l.last_refreshed_at(), + Either::Right(r) => r.last_refreshed_at(), + } + } + + async fn read_from_id( + object_id: Url, + data: &Data, + ) -> Result, Self::Error> { + let l = T::read_from_id(object_id.clone(), data).await?; + if let Some(l) = l { + return Ok(Some(Either::Left(l))); + } + let r = R::read_from_id(object_id.clone(), data).await?; + if let Some(r) = r { + return Ok(Some(Either::Right(r))); + } + Ok(None) + } + + async fn delete(self, data: &Data) -> Result<(), Self::Error> { + match self { + Either::Left(l) => l.delete(data).await, + Either::Right(r) => r.delete(data).await, + } + } + + async fn into_json(self, data: &Data) -> Result { + Ok(match self { + Either::Left(l) => UntaggedEither::Left(l.into_json(data).await?), + Either::Right(r) => UntaggedEither::Right(r.into_json(data).await?), + }) + } + + async fn verify( + json: &Self::Kind, + expected_domain: &Url, + data: &Data, + ) -> Result<(), Self::Error> { + match json { + UntaggedEither::Left(l) => T::verify(l, expected_domain, data).await?, + UntaggedEither::Right(r) => R::verify(r, expected_domain, data).await?, + }; + Ok(()) + } + + async fn from_json(json: Self::Kind, data: &Data) -> Result { + Ok(match json { + UntaggedEither::Left(l) => Either::Left(T::from_json(l, data).await?), + UntaggedEither::Right(r) => Either::Right(R::from_json(r, data).await?), + }) + } +} + +#[async_trait] +impl Actor for Either +where + T: Actor + Object + Object + Send + 'static, + R: Actor + Object + Object + Send + 'static, + ::Kind: Send + Sync, + ::Kind: Send + Sync, + D: Sync + Send + Clone, + E: From + Debug, +{ + fn id(&self) -> Url { + match self { + Either::Left(l) => l.id(), + Either::Right(r) => r.id(), + } + } + + fn public_key_pem(&self) -> &str { + match self { + Either::Left(l) => l.public_key_pem(), + Either::Right(r) => r.public_key_pem(), + } + } + + fn private_key_pem(&self) -> Option { + match self { + Either::Left(l) => l.private_key_pem(), + Either::Right(r) => r.private_key_pem(), + } + } + + fn inbox(&self) -> Url { + match self { + Either::Left(l) => l.inbox(), + Either::Right(r) => r.inbox(), + } + } +} diff --git a/src/traits.rs b/src/traits/mod.rs similarity index 99% rename from src/traits.rs rename to src/traits/mod.rs index de318ff..58fadcc 100644 --- a/src/traits.rs +++ b/src/traits/mod.rs @@ -7,6 +7,9 @@ use serde::Deserialize; use std::{fmt::Debug, ops::Deref}; use url::Url; +/// `Either` implementations for traits +pub mod either; + /// Helper for converting between database structs and federated protocol structs. /// /// ```