diff --git a/docker/nginx.conf b/docker/nginx.conf index 07eec16..fa3c4d1 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -57,16 +57,7 @@ server { # lemmybb frontend location / { - set $proxpass "http://0.0.0.0:8701"; - # federation requests go to backend - if ($http_accept ~ "^application/.*$") { - set $proxpass "http://0.0.0.0:8703"; - } - # fonts have similar accept header but need to be handled by frontend - if ($http_accept ~ "^application/font.*$") { - set $proxpass "http://0.0.0.0:8701"; - } - proxy_pass $proxpass; + proxy_pass "http://0.0.0.0:8701"; rewrite ^(.+)/+$ $1 permanent; } diff --git a/src/main.rs b/src/main.rs index 1623229..250287d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,9 +20,9 @@ use crate::{ routes::{ comment::*, community::*, + federation::*, post::*, private_message::*, - redirects::*, site::*, user::*, }, @@ -106,15 +106,16 @@ fn init_rocket() -> Result, Error> { private_message_editor, do_send_private_message, image, - redirect_apub_community, - redirect_apub_user, - redirect_apub_post, - redirect_apub_comment, + apub_community, + apub_user, + apub_post, + apub_comment, report, do_report, edit_profile, do_edit_profile, - community_list + community_list, + inboxes ], ) .mount("/assets", FileServer::from(relative!("assets")))) diff --git a/src/routes/federation.rs b/src/routes/federation.rs new file mode 100644 index 0000000..b72db6e --- /dev/null +++ b/src/routes/federation.rs @@ -0,0 +1,138 @@ +use crate::{ + api::{comment::get_comment, community::get_community, user::get_person, NameOrId, CLIENT}, + env::lemmy_backend, + error::ErrorPage, + rocket_uri_macro_view_forum, + rocket_uri_macro_view_profile, + rocket_uri_macro_view_topic, + routes::auth, +}; +use anyhow::Error; +use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; +use rocket::{ + http::CookieJar, + request::{FromRequest, Outcome}, + response::Redirect, + Either, +}; +use std::{path::PathBuf, str::FromStr}; + +type ReturnType = Result, ErrorPage>; + +#[get("/c/")] +pub async fn apub_community( + name: String, + accept: AcceptHeader, + cookies: &CookieJar<'_>, +) -> ReturnType { + if accept.0.starts_with("application/") { + return foward_apub_fetch(format!("{}/c/{}", lemmy_backend(), name), accept).await; + } + let community = get_community(NameOrId::Name(name), auth(cookies)).await?; + let f = community.community_view.community.id.0; + Ok(Either::Left(Redirect::to(uri!(view_forum( + f, + Some(1), + Option::::None + ))))) +} + +#[get("/u/")] +pub async fn apub_user(name: String, accept: AcceptHeader, cookies: &CookieJar<'_>) -> ReturnType { + if accept.0.starts_with("application/") { + return foward_apub_fetch(format!("{}/u/{}", lemmy_backend(), name), accept).await; + } + let user = get_person(NameOrId::Name(name), auth(cookies)).await?; + let u = user.person_view.person.id.0; + Ok(Either::Left(Redirect::to(uri!(view_profile(u))))) +} + +#[get("/post/")] +pub async fn apub_post(id: i32, accept: AcceptHeader) -> ReturnType { + if accept.0.starts_with("application/") { + return foward_apub_fetch(format!("{}/post/{}", lemmy_backend(), id), accept).await; + } + Ok(Either::Left(Redirect::to(uri!(view_topic(id, Some(1)))))) +} + +#[get("/comment/")] +pub async fn apub_comment(t: i32, accept: AcceptHeader, cookies: &CookieJar<'_>) -> ReturnType { + if accept.0.starts_with("application/") { + return foward_apub_fetch(format!("{}/comment/{}", lemmy_backend(), t), accept).await; + } + let comment = get_comment(t, auth(cookies)).await?; + // TODO: figure out actual page + Ok(Either::Left(Redirect::to(format!( + "/view_topic?t={}&page=1#p{}", + t, comment.comment_view.comment.id + )))) +} + +/// In case an activitypub object is being fetched, forward request to Lemmy backend +async fn foward_apub_fetch(url: String, accept: AcceptHeader) -> ReturnType { + let res = CLIENT + .get(url) + .header("accept", accept.0) + .send() + .await? + .text() + .await?; + Ok(Either::Right(res)) +} + +#[post("/", data = "")] +pub async fn inboxes( + path: PathBuf, + body: String, + headers: Headers<'_>, +) -> Result { + let url = format!("{}/{}", lemmy_backend(), path.to_str().unwrap()); + let headers = headers + .0 + .iter() + .map(|h| { + Ok(( + HeaderName::from_str(h.name.as_str())?, + HeaderValue::from_str(&h.value)?, + )) + }) + .collect::>()?; + + Ok(CLIENT + .post(url) + .headers(headers) + .body(body) + .send() + .await? + .text() + .await?) +} + +// Retrieve request headers +// https://github.com/SergioBenitez/Rocket/issues/178#issuecomment-953370904 +pub struct Headers<'r>(&'r rocket::http::HeaderMap<'r>); + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for Headers<'r> { + type Error = std::convert::Infallible; + + async fn from_request(req: &'r rocket::Request<'_>) -> Outcome { + Outcome::Success(Headers(req.headers())) + } +} + +pub struct AcceptHeader(String); + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for AcceptHeader { + type Error = std::convert::Infallible; + + async fn from_request(req: &'r rocket::Request<'_>) -> Outcome { + Outcome::Success(AcceptHeader( + req.headers() + .get_one("accept") + .unwrap_or_default() + .to_string(), + )) + } +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 0ff17ce..0f7c1cb 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,8 +1,8 @@ pub mod comment; pub mod community; +pub mod federation; pub mod post; pub mod private_message; -pub mod redirects; pub mod site; pub mod user; diff --git a/src/routes/redirects.rs b/src/routes/redirects.rs deleted file mode 100644 index 481c9d3..0000000 --- a/src/routes/redirects.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::{ - api::{comment::get_comment, community::get_community, user::get_person, NameOrId}, - error::ErrorPage, - rocket_uri_macro_view_forum, - rocket_uri_macro_view_profile, - rocket_uri_macro_view_topic, - routes::auth, -}; -use rocket::{http::CookieJar, response::Redirect}; - -#[get("/c/")] -pub async fn redirect_apub_community( - name: String, - cookies: &CookieJar<'_>, -) -> Result { - let community = get_community(NameOrId::Name(name), auth(cookies)).await?; - let f = community.community_view.community.id.0; - Ok(Redirect::to(uri!(view_forum( - f, - Some(1), - Option::::None - )))) -} - -#[get("/u/")] -pub async fn redirect_apub_user( - name: String, - cookies: &CookieJar<'_>, -) -> Result { - let user = get_person(NameOrId::Name(name), auth(cookies)).await?; - let u = user.person_view.person.id.0; - Ok(Redirect::to(uri!(view_profile(u)))) -} - -#[get("/post/")] -pub async fn redirect_apub_post(id: i32) -> Redirect { - Redirect::to(uri!(view_topic(id, Some(1)))) -} - -#[get("/comment/")] -pub async fn redirect_apub_comment(t: i32, cookies: &CookieJar<'_>) -> Result { - let comment = get_comment(t, auth(cookies)).await?; - // TODO: figure out actual page - Ok(Redirect::to(format!( - "/viewtopic?t={}&page=1#p{}", - t, comment.comment_view.comment.id - ))) -}