diff options
author | rtkay123 <dev@kanjala.com> | 2025-07-17 21:27:54 +0200 |
---|---|---|
committer | rtkay123 <dev@kanjala.com> | 2025-07-17 21:27:54 +0200 |
commit | 9145bdf4a600758a05b02394ebf14e811f75ccfc (patch) | |
tree | c5270a9d043b95ab976a3c354f3cb54b353df428 | |
parent | 69fe55ad54468948c13af520a498ed4aeac194ed (diff) | |
download | sellershut-9145bdf4a600758a05b02394ebf14e811f75ccfc.tar.bz2 sellershut-9145bdf4a600758a05b02394ebf14e811f75ccfc.zip |
test: follow activity
-rw-r--r-- | Cargo.lock | 3 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | crates/sellershut/Cargo.toml | 7 | ||||
-rw-r--r-- | crates/sellershut/src/server.rs | 3 | ||||
-rw-r--r-- | crates/sellershut/src/server/activities/follow.rs | 59 | ||||
-rw-r--r-- | crates/sellershut/src/server/middleware.rs | 1 | ||||
-rw-r--r-- | crates/sellershut/src/server/middleware/sign_request.rs | 64 | ||||
-rw-r--r-- | crates/sellershut/src/server/routes/users.rs | 27 | ||||
-rw-r--r-- | crates/sellershut/src/server/routes/users/post_inbox.rs | 18 | ||||
-rw-r--r-- | crates/sellershut/src/state.rs | 8 |
10 files changed, 167 insertions, 24 deletions
@@ -2062,9 +2062,12 @@ dependencies = [ "clap", "config", "enum_delegate", + "futures-util", + "hmac", "nanoid", "serde", "serde_json", + "sha2", "sqlx", "stack-up", "tokio", @@ -14,6 +14,7 @@ async-trait = "0.1.88" axum = "0.8.4" clap = "4.5.41" config = { version = "0.15.13", default-features = false } +futures-util = { version = "0.3.31", default-features = false } nanoid = "0.4.0" serde = "1.0.219" serde_json = "1.0.140" diff --git a/crates/sellershut/Cargo.toml b/crates/sellershut/Cargo.toml index 5bacc49..43948f8 100644 --- a/crates/sellershut/Cargo.toml +++ b/crates/sellershut/Cargo.toml @@ -15,11 +15,15 @@ axum = { workspace = true, features = ["macros"] } clap = { workspace = true, features = ["derive"] } config = { workspace = true, features = ["toml"] } enum_delegate = "0.2.0" +futures-util.workspace = true +hmac = "0.12.1" nanoid.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true +sha2 = "0.10.9" sqlx = { workspace = true, features = ["macros", "migrate", "runtime-tokio", "time", "tls-rustls", "uuid"] } tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] } +tower = { workspace = true, features = ["util"] } tower-http = { workspace = true, features = ["trace"] } tracing.workspace = true url.workspace = true @@ -28,6 +32,3 @@ uuid = { workspace = true, features = ["v7"] } [dependencies.stack-up] workspace = true features = ["api", "postgres", "tracing"] - -[dev-dependencies] -tower = { workspace = true, features = ["util"] } diff --git a/crates/sellershut/src/server.rs b/crates/sellershut/src/server.rs index dd49a54..32bf036 100644 --- a/crates/sellershut/src/server.rs +++ b/crates/sellershut/src/server.rs @@ -8,6 +8,7 @@ use url::Url; use crate::{error::AppError, server::routes::health_check, state::AppHandle}; pub mod activities; +pub mod middleware; pub mod routes; const ALPHABET: [char; 36] = [ @@ -28,7 +29,7 @@ pub fn generate_object_id(domain: &str, env: Environment) -> Result<Url, AppErro pub fn router(state: FederationConfig<AppHandle>) -> Router { Router::new() - .merge(routes::users::users_router()) + .merge(routes::users::users_router(state.clone())) .route("/", get(health_check)) .layer(TraceLayer::new_for_http()) .layer(FederationMiddleware::new(state)) diff --git a/crates/sellershut/src/server/activities/follow.rs b/crates/sellershut/src/server/activities/follow.rs index 466edb7..a004c6b 100644 --- a/crates/sellershut/src/server/activities/follow.rs +++ b/crates/sellershut/src/server/activities/follow.rs @@ -89,3 +89,62 @@ impl Activity for Follow { Ok(()) } } + +#[cfg(test)] +mod tests { + use axum::{ + body::Body, + http::{Request, StatusCode}, + }; + use sqlx::PgPool; + use stack_up::Services; + use tower::ServiceExt; + + use crate::{ + server::{self, test_config}, + state::AppState, + }; + + #[sqlx::test] + async fn follow(pool: PgPool) { + let services = Services { postgres: pool }; + let config = test_config(); + + let hut_config: crate::cnfg::LocalConfig = + serde_json::from_value(config.misc.clone()).unwrap(); + let state = AppState::create(services, &config).await.unwrap(); + + let id = format!( + "http://localhost:{}/activity/follow/1", + config.application.port + ); + let actor = format!( + "http://localhost:{}/users/{}", + config.application.port, hut_config.instance_name + ); + + let app = server::router(state); + + let body = serde_json::json!({ + "@context": "https://www.w3.org/ns/activitystreams", + "id": id, + "type": "Follow", + "actor": actor, + "object": actor, + }); + let body = serde_json::to_vec(&body).unwrap(); + + let response = app + .oneshot( + Request::builder() + .method("POST") + .uri("/users/sellershut/inbox") + .body(Body::from(body)) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::OK); + } +} diff --git a/crates/sellershut/src/server/middleware.rs b/crates/sellershut/src/server/middleware.rs new file mode 100644 index 0000000..aa73518 --- /dev/null +++ b/crates/sellershut/src/server/middleware.rs @@ -0,0 +1 @@ +pub mod sign_request; diff --git a/crates/sellershut/src/server/middleware/sign_request.rs b/crates/sellershut/src/server/middleware/sign_request.rs new file mode 100644 index 0000000..a8f2f3a --- /dev/null +++ b/crates/sellershut/src/server/middleware/sign_request.rs @@ -0,0 +1,64 @@ +use activitypub_federation::config::FederationConfig; +use axum::{body::Body, extract::Request, response::Response}; +use futures_util::future::BoxFuture; +use hmac::{Hmac, Mac}; +use sha2::{Sha256, digest::KeyInit}; +use std::task::{Context, Poll}; +use tower::{Layer, Service}; + +use crate::state::AppHandle; + +type HmacSha256 = Hmac<Sha256>; + +#[derive(Clone)] +pub struct SignRequestLayer { + state: FederationConfig<AppHandle>, +} + +impl SignRequestLayer { + pub fn new(state: &FederationConfig<AppHandle>) -> Self { + Self { + state: state.to_owned(), + } + } +} + +impl<S> Layer<S> for SignRequestLayer { + type Service = SignRequestMiddleware<S>; + + fn layer(&self, inner: S) -> Self::Service { + SignRequestMiddleware { + inner, + state: self.state.clone(), + } + } +} + +#[derive(Clone)] +pub struct SignRequestMiddleware<S> { + inner: S, + state: FederationConfig<AppHandle>, +} + +impl<S> Service<Request> for SignRequestMiddleware<S> +where + S: Service<Request, Response = Response> + Send + 'static, + S::Future: Send + 'static, +{ + type Response = S::Response; + type Error = S::Error; + // `BoxFuture` is a type alias for `Pin<Box<dyn Future + Send + 'a>>` + type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, request: Request) -> Self::Future { + let future = self.inner.call(request); + Box::pin(async move { + let response: Response = future.await?; + Ok(response) + }) + } +} diff --git a/crates/sellershut/src/server/routes/users.rs b/crates/sellershut/src/server/routes/users.rs index 9c9a3bf..56078b6 100644 --- a/crates/sellershut/src/server/routes/users.rs +++ b/crates/sellershut/src/server/routes/users.rs @@ -1,15 +1,24 @@ pub mod get_outbox; -pub mod post_inbox; pub mod get_user; +pub mod post_inbox; pub mod webfinger; -use activitypub_federation::traits::Activity; -use axum::{routing::{get, post}, Router}; +use activitypub_federation::{config::FederationConfig, traits::Activity}; +use axum::{ + Router, + routing::{get, post}, +}; use serde::{Deserialize, Serialize}; -use crate::server::activities::{accept::Accept, follow::Follow}; -use url::Url; +use crate::{ + server::{ + activities::{accept::Accept, follow::Follow}, + middleware::sign_request::SignRequestLayer, + }, + state::AppHandle, +}; use activitypub_federation::config::Data; +use url::Url; /// List of all activities which this actor can receive. #[derive(Deserialize, Serialize, Debug)] @@ -20,11 +29,13 @@ pub enum PersonAcceptedActivities { Accept(Accept), } - -pub fn users_router() -> Router { +pub fn users_router(state: FederationConfig<AppHandle>) -> Router { Router::new() .route("/users/{username}", get(get_user::http_get_user)) .route("/users/{username}/outbox", get(get_outbox::http_get_outbox)) - .route("/users/{username}/inbox", post(post_inbox::http_post_user_inbox)) + .route( + "/users/{username}/inbox", + post(post_inbox::http_post_user_inbox).layer(SignRequestLayer::new(&state)), + ) .route("/.well-known/webfinger", get(webfinger::webfinger)) } diff --git a/crates/sellershut/src/server/routes/users/post_inbox.rs b/crates/sellershut/src/server/routes/users/post_inbox.rs index 5e3258b..6be2544 100644 --- a/crates/sellershut/src/server/routes/users/post_inbox.rs +++ b/crates/sellershut/src/server/routes/users/post_inbox.rs @@ -1,16 +1,18 @@ -use activitypub_federation::{axum::inbox::{receive_activity, ActivityData}, config::Data, protocol::context::WithContext}; +use activitypub_federation::{ + axum::inbox::{ActivityData, receive_activity}, + config::Data, + protocol::context::WithContext, +}; use axum::response::IntoResponse; -use crate::{entity::user::User, server::routes::users::PersonAcceptedActivities, state::AppHandle}; +use crate::{ + entity::user::User, server::routes::users::PersonAcceptedActivities, state::AppHandle, +}; pub async fn http_post_user_inbox( data: Data<AppHandle>, activity_data: ActivityData, ) -> impl IntoResponse { - receive_activity::<WithContext<PersonAcceptedActivities>, User, AppHandle>( - activity_data, - &data, - ) - .await + receive_activity::<WithContext<PersonAcceptedActivities>, User, AppHandle>(activity_data, &data) + .await } - diff --git a/crates/sellershut/src/state.rs b/crates/sellershut/src/state.rs index 9129030..c524152 100644 --- a/crates/sellershut/src/state.rs +++ b/crates/sellershut/src/state.rs @@ -26,18 +26,18 @@ impl AppState { services: Services, configuration: &Configuration, ) -> Result<FederationConfig<AppHandle>, AppError> { - let warden_config: LocalConfig = serde_json::from_value(configuration.misc.clone())?; + let hut_config: LocalConfig = serde_json::from_value(configuration.misc.clone())?; let user = User::new( - &warden_config.instance_name, - &warden_config.hostname, + &hut_config.instance_name, + &hut_config.hostname, &services, configuration.application.env, ) .await?; let config = FederationConfig::builder() - .domain(&warden_config.hostname) + .domain(&hut_config.hostname) .signed_fetch_actor(&user) .app_data(AppHandle(Arc::new(Self { services, |