This commit is contained in:
Matthieu Bessat 2024-10-15 12:10:46 +02:00
parent 66b3a99814
commit 98be8dd574
13 changed files with 329 additions and 46 deletions

116
Cargo.lock generated
View file

@ -245,6 +245,17 @@ dependencies = [
"tracing",
]
[[package]]
name = "axum-macros"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
]
[[package]]
name = "axum-template"
version = "2.4.0"
@ -337,9 +348,9 @@ checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
[[package]]
name = "cc"
version = "1.1.28"
version = "1.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1"
checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945"
dependencies = [
"shlex",
]
@ -796,6 +807,12 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "http-range-header"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a"
[[package]]
name = "httparse"
version = "1.9.5"
@ -906,9 +923,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "js-sys"
version = "0.3.71"
version = "0.3.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cb94a0ffd3f3ee755c20f7d8752f45cac88605a4dcf808abcff72873296ec7b"
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
dependencies = [
"wasm-bindgen",
]
@ -995,6 +1012,16 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "minauthator"
version = "0.1.0"
@ -1003,19 +1030,23 @@ dependencies = [
"argh",
"argon2",
"axum",
"axum-macros",
"axum-template",
"chrono",
"env_logger",
"fully_pub",
"log",
"minijinja",
"minijinja-embed",
"redis",
"serde",
"serde_json",
"sqlx",
"strum_macros",
"tokio",
"toml",
"totp-rs",
"tower-http",
"uuid",
]
@ -1028,6 +1059,12 @@ dependencies = [
"serde",
]
[[package]]
name = "minijinja-embed"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c76b834769fa7cfc97332d7c6b4025f264a41dfca1b9c0959af25611a7892310"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@ -1382,9 +1419,9 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.17"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
[[package]]
name = "ryu"
@ -1905,6 +1942,19 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]]
name = "toml"
version = "0.8.19"
@ -1968,6 +2018,31 @@ dependencies = [
"tracing",
]
[[package]]
name = "tower-http"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97"
dependencies = [
"bitflags",
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"http-range-header",
"httpdate",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
@ -2018,6 +2093,15 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicase"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.17"
@ -2116,9 +2200,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
version = "0.2.94"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef073ced962d62984fb38a36e5fdc1a2b23c9e0e1fa0689bb97afa4202ef6887"
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
dependencies = [
"cfg-if",
"once_cell",
@ -2127,9 +2211,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.94"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4bfab14ef75323f4eb75fa52ee0a3fb59611977fd3240da19b2cf36ff85030e"
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
dependencies = [
"bumpalo",
"log",
@ -2142,9 +2226,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.94"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7bec9830f60924d9ceb3ef99d55c155be8afa76954edffbb5936ff4509474e7"
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -2152,9 +2236,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.94"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c74f6e152a76a2ad448e223b0fc0b6b5747649c3d769cc6bf45737bf97d0ed6"
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [
"proc-macro2",
"quote",
@ -2165,9 +2249,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.94"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a42f6c679374623f295a8623adfe63d9284091245c3504bde47c17a3ce2777d9"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
[[package]]
name = "whoami"

View file

@ -13,11 +13,14 @@ strum_macros = "0.26"
uuid = { version = "1.8", features = ["serde", "v4"] }
argh = "0.1" # for CLI
# logging
# Async
tokio = { version = "1.40.0", features = ["rt-multi-thread"] }
# Logging
log = "0.4"
env_logger = "0.11"
# serialization
# Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.8"
@ -28,10 +31,17 @@ chrono = { version = "0.4", features = ["serde"] }
sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio", "chrono", "uuid"] }
redis = { version = "0.27.3", default-features = false, features = ["acl"] }
# web
# Web
axum = { version = "0.7.7", features = ["json"] }
axum-template = { version = "2.4.0", features = ["minijinja"] }
minijinja = { version = "2.1", features = ["builtins"] }
# to make work the static assets server
tower-http = { version = "0.6.1", features = ["fs"] }
# Auth utils
totp-rs = "5.6"
minijinja-embed = "2.3.1"
axum-macros = "0.4.2"
[build-dependencies]
minijinja-embed = "2.3.1"

View file

@ -1,10 +1,12 @@
use argh::FromArgs;
use anyhow::{anyhow, Context, Result};
use log::info;
use crate::{get_app_context, server::{start_http_server, ServerConfig}};
#[derive(Debug, FromArgs)]
/// Autotasker daemon
struct CliFlags {
struct ServerCliFlags {
/// path to YAML config file to use to configure autotasker
#[argh(option)]
config: Option<String>,
@ -25,4 +27,22 @@ struct CliFlags {
listen_port: u32
}
const DEFAULT_ASSETS_PATH: &'static str = &"/usr/local/lib/autotasker/assets";
pub async fn start_server_cli() -> Result<()> {
info!("Starting minauth");
let flags: ServerCliFlags = argh::from_env();
let (config, db_pool) = get_app_context(crate::StartAppConfig {
config_path: flags.config,
database_path: flags.database
}).await.context("Getting app context")?;
start_http_server(
ServerConfig {
assets_path: flags.static_assets.unwrap_or(DEFAULT_ASSETS_PATH.to_string()),
listen_host: flags.listen_host,
listen_port: flags.listen_port
},
config,
db_pool
).await
}

View file

@ -0,0 +1,17 @@
use axum::{extract::State, response::{Html, IntoResponse}};
use axum_macros::debug_handler;
use minijinja::context;
use crate::server::AppState;
#[debug_handler]
pub async fn home(
State(app_state): State<AppState>
) -> impl IntoResponse {
Html(
app_state.templating_env.get_template("pages/home.html").unwrap()
.render(context!())
.unwrap()
)
}

View file

@ -0,0 +1,26 @@
use axum::{extract::State, response::{Html, IntoResponse}};
use minijinja::context;
use crate::server::AppState;
pub async fn login_form(
State(app_state): State<AppState>
) -> impl IntoResponse {
Html(
app_state.templating_env.get_template("pages/home.html").unwrap()
.render(context!())
.unwrap()
)
}
pub async fn perform_login(
State(app_state): State<AppState>
) -> impl IntoResponse {
Html(
app_state.templating_env.get_template("pages/home.html").unwrap()
.render(context!())
.unwrap()
)
}

View file

@ -1,3 +1,4 @@
pub mod home;
pub mod authorize;
pub mod login;
pub mod register;

19
src/database.rs Normal file
View file

@ -0,0 +1,19 @@
use anyhow::{anyhow, Context, Result};
use sqlx::{sqlite::{SqliteConnectOptions, SqlitePoolOptions}, Pool, Sqlite, ConnectOptions};
use std::str::FromStr;
pub async fn prepare_database(sqlite_db_path: &str) -> Result<Pool<Sqlite>> {
let conn_str = format!("sqlite:{}", sqlite_db_path);
let pool = SqlitePoolOptions::new()
.max_connections(50)
.connect_with(
SqliteConnectOptions::from_str(&conn_str)?
.log_statements(log::LevelFilter::Trace)
)
.await
.context("could not connect to database_url")?;
Ok(pool)
}

View file

@ -1,20 +1,51 @@
pub mod models;
pub mod controllers;
pub mod router;
pub mod server;
pub mod database;
pub mod cli;
use std::fs;
use anyhow::{Result, Context, anyhow};
use database::prepare_database;
use log::info;
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
use sqlx::{ConnectOptions, Pool, Sqlite};
use models::config::Config;
use minijinja::{context, Environment};
const DEFAULT_DB_PATH: &'static str = &"/var/lib/autotasker/autotasker.db";
const DEFAULT_ASSETS_PATH: &'static str = &"/usr/local/lib/autotasker/assets";
const DEFAULT_CONFIG_PATH: &'static str = &"/etc/autotasker/config.yaml";
#[derive(Debug, Clone)]
pub struct AppState {
config: Config,
db: Pool<Sqlite>,
templating_env: Environment<'static>
fn get_config(path: String) -> Result<Config> {
let inp_def_yaml = fs::read_to_string(path)
.expect("Should have been able to read the the config file");
toml::from_str(&inp_def_yaml)
.map_err(|e| anyhow!("Failed to parse config, {:?}", e))
}
fn main() {
println!("Hello, world!");
struct StartAppConfig {
config_path: Option<String>,
database_path: Option<String>,
}
#[tokio::main]
async fn main() -> Result<()> {
cli::start_server_cli().await
}
async fn get_app_context(start_app_config: StartAppConfig) -> Result<(Config, Pool<Sqlite>)> {
env_logger::init();
let pool = prepare_database(
&start_app_config.database_path.unwrap_or(DEFAULT_DB_PATH.to_string())
).await.context("Could not prepare db")?;
let config_path = start_app_config.config_path.unwrap_or(DEFAULT_CONFIG_PATH.to_string());
info!("Using config file at {}", &config_path);
let config: Config = get_config(config_path).expect("Cannot get config");
Ok((config, pool))
}

10
src/router.rs Normal file
View file

@ -0,0 +1,10 @@
use axum::{routing::{get, post}, Router};
use crate::{controllers::ui, server::AppState};
pub fn build_router() -> Router<AppState> {
Router::new()
.route("/", get(ui::home::home))
.route("/login", get(ui::login::login_form))
.route("/login", post(ui::login::perform_login))
}

View file

@ -1,27 +1,18 @@
use fully_pub::fully_pub;
use tower_http::services::ServeDir;
use anyhow::{Result, Context, anyhow};
use log::info;
use minijinja::{context, Environment};
use sqlx::{Pool, Sqlite};
use crate::{models::config::Config, router::build_router};
fn get_config(path: String) -> Result<Config> {
let inp_def_yaml = fs::read_to_string(path)
.expect("Should have been able to read the the config file");
serde_yaml::from_str(&inp_def_yaml)
.map_err(|e| anyhow!("Failed to parse config, {:?}", e))
}
fn build_templating_env() -> Environment<'static> {
fn build_templating_env(config: &Config) -> Environment<'static> {
let mut templating_env = Environment::new();
let _ = templating_env
.add_template("layouts/base.html", include_str!("./templates/layouts/base.html"));
let _ = templating_env
.add_template("pages/home.html", include_str!("./templates/pages/home.html"));
let _ = templating_env
.add_template("pages/list_tasks.html", include_str!("./templates/pages/list_tasks.html"));
let _ = templating_env
.add_template("pages/list_task_runs.html", include_str!("./templates/pages/list_task_runs.html"));
let _ = templating_env
.add_template("pages/task_run_details.html", include_str!("./templates/pages/task_run_details.html"));
let _ = templating_env
.add_template("pages/run_task.html", include_str!("./templates/pages/run_task.html"));
// TODO: better loading with embed https://docs.rs/minijinja-embed/latest/minijinja_embed/
templating_env.add_global("gl", context! {
@ -32,3 +23,44 @@ fn build_templating_env() -> Environment<'static> {
templating_env
}
#[derive(Debug)]
#[fully_pub]
/// HTTP server arguments
pub struct ServerConfig {
listen_host: String,
listen_port: u32,
assets_path: String
}
#[derive(Debug, Clone)]
#[fully_pub]
pub struct AppState {
config: Config,
db: Pool<Sqlite>,
templating_env: Environment<'static>
}
pub async fn start_http_server(server_config: ServerConfig, config: Config, db_pool: Pool<Sqlite>) -> Result<()> {
// build state
let state = AppState {
templating_env: build_templating_env(&config),
config,
db: db_pool
};
// build routes
let services = build_router()
.nest_service(
"/assets",
ServeDir::new(server_config.assets_path)
)
.with_state(state);
let listen_addr = format!("{}:{}", server_config.listen_host, server_config.listen_port);
info!("Starting web server on http://{}", &listen_addr);
let listener = tokio::net::TcpListener::bind(listen_addr).await.unwrap();
axum::serve(listener, services).await.context("Axum serve")?;
Ok(())
}

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Minauth</title>
<link href="/assets/styles/simple.css" rel="stylesheet">
<link href="/assets/styles/app.css" rel="stylesheet">
</head>
<body>
<header>
Minauth
</header>
<main class="container">
{% block body %}{% endblock %}
</main>
<footer>
Minauth {{ gl.instance.version }}
</footer>
</body>
</html>

View file

@ -0,0 +1,11 @@
{% extends "layouts/base.html" %}
{% block body %}
<h1>Bienvenue sur Minauth</h1>
<p>
Minauth is free software under <a href="https://www.gnu.org/licenses/gpl-3.0.txt">GPLv3</a> licence.
You can find source code on a <a href="https://forge.lefuturiste.fr/mbess/minauth">self-hosted forge repository</a>.
</p>
{% endblock %}

View file