axum_js_advice/lib.rs
1//!
2//! # Axum JS Advice Middleware
3//!
4//! This project allows you to includes a custom middleware that injects a JavaScript alert in your axum application, advising users to disable JavaScript for enhanced security.
5//!
6//! ## Features
7//!
8//! - **Custom Middleware**: Adds an HTML `<script>` tag to responses, displaying a warning pop-up if JavaScript is enabled.
9//! - **Request and Response Buffering**: Reads and modifies HTTP request/response bodies to include the custom script.
10//! - **Minimal Setup**: Built with simplicity in mind using `axum` and `tokio`.
11//!
12//! ## How It Works
13//!
14//! The middleware intercepts responses and appends a JavaScript alert to the body. The alert advises users to disable JavaScript for their safety.
15//!
16//! ### Example Warning Script Injected:
17//! ```html
18//! <script>
19//! alert("Warning!\nYou have JavaScript enabled, you are putting yourself at risk!\nPlease disable it immediately!");
20//! </script>
21//! ```
22//!
23//! ### Usage
24//!
25//! ```bash
26//! cargo add axum_js_advice
27//! ```
28//!
29//! Then, add axum_js_advice() in your middleware layer, for example:
30//!
31//! ```rust
32//! use axum::{middleware, Router};
33//! use axum_js_advice::js_advice;
34//!
35//! #[tokio::main]
36//! async fn main() {
37//! let app = Router::new()
38//! .route(
39//! "/",
40//! axum::routing::get(|| async move { axum::response::Html("Hello from `POST /`") }),
41//! )
42//! //.layer(middleware::from_fn(OTHER_MIDDLEWARE_RULE))
43//! .layer(middleware::from_fn(js_advice));
44//!
45//! let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
46//! .await
47//! .unwrap();
48//! println!("listening on {}", listener.local_addr().unwrap());
49//! axum::serve(listener, app).await.unwrap();
50//! }
51//! ```
52//!
53//! ## Example Output
54//!
55//! ```bash
56//! cargo run --example middleware
57//! ```
58//!
59//! When visiting `http://127.0.0.1:3000/`, the response will include:
60//!
61//! ```html
62//! <script>
63//! alert("Warning!\nYou have JavaScript enabled, you are putting yourself at risk!\nPlease disable it immediately!");
64//! </script>
65//! Hello from `POST /`
66//! ```
67//!
68//! If you have JavaScript enabled, a reminder will pop up asking you to disable it; otherwise, you can browse freely.
69//!
70//! ## License
71//!
72//! This project is licensed under the GPL License. See the [LICENSE](./LICENSE) file for details.
73//!
74
75use http_body_util::BodyExt;
76
77/// Middleware fn to add a pop-up of JS advice
78pub async fn js_advice(
79 req: axum::extract::Request,
80 next: axum::middleware::Next,
81) -> Result<impl axum::response::IntoResponse, (axum::http::StatusCode, String)> {
82 let (parts, body) = req.into_parts();
83 let bytes = buffer("request", body).await?;
84 let req = axum::extract::Request::from_parts(parts, axum::body::Body::from(bytes));
85 let res = next.run(req).await;
86 let (parts, body) = res.into_parts();
87 let raw_response = buffer("response", body).await?;
88 if parts
89 .headers
90 .get("content-type")
91 .unwrap_or(&axum::http::header::HeaderValue::from_str("content-type").unwrap())
92 .to_str()
93 .unwrap_or("")
94 .contains("text/html")
95 {
96 let bytes_response = [js_advice_html().into(), raw_response.clone()].concat();
97 let res =
98 axum::response::Response::from_parts(parts, axum::body::Body::from(bytes_response));
99 return Ok(res);
100 }
101 let res = axum::response::Response::from_parts(parts, axum::body::Body::from(raw_response));
102 Ok(res)
103}
104
105/// Return buffer | StatusCode from a given body
106async fn buffer<B>(
107 direction: &str,
108 body: B,
109) -> Result<axum::body::Bytes, (axum::http::StatusCode, String)>
110where
111 B: axum::body::HttpBody<Data = axum::body::Bytes>,
112 B::Error: std::fmt::Display,
113{
114 let bytes = match body.collect().await {
115 Ok(collected) => collected.to_bytes(),
116 Err(err) => {
117 return Err((
118 axum::http::StatusCode::BAD_REQUEST,
119 format!("failed to read {direction} body: {err}"),
120 ));
121 }
122 };
123 Ok(bytes)
124}
125
126/// HTML minified
127fn js_advice_html() -> &'static str {
128 r#"<script>alert("Warning!\nYou have JavaScript enabled, you are putting yourself at risk!\nPlease disable it immediately!")</script>"#
129}