diff --git a/Cargo.toml b/Cargo.toml index 9bece25..7ea5c40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,7 @@ repository = "https://github.com/LemmyNet/activitypub-federation-rust" documentation = "https://docs.rs/activitypub_federation/" [features] -default = ["actix-web", "axum"] -actix-web = ["dep:actix-web"] +default = ["axum"] axum = ["dep:axum", "dep:tower", "dep:hyper", "dep:http-body-util"] diesel = ["dep:diesel"] @@ -82,9 +81,6 @@ diesel = { version = "2.2.1", features = [ futures = "0.3.30" moka = { version = "0.12.8", features = ["future"] } -# Actix-web -actix-web = { version = "4.8.0", default-features = false, optional = true } - # Axum axum = { version = "0.6.20", features = [ "json", diff --git a/src/actix_web/inbox.rs b/src/actix_web/inbox.rs deleted file mode 100644 index 7c10659..0000000 --- a/src/actix_web/inbox.rs +++ /dev/null @@ -1,182 +0,0 @@ -//! Handles incoming activities, verifying HTTP signatures and other checks - -use crate::{ - config::Data, - error::Error, - http_signatures::{verify_body_hash, verify_signature}, - parse_received_activity, - traits::{ActivityHandler, Actor, Object}, -}; -use actix_web::{web::Bytes, HttpRequest, HttpResponse}; -use serde::de::DeserializeOwned; -use tracing::debug; - -/// Handles incoming activities, verifying HTTP signatures and other checks -/// -/// After successful validation, activities are passed to respective [trait@ActivityHandler]. -pub async fn receive_activity( - request: HttpRequest, - body: Bytes, - data: &Data, -) -> Result::Error> -where - Activity: ActivityHandler + DeserializeOwned + Send + 'static, - ActorT: Object + Actor + Send + 'static, - for<'de2> ::Kind: serde::Deserialize<'de2>, - ::Error: From + From<::Error>, - ::Error: From, - Datatype: Clone, -{ - verify_body_hash(request.headers().get("Digest"), &body)?; - - let (activity, actor) = parse_received_activity::(&body, data).await?; - - verify_signature( - request.headers(), - request.method(), - request.uri(), - actor.public_key_pem(), - )?; - - debug!("Receiving activity {}", activity.id().to_string()); - activity.verify(data).await?; - activity.receive(data).await?; - Ok(HttpResponse::Ok().finish()) -} - -#[cfg(test)] -#[allow(clippy::unwrap_used)] -mod test { - use super::*; - use crate::{ - activity_sending::generate_request_headers, - config::FederationConfig, - fetch::object_id::ObjectId, - http_signatures::sign_request, - traits::tests::{DbConnection, DbUser, Follow, DB_USER_KEYPAIR}, - }; - use actix_web::test::TestRequest; - use reqwest::Client; - use reqwest_middleware::ClientWithMiddleware; - use serde_json::json; - use url::Url; - - #[tokio::test] - async fn test_receive_activity() { - let (body, incoming_request, config) = setup_receive_test().await; - receive_activity::( - incoming_request.to_http_request(), - body, - &config.to_request_data(), - ) - .await - .unwrap(); - } - - #[tokio::test] - async fn test_receive_activity_invalid_body_signature() { - let (_, incoming_request, config) = setup_receive_test().await; - let err = receive_activity::( - incoming_request.to_http_request(), - "invalid".into(), - &config.to_request_data(), - ) - .await - .err() - .unwrap(); - - assert_eq!(&err, &Error::ActivityBodyDigestInvalid) - } - - #[tokio::test] - async fn test_receive_activity_invalid_path() { - let (body, incoming_request, config) = setup_receive_test().await; - let incoming_request = incoming_request.uri("/wrong"); - let err = receive_activity::( - incoming_request.to_http_request(), - body, - &config.to_request_data(), - ) - .await - .err() - .unwrap(); - - assert_eq!(&err, &Error::ActivitySignatureInvalid) - } - - #[tokio::test] - async fn test_receive_unparseable_activity() { - let (_, _, config) = setup_receive_test().await; - - let actor = Url::parse("http://ds9.lemmy.ml/u/lemmy_alpha").unwrap(); - let id = "http://localhost:123/1"; - let activity = json!({ - "actor": actor.as_str(), - "to": ["https://www.w3.org/ns/activitystreams#Public"], - "object": "http://ds9.lemmy.ml/post/1", - "cc": ["http://enterprise.lemmy.ml/c/main"], - "type": "Delete", - "id": id - } - ); - let body: Bytes = serde_json::to_vec(&activity).unwrap().into(); - let incoming_request = construct_request(&body, &actor).await; - - // intentionally cause a parse error by using wrong type for deser - let res = receive_activity::( - incoming_request.to_http_request(), - body, - &config.to_request_data(), - ) - .await; - - match res { - Err(Error::ParseReceivedActivity(_, url)) => { - assert_eq!(id, url.expect("has url").as_str()); - } - _ => unreachable!(), - } - } - - async fn construct_request(body: &Bytes, actor: &Url) -> TestRequest { - let inbox = "https://example.com/inbox"; - let headers = generate_request_headers(&Url::parse(inbox).unwrap()); - let request_builder = ClientWithMiddleware::from(Client::default()) - .post(inbox) - .headers(headers); - let outgoing_request = sign_request( - request_builder, - actor, - body.clone(), - DB_USER_KEYPAIR.private_key().unwrap(), - false, - ) - .await - .unwrap(); - let mut incoming_request = TestRequest::post().uri(outgoing_request.url().path()); - for h in outgoing_request.headers() { - incoming_request = incoming_request.append_header(h); - } - incoming_request - } - - async fn setup_receive_test() -> (Bytes, TestRequest, FederationConfig) { - let activity = Follow { - actor: ObjectId::parse("http://localhost:123").unwrap(), - object: ObjectId::parse("http://localhost:124").unwrap(), - kind: Default::default(), - id: "http://localhost:123/1".try_into().unwrap(), - }; - let body: Bytes = serde_json::to_vec(&activity).unwrap().into(); - let incoming_request = construct_request(&body, activity.actor.inner()).await; - - let config = FederationConfig::builder() - .domain("localhost:8002") - .app_data(DbConnection) - .debug(true) - .build() - .await - .unwrap(); - (body, incoming_request, config) - } -} diff --git a/src/actix_web/middleware.rs b/src/actix_web/middleware.rs deleted file mode 100644 index afa0117..0000000 --- a/src/actix_web/middleware.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::config::{Data, FederationConfig, FederationMiddleware}; -use actix_web::{ - dev::{forward_ready, Payload, Service, ServiceRequest, ServiceResponse, Transform}, - Error, - FromRequest, - HttpMessage, - HttpRequest, -}; -use std::future::{ready, Ready}; - -impl Transform for FederationMiddleware -where - S: Service, Error = Error>, - S::Future: 'static, - B: 'static, - T: Clone + Sync + 'static, -{ - type Response = ServiceResponse; - type Error = Error; - type Transform = FederationService; - type InitError = (); - type Future = Ready>; - - fn new_transform(&self, service: S) -> Self::Future { - ready(Ok(FederationService { - service, - config: self.0.clone(), - })) - } -} - -/// Passes [FederationConfig] to HTTP handlers, converting it to [Data] in the process -#[doc(hidden)] -pub struct FederationService -where - S: Service, - S::Future: 'static, - T: Sync, -{ - service: S, - config: FederationConfig, -} - -impl Service for FederationService -where - S: Service, Error = Error>, - S::Future: 'static, - B: 'static, - T: Clone + Sync + 'static, -{ - type Response = ServiceResponse; - type Error = Error; - type Future = S::Future; - - forward_ready!(service); - - fn call(&self, req: ServiceRequest) -> Self::Future { - req.extensions_mut().insert(self.config.clone()); - - self.service.call(req) - } -} - -impl FromRequest for Data { - type Error = Error; - type Future = Ready>; - - fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future { - ready(match req.extensions().get::>() { - Some(c) => Ok(c.to_request_data()), - None => Err(actix_web::error::ErrorBadRequest( - "Missing extension, did you register FederationMiddleware?", - )), - }) - } -} diff --git a/src/actix_web/mod.rs b/src/actix_web/mod.rs deleted file mode 100644 index d3323ad..0000000 --- a/src/actix_web/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Utilities for using this library with actix-web framework - -pub mod inbox; -#[doc(hidden)] -pub mod middleware; - -use crate::{ - config::Data, - error::Error, - http_signatures::{self, verify_body_hash}, - traits::{Actor, Object}, -}; -use actix_web::{web::Bytes, HttpRequest}; -use serde::Deserialize; - -/// Checks whether the request is signed by an actor of type A, and returns -/// the actor in question if a valid signature is found. -pub async fn signing_actor( - request: &HttpRequest, - body: Option, - data: &Data<::DataType>, -) -> Result::Error> -where - A: Object + Actor, - ::Error: From, - for<'de2> ::Kind: Deserialize<'de2>, -{ - verify_body_hash(request.headers().get("Digest"), &body.unwrap_or_default())?; - - http_signatures::signing_actor(request.headers(), request.method(), request.uri(), data).await -} diff --git a/src/http_signatures.rs b/src/http_signatures.rs index aa526f9..8d69929 100644 --- a/src/http_signatures.rs +++ b/src/http_signatures.rs @@ -2,7 +2,6 @@ //! //! Signature creation and verification is handled internally in the library. See //! [send_activity](crate::activity_sending::SendActivityTask::sign_and_send) and -//! [receive_activity (actix-web)](crate::actix_web::inbox::receive_activity) / //! [receive_activity (axum)](crate::axum::inbox::receive_activity). use crate::{ diff --git a/src/lib.rs b/src/lib.rs index 0a44fc9..3f98fb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,8 +12,6 @@ pub mod activity_queue; pub mod activity_sending; -#[cfg(feature = "actix-web")] -pub mod actix_web; #[cfg(feature = "axum")] pub mod axum; pub mod config;