diff --git a/Cargo.lock b/Cargo.lock index defab1a..2f93e62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,6 +64,16 @@ dependencies = [ "tower-service", ] +[[package]] +name = "axum-client-ip" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfb5a3ddd6367075d50629546fb46710584016ae7704cd03b6d41cb5be82e5a" +dependencies = [ + "axum", + "forwarded-header-value", +] + [[package]] name = "axum-core" version = "0.3.2" @@ -187,6 +197,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "forwarded-header-value" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" +dependencies = [ + "nonempty", + "thiserror", +] + [[package]] name = "futures-channel" version = "0.3.25" @@ -463,6 +483,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -780,6 +806,26 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.4" @@ -1134,6 +1180,7 @@ name = "xssbook" version = "0.0.1" dependencies = [ "axum", + "axum-client-ip", "bytes", "lazy_static", "rand", diff --git a/Cargo.toml b/Cargo.toml index f74b284..9181b8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ tower-http = { version = "0.3.5", features = ["fs"] } tower-cookies = "0.8.0" tower = "0.4.13" bytes = "1.3.0" +axum-client-ip = "0.3.1" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["std"] } rusqlite = { version = "0.28.0", features = ["bundled"] } diff --git a/README.md b/README.md index 7f50320..36a06ba 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ The project is written in rust, so you can build it by running `cargo build --release` -Make sure where you run the binary from, you also move the public folder to. +Next, make sure where you are runing the binary from, that you copy the sources public folder to the same directory. The public folder is needed to server html, css, js, and font files. Finally, the site runs on port `8080`, so its recommended you put it behind a reverse proxy, or you could use a docker container and remap the outsite port (see below). @@ -37,6 +37,10 @@ to make the database persistant. Finally, before running the container run since docker will create a folder there otherwise and it won't work. +**reverse proxy** + +Finally if you are using docker by itself, a reverse proxy, or both, the ip send to the container likily will not be the correct ip. xssbook looks for headers `x-forwarded-for`, `x-real-ip`, and `forwarded` to check for proxies. So make sure to have those headers set. Or if your running just docker, you could also run the docker container on the host network instead of on the bridge network. + **license** This amazing project is licensed under the WTFPL \ No newline at end of file diff --git a/src/types/extract.rs b/src/types/extract.rs index f21c352..e79aa7a 100644 --- a/src/types/extract.rs +++ b/src/types/extract.rs @@ -1,14 +1,15 @@ -use std::{io::Read, net::SocketAddr}; +use std::io::Read; use axum::{ async_trait, body::HttpBody, - extract::{ConnectInfo, FromRequest, FromRequestParts}, + extract::{FromRequest, FromRequestParts}, headers::Cookie, http::{request::Parts, Request}, response::Response, BoxError, RequestExt, TypedHeader, }; +use axum_client_ip::ClientIp; use bytes::Bytes; use serde::de::DeserializeOwned; @@ -53,6 +54,7 @@ where } pub struct Log; + #[async_trait] impl FromRequest for Log where @@ -63,36 +65,8 @@ where { type Rejection = Response; - async fn from_request(mut req: Request, state: &S) -> Result { - let Ok(ConnectInfo(info)) = req.extract_parts::>().await else { - return Ok(Self) - }; - let method = req.method().clone(); - let path = req - .extensions() - .get::() - .map_or("", |path| path.0); - let uri = req.uri().clone(); - - let Ok(bytes) = Bytes::from_request(req, state).await else { - console::log(info.ip(), method.clone(), uri.clone(), Some(path.to_string()), None).await; - return Ok(Self) - }; - - let Ok(body) = String::from_utf8(bytes.bytes().flatten().collect()) else { - console::log(info.ip(), method.clone(), uri.clone(), Some(path.to_string()), None).await; - return Ok(Self) - }; - - console::log( - info.ip(), - method.clone(), - uri.clone(), - Some(path.to_string()), - Some(body.to_string()), - ) - .await; - + async fn from_request(req: Request, state: &S) -> Result { + parse_body(req, state).await?; Ok(Self) } } @@ -110,35 +84,12 @@ where { type Rejection = Response; - async fn from_request(mut req: Request, state: &S) -> Result { - let Ok(ConnectInfo(info)) = req.extract_parts::>().await else { - tracing::error!("Failed to read connection info"); - return Err(ResponseCode::InternalServerError.text("Failed to read connection info")); + async fn from_request(req: Request, state: &S) -> Result { + + let body = match parse_body(req, state).await { + Ok(body) => body, + Err(err) => return Err(err) }; - let method = req.method().clone(); - let path = req - .extensions() - .get::() - .map_or("", |path| path.0); - let uri = req.uri().clone(); - - let Ok(bytes) = Bytes::from_request(req, state).await else { - tracing::error!("Failed to read request body"); - return Err(ResponseCode::InternalServerError.text("Failed to read request body")); - }; - - let Ok(body) = String::from_utf8(bytes.bytes().flatten().collect()) else { - return Err(ResponseCode::BadRequest.text("Invalid utf8 body")) - }; - - console::log( - info.ip(), - method.clone(), - uri.clone(), - Some(path.to_string()), - Some(body.to_string()), - ) - .await; let Ok(value) = serde_json::from_str::(&body) else { return Err(ResponseCode::BadRequest.text("Invalid request body")) @@ -172,5 +123,45 @@ pub trait Check { } } +pub async fn parse_body(mut req: Request, state: &S) -> Result +where + B: HttpBody + Sync + Send + 'static, + B::Data: Send, + B::Error: Into, + S: Send + Sync +{ + let Ok(ClientIp(ip)) = req.extract_parts::().await else { + tracing::error!("Failed to read client ip"); + return Err(ResponseCode::InternalServerError.text("Failed to read client ip")); + }; + + let method = req.method().clone(); + let uri = req.uri().clone(); + let path = req + .extensions() + .get::() + .map_or("", |path| path.0); + + let Ok(bytes) = Bytes::from_request(req, state).await else { + tracing::error!("Failed to read request body"); + return Err(ResponseCode::InternalServerError.text("Failed to read request body")); + }; + + let Ok(body) = String::from_utf8(bytes.bytes().flatten().collect()) else { + return Err(ResponseCode::BadRequest.text("Invalid utf8 body")) + }; + + console::log( + ip, + method, + uri, + Some(path.to_string()), + Some(body.to_string()), + ) + .await; + + Ok(body) +} + #[derive(Clone)] pub struct RouterURI(pub &'static str);