From 487c9883770fe0cda778bb3fb0916686ebfc4291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kangwook=20Lee=20=28=EC=9D=B4=EA=B0=95=EC=9A=B1=29?= Date: Wed, 11 Sep 2024 21:47:13 +0900 Subject: [PATCH 01/13] Upgrade axum and http (#123) * Upgrade axum and http * Fix formatting * use expect --------- Co-authored-by: Felix Ableitner --- Cargo.toml | 29 +++++++++---------------- docs/06_http_endpoints_axum.md | 9 ++++---- examples/live_federation/http.rs | 2 +- examples/live_federation/main.rs | 5 ++--- examples/local_federation/axum/http.rs | 11 +++++++--- src/activity_queue.rs | 4 ++-- src/activity_sending.rs | 4 ++-- src/actix_web/http_compat.rs | 30 ++++++++++++++++++++++++++ src/actix_web/inbox.rs | 29 ++++++++++++++++++------- src/actix_web/mod.rs | 12 +++++++++-- src/axum/inbox.rs | 12 ++++------- 11 files changed, 94 insertions(+), 53 deletions(-) create mode 100644 src/actix_web/http_compat.rs diff --git a/Cargo.toml b/Cargo.toml index 9bece25..2dd99ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,8 @@ documentation = "https://docs.rs/activitypub_federation/" [features] default = ["actix-web", "axum"] -actix-web = ["dep:actix-web"] -axum = ["dep:axum", "dep:tower", "dep:hyper", "dep:http-body-util"] +actix-web = ["dep:actix-web", "dep:http02"] +axum = ["dep:axum", "dep:tower"] diesel = ["dep:diesel"] [lints.rust] @@ -37,18 +37,18 @@ serde = { version = "1.0.204", features = ["derive"] } async-trait = "0.1.81" url = { version = "2.5.2", features = ["serde"] } serde_json = { version = "1.0.120", features = ["preserve_order"] } -reqwest = { version = "0.11.27", default-features = false, features = [ +reqwest = { version = "0.12.5", default-features = false, features = [ "json", "stream", "rustls-tls", ] } -reqwest-middleware = "0.2.5" +reqwest-middleware = "0.3.2" tracing = "0.1.40" base64 = "0.22.1" rand = "0.8.5" rsa = "0.9.6" once_cell = "1.19.0" -http = "0.2.12" +http = "1.1.0" sha2 = { version = "0.10.8", features = ["oid"] } thiserror = "1.0.62" derive_builder = "0.20.0" @@ -56,7 +56,7 @@ itertools = "0.13.0" dyn-clone = "1.0.17" enum_delegate = "0.2.0" httpdate = "1.0.3" -http-signature-normalization-reqwest = { version = "0.10.0", default-features = false, features = [ +http-signature-normalization-reqwest = { version = "0.12.0", default-features = false, features = [ "sha-2", "middleware", "default-spawner", @@ -84,26 +84,17 @@ moka = { version = "0.12.8", features = ["future"] } # Actix-web actix-web = { version = "4.8.0", default-features = false, optional = true } +http02 = { package = "http", version = "0.2.12", optional = true } # Axum -axum = { version = "0.6.20", features = [ - "json", - "headers", -], default-features = false, optional = true } +axum = { version = "0.7.5", features = ["json"], default-features = false, optional = true } tower = { version = "0.4.13", optional = true } -hyper = { version = "0.14", optional = true } -http-body-util = { version = "0.1.2", optional = true } [dev-dependencies] anyhow = "1.0.86" +axum = { version = "0.7.5", features = ["macros"] } +axum-extra = { version = "0.9.3", features = ["typed-header"] } env_logger = "0.11.3" -tower-http = { version = "0.5.2", features = ["map-request-body", "util"] } -axum = { version = "0.6.20", features = [ - "http1", - "tokio", - "query", -], default-features = false } -axum-macros = "0.3.8" tokio = { version = "1.38.0", features = ["full"] } [profile.dev] diff --git a/docs/06_http_endpoints_axum.md b/docs/06_http_endpoints_axum.md index 3a33410..fc8089a 100644 --- a/docs/06_http_endpoints_axum.md +++ b/docs/06_http_endpoints_axum.md @@ -15,9 +15,9 @@ The next step is to allow other servers to fetch our actors and objects. For thi # use activitypub_federation::config::FederationMiddleware; # use axum::routing::get; # use crate::activitypub_federation::traits::Object; -# use axum::headers::ContentType; +# use axum_extra::headers::ContentType; # use activitypub_federation::FEDERATION_CONTENT_TYPE; -# use axum::TypedHeader; +# use axum_extra::TypedHeader; # use axum::response::IntoResponse; # use http::HeaderMap; # async fn generate_user_html(_: String, _: Data) -> axum::response::Response { todo!() } @@ -34,10 +34,9 @@ async fn main() -> Result<(), Error> { .layer(FederationMiddleware::new(data)); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + let listener = tokio::net::TcpListener::bind(addr).await?; tracing::debug!("listening on {}", addr); - axum::Server::bind(&addr) - .serve(app.into_make_service()) - .await?; + axum::serve(listener, app.into_make_service()).await?; Ok(()) } diff --git a/examples/live_federation/http.rs b/examples/live_federation/http.rs index e0d2869..5cf8545 100644 --- a/examples/live_federation/http.rs +++ b/examples/live_federation/http.rs @@ -14,11 +14,11 @@ use activitypub_federation::{ traits::Object, }; use axum::{ + debug_handler, extract::{Path, Query}, response::{IntoResponse, Response}, Json, }; -use axum_macros::debug_handler; use http::StatusCode; use serde::Deserialize; diff --git a/examples/live_federation/main.rs b/examples/live_federation/main.rs index 3fa0b18..dc593b1 100644 --- a/examples/live_federation/main.rs +++ b/examples/live_federation/main.rs @@ -64,9 +64,8 @@ async fn main() -> Result<(), Error> { .to_socket_addrs()? .next() .expect("Failed to lookup domain name"); - axum::Server::bind(&addr) - .serve(app.into_make_service()) - .await?; + let listener = tokio::net::TcpListener::bind(addr).await?; + axum::serve(listener, app.into_make_service()).await?; Ok(()) } diff --git a/examples/local_federation/axum/http.rs b/examples/local_federation/axum/http.rs index f17ea4a..dd9d002 100644 --- a/examples/local_federation/axum/http.rs +++ b/examples/local_federation/axum/http.rs @@ -14,13 +14,13 @@ use activitypub_federation::{ traits::Object, }; use axum::{ + debug_handler, extract::{Path, Query}, response::IntoResponse, routing::{get, post}, Json, Router, }; -use axum_macros::debug_handler; use serde::Deserialize; use std::net::ToSocketAddrs; use tracing::info; @@ -39,9 +39,14 @@ pub fn listen(config: &FederationConfig) -> Result<(), Error> { .to_socket_addrs()? .next() .expect("Failed to lookup domain name"); - let server = axum::Server::bind(&addr).serve(app.into_make_service()); + let fut = async move { + let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + axum::serve(listener, app.into_make_service()) + .await + .unwrap(); + }; - tokio::spawn(server); + tokio::spawn(fut); Ok(()) } diff --git a/src/activity_queue.rs b/src/activity_queue.rs index 20852bd..c666e7a 100644 --- a/src/activity_queue.rs +++ b/src/activity_queue.rs @@ -451,8 +451,8 @@ mod tests { .route("/", post(dodgy_handler)) .with_state(state); - axum::Server::bind(&"0.0.0.0:8002".parse().unwrap()) - .serve(app.into_make_service()) + let listener = tokio::net::TcpListener::bind("0.0.0.0:8002").await.unwrap(); + axum::serve(listener, app.into_make_service()) .await .unwrap(); } diff --git a/src/activity_sending.rs b/src/activity_sending.rs index f9023ce..45efeb8 100644 --- a/src/activity_sending.rs +++ b/src/activity_sending.rs @@ -251,8 +251,8 @@ mod tests { .route("/", post(dodgy_handler)) .with_state(state); - axum::Server::bind(&"0.0.0.0:8001".parse().unwrap()) - .serve(app.into_make_service()) + let listener = tokio::net::TcpListener::bind("0.0.0.0:8001").await.unwrap(); + axum::serve(listener, app.into_make_service()) .await .unwrap(); } diff --git a/src/actix_web/http_compat.rs b/src/actix_web/http_compat.rs new file mode 100644 index 0000000..b605444 --- /dev/null +++ b/src/actix_web/http_compat.rs @@ -0,0 +1,30 @@ +//! Remove these conversion helpers after actix-web upgrades to http 1.0 + +use std::str::FromStr; + +pub fn header_value(v: &http02::HeaderValue) -> http::HeaderValue { + http::HeaderValue::from_bytes(v.as_bytes()).expect("can convert http types") +} + +pub fn header_map<'a, H>(m: H) -> http::HeaderMap +where + H: IntoIterator, +{ + let mut new_map = http::HeaderMap::new(); + for (n, v) in m { + new_map.insert( + http::HeaderName::from_lowercase(n.as_str().as_bytes()) + .expect("can convert http types"), + header_value(v), + ); + } + new_map +} + +pub fn method(m: &http02::Method) -> http::Method { + http::Method::from_bytes(m.as_str().as_bytes()).expect("can convert http types") +} + +pub fn uri(m: &http02::Uri) -> http::Uri { + http::Uri::from_str(&m.to_string()).expect("can convert http types") +} diff --git a/src/actix_web/inbox.rs b/src/actix_web/inbox.rs index 7c10659..9bce475 100644 --- a/src/actix_web/inbox.rs +++ b/src/actix_web/inbox.rs @@ -1,5 +1,6 @@ //! Handles incoming activities, verifying HTTP signatures and other checks +use super::http_compat; use crate::{ config::Data, error::Error, @@ -27,16 +28,18 @@ where ::Error: From, Datatype: Clone, { - verify_body_hash(request.headers().get("Digest"), &body)?; + let digest_header = request + .headers() + .get("Digest") + .map(http_compat::header_value); + verify_body_hash(digest_header.as_ref(), &body)?; let (activity, actor) = parse_received_activity::(&body, data).await?; - verify_signature( - request.headers(), - request.method(), - request.uri(), - actor.public_key_pem(), - )?; + let headers = http_compat::header_map(request.headers()); + let method = http_compat::method(request.method()); + let uri = http_compat::uri(request.uri()); + verify_signature(&headers, &method, &uri, actor.public_key_pem())?; debug!("Receiving activity {}", activity.id().to_string()); activity.verify(data).await?; @@ -61,6 +64,16 @@ mod test { use serde_json::json; use url::Url; + /// Remove this conversion helper after actix-web upgrades to http 1.0 + fn header_pair( + p: (&http::HeaderName, &http::HeaderValue), + ) -> (http02::HeaderName, http02::HeaderValue) { + ( + http02::HeaderName::from_lowercase(p.0.as_str().as_bytes()).unwrap(), + http02::HeaderValue::from_bytes(p.1.as_bytes()).unwrap(), + ) + } + #[tokio::test] async fn test_receive_activity() { let (body, incoming_request, config) = setup_receive_test().await; @@ -155,7 +168,7 @@ mod test { .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 = incoming_request.append_header(header_pair(h)); } incoming_request } diff --git a/src/actix_web/mod.rs b/src/actix_web/mod.rs index d3323ad..7683d4b 100644 --- a/src/actix_web/mod.rs +++ b/src/actix_web/mod.rs @@ -1,5 +1,6 @@ //! Utilities for using this library with actix-web framework +mod http_compat; pub mod inbox; #[doc(hidden)] pub mod middleware; @@ -25,7 +26,14 @@ where ::Error: From, for<'de2> ::Kind: Deserialize<'de2>, { - verify_body_hash(request.headers().get("Digest"), &body.unwrap_or_default())?; + let digest_header = request + .headers() + .get("Digest") + .map(http_compat::header_value); + verify_body_hash(digest_header.as_ref(), &body.unwrap_or_default())?; - http_signatures::signing_actor(request.headers(), request.method(), request.uri(), data).await + let headers = http_compat::header_map(request.headers()); + let method = http_compat::method(request.method()); + let uri = http_compat::uri(request.uri()); + http_signatures::signing_actor(&headers, &method, &uri, data).await } diff --git a/src/axum/inbox.rs b/src/axum/inbox.rs index 5bb147a..1767c10 100644 --- a/src/axum/inbox.rs +++ b/src/axum/inbox.rs @@ -11,7 +11,7 @@ use crate::{ }; use axum::{ async_trait, - body::{Bytes, HttpBody}, + body::Body, extract::FromRequest, http::{Request, StatusCode}, response::{IntoResponse, Response}, @@ -59,21 +59,17 @@ pub struct ActivityData { } #[async_trait] -impl FromRequest for ActivityData +impl FromRequest for ActivityData where - Bytes: FromRequest, - B: HttpBody + Send + 'static, S: Send + Sync, - ::Error: std::fmt::Display, - ::Data: Send, { type Rejection = Response; - async fn from_request(req: Request, _state: &S) -> Result { + async fn from_request(req: Request, _state: &S) -> Result { let (parts, body) = req.into_parts(); // this wont work if the body is an long running stream - let bytes = hyper::body::to_bytes(body) + let bytes = axum::body::to_bytes(body, usize::MAX) .await .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response())?; From 2079b82de76f40be6957e82674d39b2319256e21 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Wed, 11 Sep 2024 15:00:47 +0200 Subject: [PATCH 02/13] Version 0.6.0-alpha1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2dd99ea..3acfde5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "activitypub_federation" -version = "0.5.8" +version = "0.6.0-alpha1" edition = "2021" description = "High-level Activitypub framework" keywords = ["activitypub", "activitystreams", "federation", "fediverse"] From 027b386514e68371e5ad3075a8d2c2986d44cf77 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Fri, 13 Sep 2024 11:21:26 +0200 Subject: [PATCH 03/13] Avoid stack overflow when fetching deeply nested comments (#124) --- src/fetch/object_id.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/fetch/object_id.rs b/src/fetch/object_id.rs index ce52c43..3f0676b 100644 --- a/src/fetch/object_id.rs +++ b/src/fetch/object_id.rs @@ -146,6 +146,10 @@ where Object::read_from_id(*id, data).await } + /// Fetch object from origin instance over HTTP, then verify and parse it. + /// + /// Uses Box::pin to wrap futures to reduce stack size and avoid stack overflow when + /// when fetching objects recursively. async fn dereference_from_http( &self, data: &Data<::DataType>, @@ -154,7 +158,7 @@ where where ::Error: From, { - let res = fetch_object_http(&self.0, data).await; + let res = Box::pin(fetch_object_http(&self.0, data)).await; if let Err(Error::ObjectDeleted(url)) = res { if let Some(db_object) = db_object { @@ -166,8 +170,8 @@ where let res = res?; let redirect_url = &res.url; - Kind::verify(&res.object, redirect_url, data).await?; - Kind::from_json(res.object, data).await + Box::pin(Kind::verify(&res.object, redirect_url, data)).await?; + Box::pin(Kind::from_json(res.object, data)).await } /// Returns true if the object's domain matches the one defined in [[FederationConfig.domain]]. From 1126603b613b21defb701166cbe8922527020f5e Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Fri, 13 Sep 2024 11:22:39 +0200 Subject: [PATCH 04/13] Version 0.6.0-alpha2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3acfde5..3e2d57e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "activitypub_federation" -version = "0.6.0-alpha1" +version = "0.6.0-alpha2" edition = "2021" description = "High-level Activitypub framework" keywords = ["activitypub", "activitystreams", "federation", "fediverse"] From a35c8cbea506e68229e7da078fc76d4b7ba93775 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Fri, 13 Sep 2024 16:09:04 +0200 Subject: [PATCH 05/13] If id of fetched object doesnt match url, refetch it (#126) --- src/fetch/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/fetch/mod.rs b/src/fetch/mod.rs index f078cf6..e36e8b2 100644 --- a/src/fetch/mod.rs +++ b/src/fetch/mod.rs @@ -73,6 +73,14 @@ pub async fn fetch_object_http( // Ensure id field matches final url after redirect if res.object_id.as_ref() != Some(&res.url) { + if let Some(res_object_id) = res.object_id { + // If id is different but still on the same domain, attempt to request object + // again from url in id field. + if res_object_id.domain() == res.url.domain() { + return Box::pin(fetch_object_http(&res_object_id, data)).await; + } + } + // Failed to fetch the object from its specified id return Err(Error::FetchWrongId(res.url)); } From df8876c096c9aaacf398ed7518dc83215796660b Mon Sep 17 00:00:00 2001 From: Nutomic Date: Thu, 19 Sep 2024 12:22:48 +0200 Subject: [PATCH 06/13] Log warning if activity sending is slow (#127) --- src/activity_sending.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/activity_sending.rs b/src/activity_sending.rs index 45efeb8..1c84757 100644 --- a/src/activity_sending.rs +++ b/src/activity_sending.rs @@ -24,9 +24,9 @@ use rsa::{pkcs8::DecodePrivateKey, RsaPrivateKey}; use serde::Serialize; use std::{ fmt::{Debug, Display}, - time::{Duration, SystemTime}, + time::{Duration, Instant, SystemTime}, }; -use tracing::debug; +use tracing::{debug, warn}; use url::Url; #[derive(Clone, Debug)] @@ -92,7 +92,17 @@ impl SendActivityTask { self.http_signature_compat, ) .await?; + + // Send the activity, and log a warning if its too slow. + let now = Instant::now(); let response = client.execute(request).await?; + let elapsed = now.elapsed().as_secs(); + if elapsed > 10 { + warn!( + "Sending activity {} to {} took {}s", + self.activity_id, self.inbox, elapsed + ); + } self.handle_response(response).await } From 6dfd30a8ab6ca25233f0fe34d129eb54ec27becd Mon Sep 17 00:00:00 2001 From: MrKaplan <169855803+MrKaplan-lw@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:02:04 +0200 Subject: [PATCH 07/13] Add test case for http fetch limit fixed in #97 (#128) --- src/fetch/mod.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/fetch/mod.rs b/src/fetch/mod.rs index e36e8b2..b8e9c84 100644 --- a/src/fetch/mod.rs +++ b/src/fetch/mod.rs @@ -154,3 +154,34 @@ async fn fetch_object_http_with_accept( )), } } + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use super::*; + use crate::{ + config::FederationConfig, + traits::tests::{DbConnection, Person}, + }; + + #[tokio::test] + async fn test_request_limit() -> Result<(), Error> { + let config = FederationConfig::builder() + .domain("example.com") + .app_data(DbConnection) + .http_fetch_limit(0) + .build() + .await + .unwrap(); + let data = config.to_request_data(); + + let fetch_url = "https://example.net/".to_string(); + + let res: Result, Error> = + fetch_object_http(&Url::parse(&fetch_url).map_err(Error::UrlParse)?, &data).await; + + assert_eq!(res.err(), Some(Error::RequestLimit)); + + Ok(()) + } +} From 6814ff1932d84692416429efa88a9a6dd078b193 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Fri, 8 Nov 2024 13:37:55 +0100 Subject: [PATCH 08/13] If dereference fails, return object from local db instead (#129) --- src/fetch/object_id.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/fetch/object_id.rs b/src/fetch/object_id.rs index 3f0676b..424c22c 100644 --- a/src/fetch/object_id.rs +++ b/src/fetch/object_id.rs @@ -120,6 +120,7 @@ where .await .map(|o| o.ok_or(Error::NotFound.into()))? } else { + // Don't pass in any db object, otherwise it would be returned in case http fetch fails self.dereference_from_http(data, None).await } } @@ -167,6 +168,10 @@ where return Err(Error::ObjectDeleted(url).into()); } + // If fetch failed, return the existing object from local database + if let (Err(_), Some(db_object)) = (&res, db_object) { + return Ok(db_object); + } let res = res?; let redirect_url = &res.url; From 1c29f4e66b86725638d1e6e248549566060a327f Mon Sep 17 00:00:00 2001 From: Nutomic Date: Tue, 12 Nov 2024 13:20:02 +0100 Subject: [PATCH 09/13] Prevent overwriting local object (#130) * Throw error when attempting to http fetch local object * clippy --- src/fetch/object_id.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/fetch/object_id.rs b/src/fetch/object_id.rs index 424c22c..108dc1a 100644 --- a/src/fetch/object_id.rs +++ b/src/fetch/object_id.rs @@ -92,7 +92,7 @@ where // object found in database if let Some(object) = db_object { if let Some(last_refreshed_at) = object.last_refreshed_at() { - let is_local = data.config.is_local_url(&self.0); + let is_local = self.is_local(data); if !is_local && should_refetch_object(last_refreshed_at) { // object is outdated and should be refetched return self.dereference_from_http(data, Some(object)).await; @@ -175,6 +175,14 @@ where let res = res?; let redirect_url = &res.url; + // Prevent overwriting local object + if data.config.is_local_url(redirect_url) { + return self + .dereference_from_db(data) + .await? + .ok_or(Error::NotFound.into()); + } + Box::pin(Kind::verify(&res.object, redirect_url, data)).await?; Box::pin(Kind::from_json(res.object, data)).await } From fbcd16aa950c41f48b85a722be1584c03ff4826c Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Tue, 12 Nov 2024 13:22:14 +0100 Subject: [PATCH 10/13] Version 0.6.0-alpha3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3e2d57e..3816d3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "activitypub_federation" -version = "0.6.0-alpha2" +version = "0.6.0-alpha3" edition = "2021" description = "High-level Activitypub framework" keywords = ["activitypub", "activitystreams", "federation", "fediverse"] From 169137be02d0a77029fa07add412064490d3357f Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Tue, 12 Nov 2024 14:11:03 +0100 Subject: [PATCH 11/13] Version 0.6.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3816d3d..0c7adc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "activitypub_federation" -version = "0.6.0-alpha3" +version = "0.6.0" edition = "2021" description = "High-level Activitypub framework" keywords = ["activitypub", "activitystreams", "federation", "fediverse"] From b9a89ffc8ef814ed473a49f2ad48a4edb6b4f763 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Tue, 19 Nov 2024 14:22:05 +0100 Subject: [PATCH 12/13] Add method to sign arbitrary http request (#131) --- src/config.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 2015750..63af704 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,14 +17,17 @@ use crate::{ activity_queue::{create_activity_queue, ActivityQueue}, error::Error, + http_signatures::sign_request, protocol::verification::verify_domains_match, traits::{ActivityHandler, Actor}, }; use async_trait::async_trait; +use bytes::Bytes; use derive_builder::Builder; use dyn_clone::{clone_trait_object, DynClone}; use moka::future::Cache; -use reqwest_middleware::ClientWithMiddleware; +use reqwest::Request; +use reqwest_middleware::{ClientWithMiddleware, RequestBuilder}; use rsa::{pkcs8::DecodePrivateKey, RsaPrivateKey}; use serde::de::DeserializeOwned; use std::{ @@ -327,6 +330,25 @@ impl Data { pub fn request_count(&self) -> u32 { self.request_counter.load(Ordering::Relaxed) } + + /// Add HTTP signature to arbitrary request + pub async fn sign_request(&self, req: RequestBuilder, body: Bytes) -> Result { + let (actor_id, private_key_pem) = + self.config + .signed_fetch_actor + .as_deref() + .ok_or(Error::Other( + "config value signed_fetch_actor is none".to_string(), + ))?; + sign_request( + req, + actor_id, + body, + private_key_pem.clone(), + self.config.http_signature_compat, + ) + .await + } } impl Deref for Data { From 426edca837c5c06f989d0b229bb26493f4b31a8c Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Tue, 19 Nov 2024 14:32:14 +0100 Subject: [PATCH 13/13] Version 0.6.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0c7adc9..5ac2d79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "activitypub_federation" -version = "0.6.0" +version = "0.6.1" edition = "2021" description = "High-level Activitypub framework" keywords = ["activitypub", "activitystreams", "federation", "fediverse"]