summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrtkay123 <dev@kanjala.com>2025-07-17 21:27:54 +0200
committerrtkay123 <dev@kanjala.com>2025-07-17 21:27:54 +0200
commit9145bdf4a600758a05b02394ebf14e811f75ccfc (patch)
treec5270a9d043b95ab976a3c354f3cb54b353df428
parent69fe55ad54468948c13af520a498ed4aeac194ed (diff)
downloadsellershut-9145bdf4a600758a05b02394ebf14e811f75ccfc.tar.bz2
sellershut-9145bdf4a600758a05b02394ebf14e811f75ccfc.zip
test: follow activity
-rw-r--r--Cargo.lock3
-rw-r--r--Cargo.toml1
-rw-r--r--crates/sellershut/Cargo.toml7
-rw-r--r--crates/sellershut/src/server.rs3
-rw-r--r--crates/sellershut/src/server/activities/follow.rs59
-rw-r--r--crates/sellershut/src/server/middleware.rs1
-rw-r--r--crates/sellershut/src/server/middleware/sign_request.rs64
-rw-r--r--crates/sellershut/src/server/routes/users.rs27
-rw-r--r--crates/sellershut/src/server/routes/users/post_inbox.rs18
-rw-r--r--crates/sellershut/src/state.rs8
10 files changed, 167 insertions, 24 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 55429c9..f2e4973 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2062,9 +2062,12 @@ dependencies = [
"clap",
"config",
"enum_delegate",
+ "futures-util",
+ "hmac",
"nanoid",
"serde",
"serde_json",
+ "sha2",
"sqlx",
"stack-up",
"tokio",
diff --git a/Cargo.toml b/Cargo.toml
index df00993..5cfc844 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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,