Add methods Object.id(), Object.deleted()
This commit is contained in:
parent
e18b13e253
commit
ffdb202a72
16 changed files with 358 additions and 228 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -4,7 +4,7 @@ version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "activitypub_federation"
|
name = "activitypub_federation"
|
||||||
version = "0.7.0-beta.3"
|
version = "0.7.0-beta.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitystreams-kinds",
|
"activitystreams-kinds",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,13 @@ impl Object for SearchableDbObjects {
|
||||||
type Kind = SearchableObjects;
|
type Kind = SearchableObjects;
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn id(&self) -> &Url {
|
||||||
|
match self {
|
||||||
|
SearchableDbObjects::User(p) => &p.federation_id,
|
||||||
|
SearchableDbObjects::Post(n) => &n.federation_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn read_from_id(
|
async fn read_from_id(
|
||||||
object_id: Url,
|
object_id: Url,
|
||||||
data: &Data<Self::DataType>,
|
data: &Data<Self::DataType>,
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,10 @@ impl Object for DbUser {
|
||||||
type Kind = Person;
|
type Kind = Person;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
|
fn id(&self) -> &Url {
|
||||||
|
self.ap_id.inner()
|
||||||
|
}
|
||||||
|
|
||||||
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
||||||
Some(self.last_refreshed_at)
|
Some(self.last_refreshed_at)
|
||||||
}
|
}
|
||||||
|
|
@ -122,10 +126,6 @@ impl Object for DbUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for DbUser {
|
impl Actor for DbUser {
|
||||||
fn id(&self) -> Url {
|
|
||||||
self.ap_id.inner().clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn public_key_pem(&self) -> &str {
|
fn public_key_pem(&self) -> &str {
|
||||||
&self.public_key
|
&self.public_key
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,10 @@ impl Object for DbPost {
|
||||||
type Kind = Note;
|
type Kind = Note;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
|
fn id(&self) -> &Url {
|
||||||
|
self.ap_id.inner()
|
||||||
|
}
|
||||||
|
|
||||||
async fn read_from_id(
|
async fn read_from_id(
|
||||||
_object_id: Url,
|
_object_id: Url,
|
||||||
_data: &Data<Self::DataType>,
|
_data: &Data<Self::DataType>,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use activitypub_federation::{
|
||||||
config::{Data, FederationConfig, FederationMiddleware},
|
config::{Data, FederationConfig, FederationMiddleware},
|
||||||
fetch::webfinger::{build_webfinger_response, extract_webfinger_name},
|
fetch::webfinger::{build_webfinger_response, extract_webfinger_name},
|
||||||
protocol::context::WithContext,
|
protocol::context::WithContext,
|
||||||
traits::{Actor, Object},
|
traits::Object,
|
||||||
FEDERATION_CONTENT_TYPE,
|
FEDERATION_CONTENT_TYPE,
|
||||||
};
|
};
|
||||||
use actix_web::{web, web::Bytes, App, HttpRequest, HttpResponse, HttpServer};
|
use actix_web::{web, web::Bytes, App, HttpRequest, HttpResponse, HttpServer};
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,10 @@ impl Object for DbUser {
|
||||||
type Kind = Person;
|
type Kind = Person;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
|
fn id(&self) -> &Url {
|
||||||
|
self.ap_id.inner()
|
||||||
|
}
|
||||||
|
|
||||||
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
||||||
Some(self.last_refreshed_at)
|
Some(self.last_refreshed_at)
|
||||||
}
|
}
|
||||||
|
|
@ -187,10 +191,6 @@ impl Object for DbUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for DbUser {
|
impl Actor for DbUser {
|
||||||
fn id(&self) -> Url {
|
|
||||||
self.ap_id.inner().clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn public_key_pem(&self) -> &str {
|
fn public_key_pem(&self) -> &str {
|
||||||
&self.public_key
|
&self.public_key
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,10 @@ impl Object for DbPost {
|
||||||
type Kind = Note;
|
type Kind = Note;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
|
fn id(&self) -> &Url {
|
||||||
|
self.ap_id.inner()
|
||||||
|
}
|
||||||
|
|
||||||
async fn read_from_id(
|
async fn read_from_id(
|
||||||
object_id: Url,
|
object_id: Url,
|
||||||
data: &Data<Self::DataType>,
|
data: &Data<Self::DataType>,
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ where
|
||||||
// PKey is internally like an Arc<>, so cloning is ok
|
// PKey is internally like an Arc<>, so cloning is ok
|
||||||
data.config
|
data.config
|
||||||
.actor_pkey_cache
|
.actor_pkey_cache
|
||||||
.try_get_with_by_ref(&actor_id, async {
|
.try_get_with_by_ref(actor_id, async {
|
||||||
let private_key_pem = actor.private_key_pem().ok_or_else(|| {
|
let private_key_pem = actor.private_key_pem().ok_or_else(|| {
|
||||||
Error::Other(format!(
|
Error::Other(format!(
|
||||||
"Actor {actor_id} does not contain a private key for signing"
|
"Actor {actor_id} does not contain a private key for signing"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ mod http_compat;
|
||||||
pub mod inbox;
|
pub mod inbox;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod middleware;
|
pub mod middleware;
|
||||||
|
pub mod response;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Data,
|
config::Data,
|
||||||
|
|
|
||||||
45
src/actix_web/response.rs
Normal file
45
src/actix_web/response.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
//! Generate HTTP responses for Activitypub ojects
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
protocol::{context::WithContext, tombstone::Tombstone},
|
||||||
|
FEDERATION_CONTENT_TYPE,
|
||||||
|
};
|
||||||
|
use actix_web::HttpResponse;
|
||||||
|
use http02::header::VARY;
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_json::Value;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
/// TODO
|
||||||
|
pub fn create_http_response<T: Serialize>(
|
||||||
|
data: T,
|
||||||
|
federation_context: &Value,
|
||||||
|
) -> Result<HttpResponse, serde_json::Error> {
|
||||||
|
let json = serde_json::to_string_pretty(&WithContext::new(data, federation_context.clone()))?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.content_type(FEDERATION_CONTENT_TYPE)
|
||||||
|
.insert_header((VARY, "Accept"))
|
||||||
|
.body(json))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn create_tombstone_response(
|
||||||
|
id: Url,
|
||||||
|
federation_context: &Value,
|
||||||
|
) -> Result<HttpResponse, serde_json::Error> {
|
||||||
|
let tombstone = Tombstone::new(id);
|
||||||
|
let json =
|
||||||
|
serde_json::to_string_pretty(&WithContext::new(tombstone, federation_context.clone()))?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Gone()
|
||||||
|
.content_type(FEDERATION_CONTENT_TYPE)
|
||||||
|
.status(actix_web::http::StatusCode::GONE)
|
||||||
|
.insert_header((VARY, "Accept"))
|
||||||
|
.body(json))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn redirect_remote_object(url: &Url) -> HttpResponse {
|
||||||
|
let mut res = HttpResponse::PermanentRedirect();
|
||||||
|
res.insert_header((actix_web::http::header::LOCATION, url.as_str()));
|
||||||
|
res.finish()
|
||||||
|
}
|
||||||
|
|
@ -258,7 +258,7 @@ impl<T: Clone> FederationConfigBuilder<T> {
|
||||||
|
|
||||||
let private_key =
|
let private_key =
|
||||||
RsaPrivateKey::from_pkcs8_pem(&private_key_pem).expect("Could not decode PEM data");
|
RsaPrivateKey::from_pkcs8_pem(&private_key_pem).expect("Could not decode PEM data");
|
||||||
self.signed_fetch_actor = Some(Some(Arc::new((actor.id(), private_key))));
|
self.signed_fetch_actor = Some(Some(Arc::new((actor.id().clone(), private_key))));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,6 @@
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod helpers;
|
pub mod helpers;
|
||||||
pub mod public_key;
|
pub mod public_key;
|
||||||
|
pub mod tombstone;
|
||||||
pub mod values;
|
pub mod values;
|
||||||
pub mod verification;
|
pub mod verification;
|
||||||
|
|
|
||||||
25
src/protocol/tombstone.rs
Normal file
25
src/protocol/tombstone.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
//! Tombstone is used to serve deleted objects
|
||||||
|
|
||||||
|
use crate::kinds::object::TombstoneType;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
/// For serving deleted objects
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Tombstone {
|
||||||
|
/// Id of the deleted object
|
||||||
|
pub id: Url,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub(crate) kind: TombstoneType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tombstone {
|
||||||
|
/// Create a new tombstone for the given object id
|
||||||
|
pub fn new(id: Url) -> Tombstone {
|
||||||
|
Tombstone {
|
||||||
|
id,
|
||||||
|
kind: TombstoneType::Tombstone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,14 @@ where
|
||||||
type Kind = UntaggedEither<T::Kind, R::Kind>;
|
type Kind = UntaggedEither<T::Kind, R::Kind>;
|
||||||
type Error = E;
|
type Error = E;
|
||||||
|
|
||||||
|
/// `id` field of the object
|
||||||
|
fn id(&self) -> &Url {
|
||||||
|
match self {
|
||||||
|
Either::Left(l) => l.id(),
|
||||||
|
Either::Right(r) => r.id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
||||||
match self {
|
match self {
|
||||||
Either::Left(l) => l.last_refreshed_at(),
|
Either::Left(l) => l.last_refreshed_at(),
|
||||||
|
|
@ -58,6 +66,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_deleted(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Either::Left(l) => l.is_deleted(),
|
||||||
|
Either::Right(r) => r.is_deleted(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn into_json(self, data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
|
async fn into_json(self, data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Either::Left(l) => UntaggedEither::Left(l.into_json(data).await?),
|
Either::Left(l) => UntaggedEither::Left(l.into_json(data).await?),
|
||||||
|
|
@ -95,13 +110,6 @@ where
|
||||||
D: Sync + Send + Clone,
|
D: Sync + Send + Clone,
|
||||||
E: From<Error> + Debug,
|
E: From<Error> + Debug,
|
||||||
{
|
{
|
||||||
fn id(&self) -> Url {
|
|
||||||
match self {
|
|
||||||
Either::Left(l) => l.id(),
|
|
||||||
Either::Right(r) => r.id(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn public_key_pem(&self) -> &str {
|
fn public_key_pem(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Either::Left(l) => l.public_key_pem(),
|
Either::Left(l) => l.public_key_pem(),
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use url::Url;
|
||||||
|
|
||||||
/// `Either` implementations for traits
|
/// `Either` implementations for traits
|
||||||
pub mod either;
|
pub mod either;
|
||||||
|
pub mod tests;
|
||||||
|
|
||||||
/// Helper for converting between database structs and federated protocol structs.
|
/// Helper for converting between database structs and federated protocol structs.
|
||||||
///
|
///
|
||||||
|
|
@ -52,6 +53,8 @@ pub mod either;
|
||||||
/// type Kind = Note;
|
/// type Kind = Note;
|
||||||
/// type Error = anyhow::Error;
|
/// type Error = anyhow::Error;
|
||||||
///
|
///
|
||||||
|
/// fn id(&self) -> &Url { self.ap_id.inner() }
|
||||||
|
///
|
||||||
/// async fn read_from_id(object_id: Url, data: &Data<Self::DataType>) -> Result<Option<Self>, Self::Error> {
|
/// async fn read_from_id(object_id: Url, data: &Data<Self::DataType>) -> Result<Option<Self>, Self::Error> {
|
||||||
/// // Attempt to read object from local database. Return Ok(None) if not found.
|
/// // Attempt to read object from local database. Return Ok(None) if not found.
|
||||||
/// let post: Option<DbPost> = data.read_post_from_json_id(object_id).await?;
|
/// let post: Option<DbPost> = data.read_post_from_json_id(object_id).await?;
|
||||||
|
|
@ -106,6 +109,9 @@ pub trait Object: Sized + Debug {
|
||||||
/// Error type returned by handler methods
|
/// Error type returned by handler methods
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
|
/// `id` field of the object
|
||||||
|
fn id(&self) -> &Url;
|
||||||
|
|
||||||
/// Returns the last time this object was updated.
|
/// Returns the last time this object was updated.
|
||||||
///
|
///
|
||||||
/// If this returns `Some` and the value is too long ago, the object is refetched from the
|
/// If this returns `Some` and the value is too long ago, the object is refetched from the
|
||||||
|
|
@ -134,6 +140,11 @@ pub trait Object: Sized + Debug {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the object was deleted
|
||||||
|
fn is_deleted(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert database type to Activitypub type.
|
/// Convert database type to Activitypub type.
|
||||||
///
|
///
|
||||||
/// Called when a local object gets fetched by another instance over HTTP, or when an object
|
/// Called when a local object gets fetched by another instance over HTTP, or when an object
|
||||||
|
|
@ -159,6 +170,37 @@ pub trait Object: Sized + Debug {
|
||||||
/// should write the received object to database. Note that there is no distinction between
|
/// should write the received object to database. Note that there is no distinction between
|
||||||
/// create and update, so an `upsert` operation should be used.
|
/// create and update, so an `upsert` operation should be used.
|
||||||
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>;
|
||||||
|
|
||||||
|
/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
|
||||||
|
/// headers.
|
||||||
|
///
|
||||||
|
/// actix-web doesn't allow pretty-print for json so we need to do this manually.
|
||||||
|
#[cfg(feature = "actix-web")]
|
||||||
|
async fn http_response(
|
||||||
|
self,
|
||||||
|
federation_context: &serde_json::Value,
|
||||||
|
data: &Data<Self::DataType>,
|
||||||
|
) -> Result<actix_web::HttpResponse, Self::Error>
|
||||||
|
where
|
||||||
|
Self::Error: From<serde_json::Error>,
|
||||||
|
Self::Kind: serde::Serialize + Send,
|
||||||
|
{
|
||||||
|
use crate::actix_web::response::{
|
||||||
|
create_http_response,
|
||||||
|
create_tombstone_response,
|
||||||
|
redirect_remote_object,
|
||||||
|
};
|
||||||
|
let id = self.id();
|
||||||
|
let res = if data.config.is_local_url(id) {
|
||||||
|
redirect_remote_object(id)
|
||||||
|
} else if !self.is_deleted() {
|
||||||
|
let json = self.into_json(data).await?;
|
||||||
|
create_http_response(json, federation_context)?
|
||||||
|
} else {
|
||||||
|
create_tombstone_response(id.clone(), federation_context)?
|
||||||
|
};
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handler for receiving incoming activities.
|
/// Handler for receiving incoming activities.
|
||||||
|
|
@ -234,9 +276,6 @@ pub trait ActivityHandler {
|
||||||
|
|
||||||
/// Trait to allow retrieving common Actor data.
|
/// Trait to allow retrieving common Actor data.
|
||||||
pub trait Actor: Object + Send + 'static {
|
pub trait Actor: Object + Send + 'static {
|
||||||
/// `id` field of the actor
|
|
||||||
fn id(&self) -> Url;
|
|
||||||
|
|
||||||
/// The actor's public key for verifying signatures of incoming activities.
|
/// The actor's public key for verifying signatures of incoming activities.
|
||||||
///
|
///
|
||||||
/// Use [generate_actor_keypair](crate::http_signatures::generate_actor_keypair) to create the
|
/// Use [generate_actor_keypair](crate::http_signatures::generate_actor_keypair) to create the
|
||||||
|
|
@ -254,7 +293,7 @@ pub trait Actor: Object + Send + 'static {
|
||||||
|
|
||||||
/// Generates a public key struct for use in the actor json representation
|
/// Generates a public key struct for use in the actor json representation
|
||||||
fn public_key(&self) -> PublicKey {
|
fn public_key(&self) -> PublicKey {
|
||||||
PublicKey::new(self.id(), self.public_key_pem().to_string())
|
PublicKey::new(self.id().clone(), self.public_key_pem().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The actor's shared inbox, if any
|
/// The actor's shared inbox, if any
|
||||||
|
|
@ -334,208 +373,3 @@ pub trait Collection: Sized {
|
||||||
data: &Data<Self::DataType>,
|
data: &Data<Self::DataType>,
|
||||||
) -> Result<Self, Self::Error>;
|
) -> Result<Self, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Some impls of these traits for use in tests. Dont use this from external crates.
|
|
||||||
///
|
|
||||||
/// TODO: Should be using `cfg[doctest]` but blocked by <https://github.com/rust-lang/rust/issues/67295>
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
pub mod tests {
|
|
||||||
use super::{async_trait, ActivityHandler, Actor, Data, Debug, Object, PublicKey, Url};
|
|
||||||
use crate::{
|
|
||||||
error::Error,
|
|
||||||
fetch::object_id::ObjectId,
|
|
||||||
http_signatures::{generate_actor_keypair, Keypair},
|
|
||||||
protocol::verification::verify_domains_match,
|
|
||||||
};
|
|
||||||
use activitystreams_kinds::{activity::FollowType, actor::PersonType};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct DbConnection;
|
|
||||||
|
|
||||||
impl DbConnection {
|
|
||||||
pub async fn read_post_from_json_id<T>(&self, _: Url) -> Result<Option<T>, Error> {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
pub async fn read_local_user(&self, _: &str) -> Result<DbUser, Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
pub async fn upsert<T>(&self, _: &T) -> Result<(), Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub async fn add_follower(&self, _: DbUser, _: DbUser) -> Result<(), Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Person {
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub kind: PersonType,
|
|
||||||
pub preferred_username: String,
|
|
||||||
pub id: ObjectId<DbUser>,
|
|
||||||
pub inbox: Url,
|
|
||||||
pub public_key: PublicKey,
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct DbUser {
|
|
||||||
pub name: String,
|
|
||||||
pub federation_id: Url,
|
|
||||||
pub inbox: Url,
|
|
||||||
pub public_key: String,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
private_key: Option<String>,
|
|
||||||
pub followers: Vec<Url>,
|
|
||||||
pub local: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub static DB_USER_KEYPAIR: LazyLock<Keypair> =
|
|
||||||
LazyLock::new(|| generate_actor_keypair().unwrap());
|
|
||||||
|
|
||||||
pub static DB_USER: LazyLock<DbUser> = LazyLock::new(|| DbUser {
|
|
||||||
name: String::new(),
|
|
||||||
federation_id: "https://localhost/123".parse().unwrap(),
|
|
||||||
inbox: "https://localhost/123/inbox".parse().unwrap(),
|
|
||||||
public_key: DB_USER_KEYPAIR.public_key.clone(),
|
|
||||||
private_key: Some(DB_USER_KEYPAIR.private_key.clone()),
|
|
||||||
followers: vec![],
|
|
||||||
local: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl Object for DbUser {
|
|
||||||
type DataType = DbConnection;
|
|
||||||
type Kind = Person;
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
async fn read_from_id(
|
|
||||||
_object_id: Url,
|
|
||||||
_data: &Data<Self::DataType>,
|
|
||||||
) -> Result<Option<Self>, Self::Error> {
|
|
||||||
Ok(Some(DB_USER.clone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
|
|
||||||
Ok(Person {
|
|
||||||
preferred_username: self.name.clone(),
|
|
||||||
kind: Default::default(),
|
|
||||||
id: self.federation_id.clone().into(),
|
|
||||||
inbox: self.inbox.clone(),
|
|
||||||
public_key: self.public_key(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn verify(
|
|
||||||
json: &Self::Kind,
|
|
||||||
expected_domain: &Url,
|
|
||||||
_data: &Data<Self::DataType>,
|
|
||||||
) -> Result<(), Self::Error> {
|
|
||||||
verify_domains_match(json.id.inner(), expected_domain)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn from_json(
|
|
||||||
json: Self::Kind,
|
|
||||||
_data: &Data<Self::DataType>,
|
|
||||||
) -> Result<Self, Self::Error> {
|
|
||||||
Ok(DbUser {
|
|
||||||
name: json.preferred_username,
|
|
||||||
federation_id: json.id.into(),
|
|
||||||
inbox: json.inbox,
|
|
||||||
public_key: json.public_key.public_key_pem,
|
|
||||||
private_key: None,
|
|
||||||
followers: vec![],
|
|
||||||
local: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for DbUser {
|
|
||||||
fn id(&self) -> Url {
|
|
||||||
self.federation_id.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn public_key_pem(&self) -> &str {
|
|
||||||
&self.public_key
|
|
||||||
}
|
|
||||||
|
|
||||||
fn private_key_pem(&self) -> Option<String> {
|
|
||||||
self.private_key.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn inbox(&self) -> Url {
|
|
||||||
self.inbox.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Follow {
|
|
||||||
pub actor: ObjectId<DbUser>,
|
|
||||||
pub object: ObjectId<DbUser>,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub kind: FollowType,
|
|
||||||
pub id: Url,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl ActivityHandler for Follow {
|
|
||||||
type DataType = DbConnection;
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn id(&self) -> &Url {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn actor(&self) -> &Url {
|
|
||||||
self.actor.inner()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn verify(&self, _: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn receive(self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Note {}
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct DbPost {}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl Object for DbPost {
|
|
||||||
type DataType = DbConnection;
|
|
||||||
type Kind = Note;
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
async fn read_from_id(
|
|
||||||
_: Url,
|
|
||||||
_: &Data<Self::DataType>,
|
|
||||||
) -> Result<Option<Self>, Self::Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn into_json(self, _: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn verify(
|
|
||||||
_: &Self::Kind,
|
|
||||||
_: &Url,
|
|
||||||
_: &Data<Self::DataType>,
|
|
||||||
) -> Result<(), Self::Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn from_json(_: Self::Kind, _: &Data<Self::DataType>) -> Result<Self, Self::Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
201
src/traits/tests.rs
Normal file
201
src/traits/tests.rs
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
#![doc(hidden)]
|
||||||
|
#![allow(clippy::unwrap_used)]
|
||||||
|
//! Some impls of these traits for use in tests. Dont use this from external crates.
|
||||||
|
//!
|
||||||
|
//! TODO: Should be using `cfg[doctest]` but blocked by <https://github.com/rust-lang/rust/issues/67295>
|
||||||
|
|
||||||
|
use super::{async_trait, ActivityHandler, Actor, Data, Debug, Object, PublicKey, Url};
|
||||||
|
use crate::{
|
||||||
|
error::Error,
|
||||||
|
fetch::object_id::ObjectId,
|
||||||
|
http_signatures::{generate_actor_keypair, Keypair},
|
||||||
|
protocol::verification::verify_domains_match,
|
||||||
|
};
|
||||||
|
use activitystreams_kinds::{activity::FollowType, actor::PersonType};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DbConnection;
|
||||||
|
|
||||||
|
impl DbConnection {
|
||||||
|
pub async fn read_post_from_json_id<T>(&self, _: Url) -> Result<Option<T>, Error> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
pub async fn read_local_user(&self, _: &str) -> Result<DbUser, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
pub async fn upsert<T>(&self, _: &T) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub async fn add_follower(&self, _: DbUser, _: DbUser) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Person {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub kind: PersonType,
|
||||||
|
pub preferred_username: String,
|
||||||
|
pub id: ObjectId<DbUser>,
|
||||||
|
pub inbox: Url,
|
||||||
|
pub public_key: PublicKey,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DbUser {
|
||||||
|
pub name: String,
|
||||||
|
pub federation_id: Url,
|
||||||
|
pub inbox: Url,
|
||||||
|
pub public_key: String,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
private_key: Option<String>,
|
||||||
|
pub followers: Vec<Url>,
|
||||||
|
pub local: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static DB_USER_KEYPAIR: LazyLock<Keypair> = LazyLock::new(|| generate_actor_keypair().unwrap());
|
||||||
|
|
||||||
|
pub static DB_USER: LazyLock<DbUser> = LazyLock::new(|| DbUser {
|
||||||
|
name: String::new(),
|
||||||
|
federation_id: "https://localhost/123".parse().unwrap(),
|
||||||
|
inbox: "https://localhost/123/inbox".parse().unwrap(),
|
||||||
|
public_key: DB_USER_KEYPAIR.public_key.clone(),
|
||||||
|
private_key: Some(DB_USER_KEYPAIR.private_key.clone()),
|
||||||
|
followers: vec![],
|
||||||
|
local: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Object for DbUser {
|
||||||
|
type DataType = DbConnection;
|
||||||
|
type Kind = Person;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn id(&self) -> &Url {
|
||||||
|
&self.federation_id
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_from_id(
|
||||||
|
_object_id: Url,
|
||||||
|
_data: &Data<Self::DataType>,
|
||||||
|
) -> Result<Option<Self>, Self::Error> {
|
||||||
|
Ok(Some(DB_USER.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
|
||||||
|
Ok(Person {
|
||||||
|
preferred_username: self.name.clone(),
|
||||||
|
kind: Default::default(),
|
||||||
|
id: self.federation_id.clone().into(),
|
||||||
|
inbox: self.inbox.clone(),
|
||||||
|
public_key: self.public_key(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify(
|
||||||
|
json: &Self::Kind,
|
||||||
|
expected_domain: &Url,
|
||||||
|
_data: &Data<Self::DataType>,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
verify_domains_match(json.id.inner(), expected_domain)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn from_json(
|
||||||
|
json: Self::Kind,
|
||||||
|
_data: &Data<Self::DataType>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
Ok(DbUser {
|
||||||
|
name: json.preferred_username,
|
||||||
|
federation_id: json.id.into(),
|
||||||
|
inbox: json.inbox,
|
||||||
|
public_key: json.public_key.public_key_pem,
|
||||||
|
private_key: None,
|
||||||
|
followers: vec![],
|
||||||
|
local: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Actor for DbUser {
|
||||||
|
fn public_key_pem(&self) -> &str {
|
||||||
|
&self.public_key
|
||||||
|
}
|
||||||
|
|
||||||
|
fn private_key_pem(&self) -> Option<String> {
|
||||||
|
self.private_key.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inbox(&self) -> Url {
|
||||||
|
self.inbox.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Follow {
|
||||||
|
pub actor: ObjectId<DbUser>,
|
||||||
|
pub object: ObjectId<DbUser>,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub kind: FollowType,
|
||||||
|
pub id: Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl ActivityHandler for Follow {
|
||||||
|
type DataType = DbConnection;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn id(&self) -> &Url {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn actor(&self) -> &Url {
|
||||||
|
self.actor.inner()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify(&self, _: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn receive(self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Note {}
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DbPost {
|
||||||
|
pub federation_id: Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Object for DbPost {
|
||||||
|
type DataType = DbConnection;
|
||||||
|
type Kind = Note;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn id(&self) -> &Url {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_from_id(_: Url, _: &Data<Self::DataType>) -> Result<Option<Self>, Self::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn into_json(self, _: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify(_: &Self::Kind, _: &Url, _: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn from_json(_: Self::Kind, _: &Data<Self::DataType>) -> Result<Self, Self::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue