use axum::http::{header, HeaderMap, HeaderValue, StatusCode};
use axum::response::{IntoResponse, Response};
use axum::routing::get;
use axum::Router;
const INDEX_HTML: &str = include_str!("../ui/index.html");
const APP_JS: &str = include_str!("../ui/app.js");
const APP_CSS: &str = include_str!("../ui/app.css");
const FAVICON_SVG: &str = include_str!("../ui/favicon.svg");
pub const CSP: &str = "default-src 'none'; script-src 'self'; style-src 'self'; \
img-src 'self' data:; connect-src 'self'; font-src 'self'; base-uri 'none'; \
form-action 'none'; frame-ancestors 'none'";
pub fn router() -> Router {
Router::new()
.route("/", get(root))
.route("/ui", get(|| async { redirect("/ui/") }))
.route(
"/ui/",
get(|| async { asset("text/html; charset=utf-8", INDEX_HTML) }),
)
.route(
"/ui/index.html",
get(|| async { asset("text/html; charset=utf-8", INDEX_HTML) }),
)
.route(
"/ui/app.js",
get(|| async { asset("text/javascript; charset=utf-8", APP_JS) }),
)
.route(
"/ui/app.css",
get(|| async { asset("text/css; charset=utf-8", APP_CSS) }),
)
.route(
"/ui/favicon.svg",
get(|| async { asset("image/svg+xml", FAVICON_SVG) }),
)
.route("/ui/{*rest}", get(not_found))
}
fn security_headers(headers: &mut HeaderMap) {
let set = |h: &mut HeaderMap, k: &'static str, v: &'static str| {
h.insert(k, HeaderValue::from_static(v));
};
set(headers, "content-security-policy", CSP);
set(headers, "x-content-type-options", "nosniff");
set(headers, "x-frame-options", "DENY");
set(headers, "referrer-policy", "no-referrer");
set(headers, "cross-origin-opener-policy", "same-origin");
set(headers, "cross-origin-resource-policy", "same-origin");
set(
headers,
"permissions-policy",
"camera=(), microphone=(), geolocation=(), payment=()",
);
set(headers, "cache-control", "no-cache");
}
fn asset(content_type: &'static str, body: &'static str) -> Response {
let mut resp = (StatusCode::OK, body).into_response();
resp.headers_mut()
.insert(header::CONTENT_TYPE, HeaderValue::from_static(content_type));
security_headers(resp.headers_mut());
resp
}
fn redirect(to: &'static str) -> Response {
let mut resp = StatusCode::FOUND.into_response();
resp.headers_mut()
.insert(header::LOCATION, HeaderValue::from_static(to));
security_headers(resp.headers_mut());
resp
}
async fn not_found() -> Response {
let mut resp = (StatusCode::NOT_FOUND, "not found\n").into_response();
resp.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("text/plain; charset=utf-8"),
);
security_headers(resp.headers_mut());
resp
}
async fn root(headers: HeaderMap) -> Response {
let html = headers
.get_all(header::ACCEPT)
.iter()
.filter_map(|v| v.to_str().ok())
.any(|v| v.contains("text/html"));
if html {
return redirect("/ui/");
}
(
StatusCode::NOT_FOUND,
axum::Json(serde_json::json!({ "error": {
"code": "NOT_FOUND",
"message": "no such endpoint; the API is under /v1/ and the web UI under /ui/",
}})),
)
.into_response()
}