1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
use axum::{
    extract::{ConnectInfo, State},
    http::{header::SET_COOKIE, HeaderMap, HeaderValue},
    response::{Html, IntoResponse},
    routing::get,
    Router,
};
use std::net::SocketAddr;

pub mod api;

/// Keep Size small, so we dont have to Clone much for each request.
#[derive(Clone)]
struct UiApiToken {
    secret_token: String,
}

pub fn router(secret_token: String) -> Router {
    let token = UiApiToken { secret_token };
    Router::new().route("/", get(ui)).with_state(token)
}

async fn ui(
    ConnectInfo(addr): ConnectInfo<SocketAddr>,
    headers: HeaderMap,
    State(token): State<UiApiToken>,
) -> impl IntoResponse {
    const X_FORWARDED_FOR: &'_ str = "X-Forwarded-For";
    if !addr.ip().is_loopback()
        || headers.contains_key(axum::http::header::FORWARDED)
        || headers.contains_key(X_FORWARDED_FOR)
    {
        return Html(
            r#"<!DOCTYPE html>
<html>
<body>
Ui is only accessible from 127.0.0.1. Usage of proxies is forbidden.
</body>
</html>
        "#
            .to_string(),
        )
        .into_response();
    }

    let js = include_str!("./ui.js");
    let css = include_str!("./ui.css");
    let inner = include_str!("./ui.html");

    let mut response = Html(format!(
        r#"<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
{js}
</script>
<style>
{css}
</style>
</head>
<body>
{inner}
</body>
</html>"#
    ))
    .into_response();

    let cookie = format!("X-Secret-Token={}; SameSite=Strict", token.secret_token);

    //Note: at this point we give a user our secret for the Api, this is only
    // intended for local users, protect this route against the whole internet
    response.headers_mut().insert(
        SET_COOKIE,
        HeaderValue::from_str(&cookie).expect("An invalid secret-token for ui was provided"),
    );
    response
}