mirror of
https://github.com/LemmyNet/activitypub-federation-rust.git
synced 2024-06-11 09:49:24 +00:00
Add initial axum functions
This commit is contained in:
parent
96f62b8513
commit
a7631019de
177
Cargo.lock
generated
177
Cargo.lock
generated
|
@ -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"
|
||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -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"]
|
0
src/axum/middleware/mod.rs
Normal file
0
src/axum/middleware/mod.rs
Normal file
1
src/axum/mod.rs
Normal file
1
src/axum/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod middleware;
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Reference in a new issue