From 8e5b7f172ac4cf3c09c8f91462ab2c9f8535497c Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 11 Jan 2020 19:20:37 +0100 Subject: [PATCH] Proxy pictshare requests (fixes #371) --- ansible/templates/nginx.conf | 1 + docker/dev/docker-compose.yml | 7 ++-- docker/lemmy.hjson | 2 + docker/prod/docker-compose.yml | 5 +-- server/Cargo.lock | 29 +++++++++++++ server/Cargo.toml | 1 + server/config/defaults.hjson | 2 + server/src/main.rs | 6 ++- server/src/routes/images.rs | 76 ++++++++++++++++++++++++++++++++++ server/src/routes/mod.rs | 14 +++++++ server/src/settings.rs | 1 + 11 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 server/src/routes/images.rs diff --git a/ansible/templates/nginx.conf b/ansible/templates/nginx.conf index 1fc6e6746..246f26910 100644 --- a/ansible/templates/nginx.conf +++ b/ansible/templates/nginx.conf @@ -70,6 +70,7 @@ server { proxy_cache_min_uses 5; } + # TODO: probably remove this (at least the proxy_pass) location /pictshare/ { proxy_pass http://0.0.0.0:8537/; proxy_set_header X-Real-IP $remote_addr; diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index eabd334d5..70dd5fbd5 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -14,6 +14,8 @@ services: build: context: ../../ dockerfile: docker/dev/Dockerfile + environment: + - RUST_LOG=actix_web=info ports: - "127.0.0.1:8536:8536" restart: always @@ -21,10 +23,9 @@ services: - ../lemmy.hjson:/config/config.hjson:ro depends_on: - lemmy_db - lemmy_pictshare: + - pictshare + pictshare: image: shtripok/pictshare:latest - ports: - - "127.0.0.1:8537:80" volumes: - lemmy_pictshare:/usr/share/nginx/html/data restart: always diff --git a/docker/lemmy.hjson b/docker/lemmy.hjson index 2ec00de50..48ec30dab 100644 --- a/docker/lemmy.hjson +++ b/docker/lemmy.hjson @@ -23,6 +23,8 @@ jwt_secret: "changeme" # The dir for the front end front_end_dir: "/app/dist" + # The url where pictshare is available (this should only be exposed to lemmy, not to the outside) + pictshare_url: "http://pictshare:80" # whether to enable activitypub federation. this feature is in alpha, do not enable in production, as might # cause problems like remote instances fetching and permanently storing bad data. federation_enabled: false diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index da2cc5248..bb0a35d96 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -19,10 +19,9 @@ services: - ./lemmy.hjson:/config/config.hjson:ro depends_on: - lemmy_db - lemmy_pictshare: + - pictshare + pictshare: image: shtripok/pictshare:latest - ports: - - "127.0.0.1:8537:80" volumes: - lemmy_pictshare:/usr/share/nginx/html/data restart: always diff --git a/server/Cargo.lock b/server/Cargo.lock index 16a51fab8..db38bcbfa 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -1206,6 +1206,16 @@ name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "idna" version = "0.2.0" @@ -1323,6 +1333,7 @@ dependencies = [ "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "strum 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)", "strum_macros 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1650,6 +1661,11 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -2503,6 +2519,16 @@ name = "untrusted" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "url" version = "2.1.0" @@ -2781,6 +2807,7 @@ dependencies = [ "checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" "checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" "checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" "checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" "checksum indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2" "checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" @@ -2827,6 +2854,7 @@ dependencies = [ "checksum openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)" = "465d16ae7fc0e313318f7de5cecf57b2fbe7511fd213978b457e1c96ff46736f" "checksum parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" "checksum parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" +"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" "checksum pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "94b90146c7216e4cb534069fb91366de4ea0ea353105ee45ed297e2d1619e469" "checksum pin-project-internal 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "44ca92f893f0656d3cba8158dd0f2b99b94de256a4a54e870bd6922fcc6c8355" @@ -2928,6 +2956,7 @@ dependencies = [ "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f" +"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" "checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" diff --git a/server/Cargo.toml b/server/Cargo.toml index 5d00d6ee5..6662de8d9 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -33,3 +33,4 @@ rss = "1.8.0" htmlescape = "0.3.1" config = "0.10.1" hjson = "0.8.2" +url = "1.7.1" diff --git a/server/config/defaults.hjson b/server/config/defaults.hjson index 0fabda0b0..34eb7b37e 100644 --- a/server/config/defaults.hjson +++ b/server/config/defaults.hjson @@ -24,6 +24,8 @@ jwt_secret: "changeme" # The dir for the front end front_end_dir: "../ui/dist" + # The url where pictshare is available (this should only be exposed to lemmy, not to the outside) + pictshare_url: "http://localhost:80" # whether to enable activitypub federation. this feature is in alpha, do not enable in production, as might # cause problems like remote instances fetching and permanently storing bad data. federation_enabled: false diff --git a/server/src/main.rs b/server/src/main.rs index 3ac07233e..ec40522d8 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -2,10 +2,12 @@ extern crate lemmy_server; #[macro_use] extern crate diesel_migrations; +use actix_web::middleware::Logger; use actix_web::*; use diesel::r2d2::{ConnectionManager, Pool}; use diesel::PgConnection; -use lemmy_server::routes::{federation, feeds, index, nodeinfo, webfinger, websocket}; +use env_logger; +use lemmy_server::routes::{federation, feeds, images, index, nodeinfo, webfinger, websocket}; use lemmy_server::settings::Settings; use std::io; @@ -38,8 +40,10 @@ async fn main() -> io::Result<()> { .wrap(middleware::Logger::default()) .data(pool.clone()) // The routes + .wrap(Logger::default()) .configure(federation::config) .configure(feeds::config) + .configure(images::config) .configure(index::config) .configure(nodeinfo::config) .configure(webfinger::config) diff --git a/server/src/routes/images.rs b/server/src/routes/images.rs new file mode 100644 index 000000000..24e696c81 --- /dev/null +++ b/server/src/routes/images.rs @@ -0,0 +1,76 @@ +use crate::settings::Settings; +use actix_web::{web, Error, HttpRequest, HttpResponse}; + +use actix_web::client::Client; +use url::Url; +use crate::routes::get_jwt_token_from_cookies; + +pub fn config(cfg: &mut web::ServiceConfig) { + cfg + .data(Client::new()) + .route("/pictshare/api/upload.php", web::post().to(upload)) + .route("/pictshare/{image}", web::get().to(get_image)) + .route( + "/pictshare/{resolution}/{image}", + web::get().to(get_thumbnail), + ); +} + +/// For uploads, check the jwt token to make sure the user is logged in. +async fn upload( + req: HttpRequest, + body: web::Bytes, + client: web::Data, +) -> Result { + match get_jwt_token_from_cookies(req.headers()) { + Ok(_) => forward(req, body, client, "/api/upload.php").await, + Err(_) => Ok(HttpResponse::Forbidden().finish()), + } +} + +async fn get_image( + req: HttpRequest, + body: web::Bytes, + client: web::Data, + path: web::Path, +) -> Result { + forward(req, body, client, &path.into_inner()).await +} + +async fn get_thumbnail( + req: HttpRequest, + body: web::Bytes, + client: web::Data, + path: web::Path<(String, String)>, +) -> Result { + forward(req, body, client, &format!("/{}/{}", path.0, path.1)).await +} + +/// Based on https://github.com/actix/examples/blob/master/http-proxy/src/main.rs +async fn forward( + req: HttpRequest, + body: web::Bytes, + client: web::Data, + path: &str, +) -> Result { + let mut new_url = match Url::parse(&Settings::get().pictshare_url) { + Ok(u) => u, + Err(_) => return Ok(HttpResponse::InternalServerError().finish()), + }; + new_url.set_path(path); + + let forwarded_req = client + .request_from(new_url.as_str(), req.head()) + .no_decompress(); + + let mut res = forwarded_req.send_body(body).await.map_err(Error::from)?; + + let mut client_resp = HttpResponse::build(res.status()); + // Remove `Connection` as per + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection#Directives + for (header_name, header_value) in res.headers().iter().filter(|(h, _)| *h != "connection") { + client_resp.header(header_name.clone(), header_value.clone()); + } + + Ok(client_resp.body(res.body().await?)) +} diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index 6556c8d58..41572e894 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -1,6 +1,20 @@ +use actix_web::http::HeaderMap; +use jsonwebtoken::TokenData; +use crate::db::user::Claims; + pub mod federation; pub mod feeds; +pub mod images; pub mod index; pub mod nodeinfo; pub mod webfinger; pub mod websocket; + +fn get_jwt_token_from_cookies(headers: &HeaderMap) -> Result, failure::Error> { + // TODO: this parsing will break if we add any new cookies + let jwt = headers.get("Cookie").unwrap().to_str()?; + match Claims::decode(&jwt.replace("jwt=", "")) { + Ok(o) => Ok(o), + Err(e) => Err(format_err!("{}", e)), + } +} diff --git a/server/src/settings.rs b/server/src/settings.rs index f4bb2a42a..d006775b4 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -18,6 +18,7 @@ pub struct Settings { pub rate_limit: RateLimitConfig, pub email: Option, pub federation_enabled: bool, + pub pictshare_url: String, } #[derive(Debug, Deserialize)]