From ccde4b4666e395f476e9518ced2255cad1d5e555 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Fri, 19 Jan 2024 15:49:08 +0100 Subject: [PATCH] fix conflicts between federation and frontend routes --- Cargo.lock | 1 + Cargo.toml | 1 + README.md | 2 + src/backend/api/instance.rs | 3 +- src/backend/federation/activities/follow.rs | 1 - src/backend/federation/routes.rs | 4 +- src/backend/mod.rs | 46 +++++++++++++++++--- src/frontend/app.rs | 7 ++- src/frontend/mod.rs | 2 +- src/frontend/pages/article.rs | 47 ++++----------------- src/frontend/pages/edit_article.rs | 11 +++++ src/frontend/pages/mod.rs | 2 + src/frontend/pages/read_article.rs | 47 +++++++++++++++++++++ tests/common.rs | 4 +- 14 files changed, 123 insertions(+), 55 deletions(-) create mode 100644 src/frontend/pages/edit_article.rs create mode 100644 src/frontend/pages/read_article.rs diff --git a/Cargo.lock b/Cargo.lock index 0f5c273..e3d3a0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1415,6 +1415,7 @@ dependencies = [ "sha2", "time", "tokio", + "tower", "tower-http", "tracing", "url", diff --git a/Cargo.toml b/Cargo.toml index f5e5b7a..7e4b35b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ wasm-bindgen = "0.2.89" console_error_panic_hook = "0.1.7" console_log = "1.0.0" time = "0.3.31" +tower = "0.4.13" [dev-dependencies] pretty_assertions = "1.4.0" diff --git a/README.md b/README.md index 3f4fb2d..b0563b4 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ You need to install [cargo](https://rustup.rs/) and [trunk](https://trunkrs.dev) ``` # start backend, with separate target folder to avoid rebuilds from arch change cargo run --target-dir target/backend +# or with automatic rebuild on changes using cargo-watch +CARGO_TARGET_DIR=target/backend cargo watch -c -x run # start frontend, automatic rebuild on changes trunk serve -w src/frontend/ diff --git a/src/backend/api/instance.rs b/src/backend/api/instance.rs index c8ba0bc..bf1d8fb 100644 --- a/src/backend/api/instance.rs +++ b/src/backend/api/instance.rs @@ -43,7 +43,6 @@ pub(super) async fn resolve_instance( data: Data, ) -> MyResult> { // TODO: workaround because axum makes it hard to have multiple routes on / - let id = format!("{}instance", query.id); - let instance: DbInstance = ObjectId::parse(&id)?.dereference(&data).await?; + let instance: DbInstance = ObjectId::from(query.id).dereference(&data).await?; Ok(Json(instance)) } diff --git a/src/backend/federation/activities/follow.rs b/src/backend/federation/activities/follow.rs index 522acdb..456e434 100644 --- a/src/backend/federation/activities/follow.rs +++ b/src/backend/federation/activities/follow.rs @@ -33,7 +33,6 @@ impl Follow { kind: Default::default(), id, }; - send_activity(&actor, follow, vec![to.shared_inbox_or_inbox()], data).await?; Ok(()) } diff --git a/src/backend/federation/routes.rs b/src/backend/federation/routes.rs index f30f6ab..f3cd191 100644 --- a/src/backend/federation/routes.rs +++ b/src/backend/federation/routes.rs @@ -35,9 +35,7 @@ use url::Url; pub fn federation_routes() -> Router { Router::new() - // TODO: would be nice if this could be at / but axum doesnt properly support routing by headers - // https://github.com/tokio-rs/axum/issues/1654 - .route("/instance", get(http_get_instance)) + .route("/", get(http_get_instance)) .route("/user/:name", get(http_get_person)) .route("/all_articles", get(http_get_all_articles)) .route("/article/:title", get(http_get_article)) diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 1986cfa..7b8c810 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -11,7 +11,10 @@ use activitypub_federation::fetch::collection_id::CollectionId; use activitypub_federation::fetch::object_id::ObjectId; use activitypub_federation::http_signatures::generate_actor_keypair; use api::api_routes; -use axum::{Router, Server}; +use axum::http::{HeaderValue, Request}; +use axum::Server; +use axum::ServiceExt; +use axum::{middleware::Next, response::Response, Router}; use chrono::Local; use diesel::Connection; use diesel::PgConnection; @@ -23,6 +26,7 @@ use leptos_axum::{generate_route_list, LeptosRoutes}; use log::info; use std::net::ToSocketAddrs; use std::sync::{Arc, Mutex}; +use tower::Layer; use tower_http::cors::CorsLayer; use tower_http::services::{ServeDir, ServeFile}; @@ -34,6 +38,8 @@ mod utils; const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); +const FEDERATION_ROUTES_PREFIX: &str = "/federation_routes"; + pub async fn start(hostname: &str, database_url: &str) -> MyResult<()> { let db_connection = Arc::new(Mutex::new(PgConnection::establish(database_url)?)); db_connection @@ -53,8 +59,7 @@ pub async fn start(hostname: &str, database_url: &str) -> MyResult<()> { // Create local instance if it doesnt exist yet // TODO: Move this into setup api call if DbInstance::read_local_instance(&config.db_connection).is_err() { - // TODO: workaround because axum makes it hard to have multiple routes on / - let ap_id = ObjectId::parse(&format!("http://{}/instance", hostname))?; + let ap_id = ObjectId::parse(&format!("http://{hostname}"))?; let articles_url = CollectionId::parse(&format!("http://{}/all_articles", hostname))?; let inbox_url = format!("http://{}/inbox", hostname); let keypair = generate_actor_keypair()?; @@ -102,13 +107,44 @@ pub async fn start(hostname: &str, database_url: &str) -> MyResult<()> { "/pkg/ibis_bg.wasm", ServeFile::new_with_mime("assets/dist/ibis_bg.wasm", &"application/wasm".parse()?), ) - .nest("", federation_routes()) + .nest(FEDERATION_ROUTES_PREFIX, federation_routes()) .nest("/api/v1", api_routes()) .layer(FederationMiddleware::new(config)) .layer(CorsLayer::permissive()); + // Rewrite federation routes + // https://docs.rs/axum/0.7.4/axum/middleware/index.html#rewriting-request-uri-in-middleware + let middleware = axum::middleware::from_fn(federation_routes_middleware); + let app_with_middleware = middleware.layer(app); + info!("{addr}"); - Server::bind(&addr).serve(app.into_make_service()).await?; + Server::bind(&addr) + .serve(app_with_middleware.into_make_service()) + .await?; Ok(()) } + +/// Rewrite federation routes to use `FEDERATION_ROUTES_PREFIX`, to avoid conflicts +/// with frontend routes. If a request is an Activitypub fetch as indicated by +/// `Accept: application/activity+json` header, use the federation routes. Otherwise +/// leave the path unchanged so it can go to frontend. +async fn federation_routes_middleware(request: Request, next: Next) -> Response { + let (mut parts, body) = request.into_parts(); + // rewrite uri based on accept header + let mut uri = parts.uri.to_string(); + let accept_value = HeaderValue::from_static("application/activity+json"); + if Some(&accept_value) == parts.headers.get("Accept") + || Some(&accept_value) == parts.headers.get("Content-Type") + { + uri = format!("{FEDERATION_ROUTES_PREFIX}{uri}"); + } + // drop trailing slash + if uri.ends_with('/') { + uri.pop(); + } + parts.uri = uri.parse().unwrap(); + let request = Request::from_parts(parts, body); + + next.run(request).await +} diff --git a/src/frontend/app.rs b/src/frontend/app.rs index aa723a4..cf3a335 100644 --- a/src/frontend/app.rs +++ b/src/frontend/app.rs @@ -1,8 +1,9 @@ use crate::common::LocalUserView; use crate::frontend::api::ApiClient; use crate::frontend::components::nav::Nav; -use crate::frontend::pages::article::Article; +use crate::frontend::pages::edit_article::EditArticle; use crate::frontend::pages::login::Login; +use crate::frontend::pages::read_article::ReadArticle; use crate::frontend::pages::register::Register; use crate::frontend::pages::Page; use leptos::{ @@ -73,7 +74,9 @@ pub fn App() -> impl IntoView {