Add initial axum functions

This commit is contained in:
MTRNord 2022-11-25 19:28:25 +01:00
parent 96f62b8513
commit a7631019de
No known key found for this signature in database
7 changed files with 272 additions and 15 deletions

177
Cargo.lock generated
View file

@ -11,6 +11,7 @@ dependencies = [
"actix-web",
"anyhow",
"async-trait",
"axum",
"background-jobs",
"base64",
"chrono",
@ -20,6 +21,7 @@ dependencies = [
"env_logger",
"http",
"http-signature-normalization-actix",
"http-signature-normalization-http",
"http-signature-normalization-reqwest",
"httpdate",
"itertools",
@ -205,7 +207,7 @@ dependencies = [
"serde_urlencoded",
"smallvec",
"socket2",
"time",
"time 0.3.17",
"url",
]
@ -270,6 +272,56 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "744864363a200a5e724a7e61bc8c11b6628cf2e3ec519c8a1a48e609a8156b40"
dependencies = [
"async-trait",
"axum-core",
"bitflags",
"bytes",
"futures-util",
"http",
"http-body",
"hyper",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tower",
"tower-http",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79b8558f5a0581152dc94dcd289132a1d377494bdeafcd41869b3258e3e2ad92"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http",
"http-body",
"mime",
"rustversion",
"tower-layer",
"tower-service",
]
[[package]]
name = "background-jobs"
version = "0.13.0"
@ -313,7 +365,7 @@ dependencies = [
"serde",
"serde_json",
"thiserror",
"time",
"time 0.3.17",
"tracing",
"tracing-futures",
"uuid",
@ -380,8 +432,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
"time 0.1.44",
"wasm-bindgen",
"winapi",
]
@ -725,7 +780,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
@ -784,6 +839,21 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "http-range-header"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29"
[[package]]
name = "http-signature-normalization"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a57536672e86bd8fb5b2c692237057796e5cf21399c61d8e01db0742ab3c5306"
dependencies = [
"chrono",
]
[[package]]
name = "http-signature-normalization"
version = "0.6.0"
@ -804,7 +874,7 @@ dependencies = [
"actix-web",
"base64",
"futures-util",
"http-signature-normalization",
"http-signature-normalization 0.6.0",
"sha2",
"thiserror",
"tokio",
@ -813,6 +883,16 @@ dependencies = [
"tracing-futures",
]
[[package]]
name = "http-signature-normalization-http"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a61ca867aaf645f09341388393c9e43dce64dd23ddbba4288610a0ebdcb05de"
dependencies = [
"http",
"http-signature-normalization 0.2.0",
]
[[package]]
name = "http-signature-normalization-reqwest"
version = "0.7.1"
@ -820,7 +900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e309145e63e70307ab1f521fb6e354158f0255e572da21c9fd56940c5bd5d854"
dependencies = [
"base64",
"http-signature-normalization",
"http-signature-normalization 0.6.0",
"httpdate",
"reqwest",
"reqwest-middleware",
@ -1031,6 +1111,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "matchit"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfc802da7b1cf80aefffa0c7b2f77247c8b32206cc83c270b61264f5b360a80"
[[package]]
name = "memchr"
version = "2.5.0"
@ -1061,7 +1147,7 @@ checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
dependencies = [
"libc",
"log",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.42.0",
]
@ -1387,6 +1473,12 @@ dependencies = [
"semver",
]
[[package]]
name = "rustversion"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
[[package]]
name = "ryu"
version = "1.0.11"
@ -1476,6 +1568,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_path_to_error"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "184c643044780f7ceb59104cef98a5a6f12cb2288a7bc701ab93a362b49fd47d"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@ -1570,6 +1671,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8"
[[package]]
name = "task-local-extensions"
version = "0.1.3"
@ -1631,6 +1738,17 @@ dependencies = [
"once_cell",
]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "time"
version = "0.3.17"
@ -1727,6 +1845,47 @@ dependencies = [
"tracing",
]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"pin-project",
"pin-project-lite",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-http"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba"
dependencies = [
"bitflags",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-range-header",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
[[package]]
name = "tower-service"
version = "0.3.2"
@ -1889,6 +2048,12 @@ dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"

View file

@ -16,14 +16,15 @@ serde_json = { version = "1.0.87", features = ["preserve_order"] }
anyhow = "1.0.66"
reqwest = { version = "0.11.12", features = ["json"] }
reqwest-middleware = "0.2.0"
tracing = "0.1.37"
tracing = {version = "0.1.37", features = ["log"]}
base64 = "0.13.1"
openssl = "0.10.42"
once_cell = "1.16.0"
http = "0.2.8"
sha2 = "0.10.6"
actix-web = { version = "4.2.1", default-features = false }
http-signature-normalization-actix = { version = "0.6.1", default-features = false, features = ["server", "sha-2"] }
actix-web = { version = "4.2.1", default-features = false, optional = true }
http-signature-normalization-actix = { version = "0.6.1", default-features = false, features = ["server", "sha-2"], optional = true }
http-signature-normalization-http = { version = "0.3.0", optional = true }
http-signature-normalization-reqwest = { version = "0.7.1", default-features = false, features = ["sha-2", "middleware"] }
background-jobs = "0.13.0"
thiserror = "1.0.37"
@ -32,6 +33,7 @@ itertools = "0.10.5"
dyn-clone = "1.0.9"
enum_delegate = "0.2.0"
httpdate = "1.0.2"
axum = { version = "0.6.0", optional = true }
[dev-dependencies]
activitystreams-kinds = "0.2.1"
@ -39,3 +41,8 @@ rand = "0.8.5"
actix-rt = "2.7.0"
tokio = "1.21.2"
env_logger = { version = "0.9.3", default-features = false }
[features]
default = ["actix"]
actix = ["dep:actix-web", "dep:http-signature-normalization-actix"]
axum = ["dep:http-signature-normalization-http", "dep:axum"]

View file

1
src/axum/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod middleware;

View file

@ -3,15 +3,20 @@ use crate::{
data::Data,
traits::{ActivityHandler, Actor, ApubObject},
utils::{verify_domains_match, verify_url_valid},
Error,
LocalInstance,
Error, LocalInstance,
};
use actix_web::{dev::Payload, FromRequest, HttpRequest, HttpResponse};
use http_signature_normalization_actix::prelude::DigestVerified;
use serde::de::DeserializeOwned;
use tracing::log::debug;
#[cfg(feature = "actix")]
use actix_web::{dev::Payload, FromRequest, HttpRequest, HttpResponse};
#[cfg(feature = "axum")]
use axum::http::{Request as AxumRequest, StatusCode};
#[cfg(feature = "actix")]
use http_signature_normalization_actix::prelude::DigestVerified;
/// Receive an activity and perform some basic checks, including HTTP signature verification.
#[cfg(feature = "actix")]
pub async fn receive_activity<Activity, ActorT, Datatype>(
request: HttpRequest,
activity: Activity,
@ -51,3 +56,41 @@ where
activity.receive(data, request_counter).await?;
Ok(HttpResponse::Ok().finish())
}
/// Receive an activity and perform some basic checks, including HTTP signature verification.
#[cfg(feature = "axum")]
pub async fn receive_activity<Activity, ActorT, Datatype, T>(
request: AxumRequest<T>,
activity: Activity,
local_instance: &LocalInstance,
data: &Data<Datatype>,
) -> Result<StatusCode, <Activity as ActivityHandler>::Error>
where
Activity: ActivityHandler<DataType = Datatype> + DeserializeOwned + Send + 'static,
ActorT: ApubObject<DataType = Datatype> + Actor + Send + 'static,
for<'de2> <ActorT as ApubObject>::ApubType: serde::Deserialize<'de2>,
<Activity as ActivityHandler>::Error: From<anyhow::Error>
+ From<Error>
+ From<<ActorT as ApubObject>::Error>
+ From<serde_json::Error>,
<ActorT as ApubObject>::Error: From<Error> + From<anyhow::Error>,
{
verify_domains_match(activity.id(), activity.actor())?;
verify_url_valid(activity.id(), &local_instance.settings).await?;
if local_instance.is_local_url(activity.id()) {
return Err(Error::UrlVerificationError("Activity was sent from local instance").into());
}
let request_counter = &mut 0;
let actor = ObjectId::<ActorT>::new(activity.actor().clone())
.dereference(data, local_instance, request_counter)
.await?;
verify_signature(&request, actor.public_key())?;
debug!("Verifying activity {}", activity.id().to_string());
activity.verify(data, request_counter).await?;
debug!("Receiving activity {}", activity.id().to_string());
activity.receive(data, request_counter).await?;
Ok(StatusCode::OK)
}

View file

@ -1,6 +1,4 @@
use actix_web::HttpRequest;
use anyhow::anyhow;
use http_signature_normalization_actix::Config as ConfigActix;
use http_signature_normalization_reqwest::prelude::{Config, SignExt};
use once_cell::sync::{Lazy, OnceCell};
use openssl::{
@ -17,7 +15,19 @@ use std::io::{Error, ErrorKind};
use tracing::debug;
use url::Url;
#[cfg(feature = "actix")]
use actix_web::HttpRequest;
#[cfg(feature = "actix")]
use http_signature_normalization_actix::Config as ConfigActix;
#[cfg(feature = "axum")]
use http_signature_normalization_http::Config as ConfigHttp;
#[cfg(feature = "axum")]
use axum::http::Request as AxumRequest;
#[cfg(feature = "actix")]
static CONFIG2: Lazy<ConfigActix> = Lazy::new(ConfigActix::new);
#[cfg(feature = "axum")]
static CONFIG2: Lazy<ConfigHttp> = Lazy::new(ConfigHttp::default);
static HTTP_SIG_CONFIG: OnceCell<Config> = OnceCell::new();
/// A private/public key pair used for HTTP signatures
@ -81,6 +91,7 @@ pub(crate) async fn sign_request(
}
/// Verifies the HTTP signature on an incoming inbox request.
#[cfg(feature = "actix")]
pub fn verify_signature(request: &HttpRequest, public_key: &str) -> Result<(), anyhow::Error> {
let verified = CONFIG2
.begin_verify(
@ -107,6 +118,35 @@ pub fn verify_signature(request: &HttpRequest, public_key: &str) -> Result<(), a
}
}
// Verifies the HTTP signature on an incoming inbox request.
#[cfg(feature = "axum")]
pub fn verify_signature<T>(request: &AxumRequest<T>, public_key: &str) -> Result<(), anyhow::Error> {
let verified = CONFIG2
.begin_verify(
request.method(),
request.uri().path_and_query(),
request.headers().clone(),
)?
.verify(|signature, signing_string| -> Result<bool, anyhow::Error> {
debug!(
"Verifying with key {}, message {}",
&public_key, &signing_string
);
let public_key = PKey::public_key_from_pem(public_key.as_bytes())?;
let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key)?;
verifier.update(signing_string.as_bytes())?;
Ok(verifier.verify(&base64::decode(signature)?)?)
})?;
if verified {
debug!("verified signature for {}", &request.uri());
Ok(())
} else {
Err(anyhow!("Invalid signature on request: {}", &request.uri()))
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PublicKey {

View file

@ -12,6 +12,7 @@ pub mod data;
pub mod deser;
pub mod traits;
pub mod utils;
pub mod axum;
/// Mime type for Activitypub, used for `Accept` and `Content-Type` HTTP headers
pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";