mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2025-02-25 18:55:32 -06:00
Fully ported login system, including anonymous users, first user creation, login, logout and user display.
This commit is contained in:
parent
26d5250472
commit
a3ce3384ee
111
src/rust/Cargo.lock
generated
111
src/rust/Cargo.lock
generated
@ -17,6 +17,41 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aead"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aes"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cipher",
|
||||||
|
"cpufeatures",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aes-gcm"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
|
||||||
|
dependencies = [
|
||||||
|
"aead",
|
||||||
|
"aes",
|
||||||
|
"cipher",
|
||||||
|
"ctr",
|
||||||
|
"ghash",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.8.11"
|
version = "0.8.11"
|
||||||
@ -316,6 +351,29 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-extra"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733"
|
||||||
|
dependencies = [
|
||||||
|
"axum 0.7.5",
|
||||||
|
"axum-core 0.4.3",
|
||||||
|
"bytes",
|
||||||
|
"cookie",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.1.0",
|
||||||
|
"http-body 1.0.0",
|
||||||
|
"http-body-util",
|
||||||
|
"mime",
|
||||||
|
"pin-project-lite",
|
||||||
|
"serde",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.73"
|
version = "0.3.73"
|
||||||
@ -650,7 +708,11 @@ version = "0.18.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aes-gcm",
|
||||||
|
"base64 0.22.1",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
|
"rand",
|
||||||
|
"subtle",
|
||||||
"time",
|
"time",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
@ -790,6 +852,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
|
"rand_core",
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -814,6 +877,15 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctr"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
||||||
|
dependencies = [
|
||||||
|
"cipher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ctrlc"
|
name = "ctrlc"
|
||||||
version = "3.4.4"
|
version = "3.4.4"
|
||||||
@ -1246,6 +1318,16 @@ dependencies = [
|
|||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ghash"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
|
||||||
|
dependencies = [
|
||||||
|
"opaque-debug",
|
||||||
|
"polyval",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.29.0"
|
version = "0.29.0"
|
||||||
@ -2031,6 +2113,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum 0.7.5",
|
"axum 0.7.5",
|
||||||
|
"axum-extra",
|
||||||
"bincode",
|
"bincode",
|
||||||
"csv",
|
"csv",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
@ -2406,6 +2489,12 @@ version = "11.1.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.64"
|
version = "0.10.64"
|
||||||
@ -2580,6 +2669,18 @@ dependencies = [
|
|||||||
"plotters-backend",
|
"plotters-backend",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "polyval"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"opaque-debug",
|
||||||
|
"universal-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@ -4056,6 +4157,16 @@ version = "0.2.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
|
checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "universal-hash"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -66,6 +66,10 @@ impl WebUsers {
|
|||||||
Ok(filename)
|
Ok(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.users.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
fn save_to_disk(&self) -> Result<(), AuthenticationError> {
|
fn save_to_disk(&self) -> Result<(), AuthenticationError> {
|
||||||
let path = Self::path()?;
|
let path = Self::path()?;
|
||||||
let new_contents = toml_edit::ser::to_string(&self);
|
let new_contents = toml_edit::ser::to_string(&self);
|
||||||
|
@ -39,6 +39,7 @@ ip_network = "0"
|
|||||||
zerocopy = {version = "0.6.1", features = [ "simd" ] }
|
zerocopy = {version = "0.6.1", features = [ "simd" ] }
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
axum = { version = "0.7.5", features = ["ws"] }
|
axum = { version = "0.7.5", features = ["ws"] }
|
||||||
|
axum-extra = { version = "0.9.3", features = ["cookie", "cookie-private"] }
|
||||||
tower-http = { version = "0.5.2", features = ["fs"] }
|
tower-http = { version = "0.5.2", features = ["fs"] }
|
||||||
strum = { version = "0.26.3", features = ["derive"] }
|
strum = { version = "0.26.3", features = ["derive"] }
|
||||||
|
|
||||||
|
@ -3,5 +3,6 @@ mod static_pages;
|
|||||||
mod template;
|
mod template;
|
||||||
mod ws;
|
mod ws;
|
||||||
mod local_api;
|
mod local_api;
|
||||||
|
mod auth;
|
||||||
|
|
||||||
pub use run::spawn_webserver;
|
pub use run::spawn_webserver;
|
137
src/rust/lqosd/src/node_manager/auth.rs
Normal file
137
src/rust/lqosd/src/node_manager/auth.rs
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
//! Provides authentication for the Node Manager.
|
||||||
|
//! This is designed to be broadly compatible with the original
|
||||||
|
//! cookie-based system, but now uses an Axum layer to be largely
|
||||||
|
//! invisible.
|
||||||
|
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::Json;
|
||||||
|
use axum::response::{Html, Response};
|
||||||
|
use axum_extra::extract::cookie::Cookie;
|
||||||
|
use axum_extra::extract::CookieJar;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use lqos_config::{UserRole, WebUsers};
|
||||||
|
|
||||||
|
const COOKIE_PATH: &str = "User-Token";
|
||||||
|
|
||||||
|
static WEB_USERS : Lazy<Mutex<Option<WebUsers>>> = Lazy::new(|| Mutex::new(None));
|
||||||
|
|
||||||
|
pub async fn get_username(jar: &CookieJar) -> String {
|
||||||
|
let lock = WEB_USERS.lock().await;
|
||||||
|
if let Some(users) = &*lock {
|
||||||
|
if let Some(token) = jar.get(COOKIE_PATH) {
|
||||||
|
return users.get_username(token.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Anonymous".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum LoginResult {
|
||||||
|
Admin,
|
||||||
|
ReadOnly,
|
||||||
|
Denied,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn check_login(jar: &CookieJar, users: &WebUsers) -> LoginResult {
|
||||||
|
if let Some(token) = jar.get(COOKIE_PATH) {
|
||||||
|
// Validate the token
|
||||||
|
return match users.get_role_from_token(token.value()).unwrap() {
|
||||||
|
UserRole::ReadOnly => LoginResult::ReadOnly,
|
||||||
|
UserRole::Admin => LoginResult::Admin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LoginResult::Denied
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks an incoming request for a User-Token cookie. If found,
|
||||||
|
/// it validates the request against the web users file. If the
|
||||||
|
/// web users file isn't found, it redirects to 'first run'. If
|
||||||
|
/// it is found, the token is checked (and redirected to login if
|
||||||
|
/// it isn't good). Finally, the user's role is injected into
|
||||||
|
/// the middleware.
|
||||||
|
pub async fn auth_layer(
|
||||||
|
jar: CookieJar,
|
||||||
|
mut req: axum::extract::Request,
|
||||||
|
next: axum::middleware::Next,
|
||||||
|
) -> Result<Response, Html<&'static str>> {
|
||||||
|
const BOUNCE: &str = "<html><body><script>window.location.href = 'login.html';</script></body></html>";
|
||||||
|
const FIRST_RUN: &str = "<html><body><script>window.location.href = 'first-run.html';</script></body></html>";
|
||||||
|
|
||||||
|
let mut lock = WEB_USERS.lock().await;
|
||||||
|
if lock.is_none() {
|
||||||
|
// No lock - let's see if there's a file to use?
|
||||||
|
if WebUsers::does_users_file_exist().unwrap() {
|
||||||
|
// It exists - we load it
|
||||||
|
let users = WebUsers::load_or_create().unwrap();
|
||||||
|
*lock = Some(users);
|
||||||
|
} else {
|
||||||
|
// No users file - redirect to first run
|
||||||
|
return Err(Html(FIRST_RUN));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(users) = &*lock {
|
||||||
|
let login_result = check_login(&jar, users).await;
|
||||||
|
return match login_result {
|
||||||
|
LoginResult::Admin | LoginResult::ReadOnly => {
|
||||||
|
req.extensions_mut().insert(login_result);
|
||||||
|
Ok(next.run(req).await)
|
||||||
|
}
|
||||||
|
LoginResult::Denied => Err(Html(BOUNCE)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Err(Html(BOUNCE))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct LoginAttempt {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn try_login(
|
||||||
|
jar: CookieJar,
|
||||||
|
Json(login) : Json<LoginAttempt>,
|
||||||
|
) -> Result<(CookieJar, StatusCode), StatusCode> {
|
||||||
|
let users = WEB_USERS.lock().await;
|
||||||
|
if let Some(users) = &*users {
|
||||||
|
return match users.login(&login.username, &login.password) {
|
||||||
|
Ok(token) => {
|
||||||
|
Ok((jar.add(Cookie::new(COOKIE_PATH, token)), StatusCode::OK))
|
||||||
|
}
|
||||||
|
Err(..) => {
|
||||||
|
if users.do_we_allow_anonymous() {
|
||||||
|
Ok((jar, StatusCode::OK))
|
||||||
|
} else {
|
||||||
|
Err(StatusCode::UNAUTHORIZED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(StatusCode::UNAUTHORIZED)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct FirstUser {
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
allow_anonymous: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn first_user(
|
||||||
|
jar: CookieJar,
|
||||||
|
Json(new_user) : Json<FirstUser>
|
||||||
|
) -> (CookieJar, StatusCode) {
|
||||||
|
let mut users = WebUsers::load_or_create().unwrap();
|
||||||
|
users.allow_anonymous(new_user.allow_anonymous).unwrap();
|
||||||
|
let token = users.add_or_update_user(&new_user.username, &new_user.password, UserRole::Admin).unwrap();
|
||||||
|
let mut lock = WEB_USERS.lock().await;
|
||||||
|
*lock = Some(users);
|
||||||
|
(
|
||||||
|
jar.add(Cookie::new(COOKIE_PATH, token)),
|
||||||
|
StatusCode::OK
|
||||||
|
)
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
scripts=( index.js template.js )
|
scripts=( index.js template.js login.js first-run.js )
|
||||||
for script in "${scripts[@]}"
|
for script in "${scripts[@]}"
|
||||||
do
|
do
|
||||||
echo "Building {$script}"
|
echo "Building {$script}"
|
||||||
|
31
src/rust/lqosd/src/node_manager/js_build/src/first-run.js
Normal file
31
src/rust/lqosd/src/node_manager/js_build/src/first-run.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
$("#btnCreateUser").on('click', () => {
|
||||||
|
let username = $("#username").val();
|
||||||
|
let password = $("#password").val();
|
||||||
|
let anon = document.getElementById("allowAnonymous").checked;
|
||||||
|
if (username === "") {
|
||||||
|
alert("You must enter a username");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (password === "") {
|
||||||
|
alert("You must enter a password");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let login = {
|
||||||
|
allow_anonymous: anon,
|
||||||
|
username: username,
|
||||||
|
password: password
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: "/firstLogin",
|
||||||
|
data: JSON.stringify(login),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: () => {
|
||||||
|
window.location.href = "/index.html";
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
alert("Something went wrong");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
30
src/rust/lqosd/src/node_manager/js_build/src/login.js
Normal file
30
src/rust/lqosd/src/node_manager/js_build/src/login.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
$("#btnLogin").on('click', () => {
|
||||||
|
let username = $("#username").val();
|
||||||
|
let password = $("#password").val();
|
||||||
|
if (username === "") {
|
||||||
|
alert("You must enter a username");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (password === "") {
|
||||||
|
alert("You must enter a password");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let login = {
|
||||||
|
username: username,
|
||||||
|
password: password
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: "/doLogin",
|
||||||
|
data: JSON.stringify(login),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: () => {
|
||||||
|
window.location.href = "/index.html";
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
alert("Login Incorrect");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
@ -45,5 +45,21 @@ function getDeviceCounts() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initLogout() {
|
||||||
|
$("#btnLogout").on('click', () => {
|
||||||
|
console.log("Logout");
|
||||||
|
const cookies = document.cookie.split(";");
|
||||||
|
|
||||||
|
for (let i = 0; i < cookies.length; i++) {
|
||||||
|
const cookie = cookies[i];
|
||||||
|
const eqPos = cookie.indexOf("=");
|
||||||
|
const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
|
||||||
|
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
|
||||||
|
}
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initLogout();
|
||||||
initDayNightMode();
|
initDayNightMode();
|
||||||
getDeviceCounts();
|
getDeviceCounts();
|
@ -4,10 +4,10 @@ use log::info;
|
|||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use axum::response::Redirect;
|
use axum::response::Redirect;
|
||||||
use axum::routing::get;
|
use axum::routing::{get, post};
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
use lqos_config::load_config;
|
use lqos_config::load_config;
|
||||||
use crate::node_manager::{static_pages::{static_routes, vendor_route}, ws::websocket_router};
|
use crate::node_manager::{auth, static_pages::{static_routes, vendor_route}, ws::websocket_router};
|
||||||
use crate::node_manager::local_api::local_api;
|
use crate::node_manager::local_api::local_api;
|
||||||
|
|
||||||
/// Launches the Axum webserver to take over node manager duties.
|
/// Launches the Axum webserver to take over node manager duties.
|
||||||
@ -26,10 +26,12 @@ pub async fn spawn_webserver() -> Result<()> {
|
|||||||
if !static_path.exists() {
|
if !static_path.exists() {
|
||||||
bail!("Static path not found for webserver (vin/static2/");
|
bail!("Static path not found for webserver (vin/static2/");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct the router from parts
|
// Construct the router from parts
|
||||||
let router = Router::new()
|
let router = Router::new()
|
||||||
.route("/", get(redirect_to_index))
|
.route("/", get(redirect_to_index))
|
||||||
|
.route("/doLogin", post(auth::try_login))
|
||||||
|
.route("/firstLogin", post(auth::first_user))
|
||||||
.nest("/websocket/", websocket_router())
|
.nest("/websocket/", websocket_router())
|
||||||
.nest("/vendor", vendor_route()?) // Serve /vendor as purely static
|
.nest("/vendor", vendor_route()?) // Serve /vendor as purely static
|
||||||
.nest("/", static_routes()?)
|
.nest("/", static_routes()?)
|
||||||
|
70
src/rust/lqosd/src/node_manager/static2/first-run.html
Normal file
70
src/rust/lqosd/src/node_manager/static2/first-run.html
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-bs-theme="auto">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>LibreQoS Node Manager</title>
|
||||||
|
<link href="vendor/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<script src="vendor/jquery-3.7.1.min.js"></script>
|
||||||
|
<script src="vendor/echarts.min.js"></script>
|
||||||
|
<script src="vendor/echarts-gl.min.js"></script>
|
||||||
|
<script src="vendor/4c979e6ebb.js"></script>
|
||||||
|
<link href="./node_manager.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="container" class="pad4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-4"></div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="card bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">First Login</h5>
|
||||||
|
<p>
|
||||||
|
No <em>lqusers.toml</em> file was found. This is probably the first time you've run
|
||||||
|
the LibreQoS web system. If it isn't, then please check permissions on that file and
|
||||||
|
use the "bin/lqusers" command to verify that your system is working.
|
||||||
|
</p>
|
||||||
|
<p class="alert alert-warning" role="alert">
|
||||||
|
This site will use a cookie to store your identification. If that's not ok,
|
||||||
|
please don't use the site.
|
||||||
|
</p>
|
||||||
|
<p>Let's create a new user, and set some parameters:</p>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<input class="form-check-input" type="checkbox" value="" id="allowAnonymous">
|
||||||
|
<label class="form-check-label" for="allowAnonymous">
|
||||||
|
Allow anonymous users to view (but not change) settings.
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>
|
||||||
|
Your Username
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="username" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Your password</td>
|
||||||
|
<td><input type="password" id="password" /></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<a class="btn btn-primary" id="btnCreateUser">Create User Account</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="first-run.js"></script>
|
||||||
|
|
||||||
|
<footer class="justify-content-center">
|
||||||
|
© Copyright 2022-2024, LibreQoE LLC
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="vendor/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
51
src/rust/lqosd/src/node_manager/static2/login.html
Normal file
51
src/rust/lqosd/src/node_manager/static2/login.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-bs-theme="auto">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>LibreQoS Node Manager</title>
|
||||||
|
<link href="vendor/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<script src="vendor/jquery-3.7.1.min.js"></script>
|
||||||
|
<script src="vendor/echarts.min.js"></script>
|
||||||
|
<script src="vendor/echarts-gl.min.js"></script>
|
||||||
|
<script src="vendor/4c979e6ebb.js"></script>
|
||||||
|
<link href="./node_manager.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="container" class="pad4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-4"></div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="card bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Login</h5>
|
||||||
|
<p>Please enter a username and password to access LibreQoS.</p>
|
||||||
|
<p>You can control access locally with <em>bin/lqusers</em> from the console.</p>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<td>Username</td>
|
||||||
|
<td><input type="text" id="username" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Password</td>
|
||||||
|
<td><input type="password" id="password" /></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<a class="btn btn-primary" id="btnLogin">Login</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="login.js"></script>
|
||||||
|
|
||||||
|
<footer class="justify-content-center">
|
||||||
|
© Copyright 2022-2024, LibreQoE LLC
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="vendor/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -102,9 +102,15 @@
|
|||||||
<hr />
|
<hr />
|
||||||
<!-- User Info -->
|
<!-- User Info -->
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link">
|
<div class="dropdown">
|
||||||
<i class="fa fa-user"></i> Herbert
|
<button class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
</a>
|
<i class="fa fa-user-circle"></i> <span id="username">%%USERNAME%%</span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a class="dropdown-item" href="#" id="btnLogout">Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,6 +3,7 @@ use axum::Router;
|
|||||||
use tower_http::services::{ServeDir, ServeFile};
|
use tower_http::services::{ServeDir, ServeFile};
|
||||||
use lqos_config::load_config;
|
use lqos_config::load_config;
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
use crate::node_manager::auth::auth_layer;
|
||||||
use crate::node_manager::template::apply_templates;
|
use crate::node_manager::template::apply_templates;
|
||||||
|
|
||||||
pub(super) fn vendor_route() -> Result<Router> {
|
pub(super) fn vendor_route() -> Result<Router> {
|
||||||
@ -46,6 +47,7 @@ pub(super) fn static_routes() -> Result<Router> {
|
|||||||
router = router.route_service(&format!("/{page}"), ServeFile::new(path));
|
router = router.route_service(&format!("/{page}"), ServeFile::new(path));
|
||||||
}
|
}
|
||||||
router = router
|
router = router
|
||||||
|
.route_layer(axum::middleware::from_fn(auth_layer))
|
||||||
.route_layer(axum::middleware::from_fn(apply_templates));
|
.route_layer(axum::middleware::from_fn(apply_templates));
|
||||||
|
|
||||||
Ok(router)
|
Ok(router)
|
||||||
|
@ -6,9 +6,12 @@ use axum::body::{Body, to_bytes};
|
|||||||
use axum::http::{HeaderValue, Request, Response, StatusCode};
|
use axum::http::{HeaderValue, Request, Response, StatusCode};
|
||||||
use axum::middleware::Next;
|
use axum::middleware::Next;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
|
use axum_extra::extract::CookieJar;
|
||||||
use lqos_config::load_config;
|
use lqos_config::load_config;
|
||||||
|
use crate::node_manager::auth::get_username;
|
||||||
|
|
||||||
pub async fn apply_templates(
|
pub async fn apply_templates(
|
||||||
|
jar: CookieJar,
|
||||||
req: Request<axum::body::Body>,
|
req: Request<axum::body::Body>,
|
||||||
next: Next,
|
next: Next,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
@ -27,13 +30,18 @@ pub async fn apply_templates(
|
|||||||
std::fs::read_to_string(path).unwrap()
|
std::fs::read_to_string(path).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Update the displayed username
|
||||||
|
let username = get_username(&jar).await;
|
||||||
|
let template_text = template_text.replace("%%USERNAME%%", &username);
|
||||||
|
|
||||||
let res = next.run(req).await;
|
let res = next.run(req).await;
|
||||||
|
|
||||||
if apply_template {
|
if apply_template {
|
||||||
let (mut res_parts, res_body) = res.into_parts();
|
let (mut res_parts, res_body) = res.into_parts();
|
||||||
let bytes = to_bytes(res_body, 1_000_000).await.unwrap();
|
let bytes = to_bytes(res_body, 1_000_000).await.unwrap();
|
||||||
let byte_string = String::from_utf8_lossy(&bytes).to_string();
|
let byte_string = String::from_utf8_lossy(&bytes).to_string();
|
||||||
let byte_string = template_text.replace("%%BODY%%", &byte_string);
|
let byte_string = template_text
|
||||||
|
.replace("%%BODY%%", &byte_string);
|
||||||
if let Some(length) = res_parts.headers.get_mut("content-length") {
|
if let Some(length) = res_parts.headers.get_mut("content-length") {
|
||||||
*length = HeaderValue::from(byte_string.len());
|
*length = HeaderValue::from(byte_string.len());
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user