diff --git a/Cargo.lock b/Cargo.lock index 16a3c24..feae8ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index f07ae33..06670e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/cli.rs b/src/cli.rs index 3fa3375..2235eac 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -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, @@ -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 +} diff --git a/src/controllers/ui/home.rs b/src/controllers/ui/home.rs new file mode 100644 index 0000000..5d5c656 --- /dev/null +++ b/src/controllers/ui/home.rs @@ -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 +) -> impl IntoResponse { + Html( + app_state.templating_env.get_template("pages/home.html").unwrap() + .render(context!()) + .unwrap() + ) +} + diff --git a/src/controllers/ui/login.rs b/src/controllers/ui/login.rs index e69de29..96650e3 100644 --- a/src/controllers/ui/login.rs +++ b/src/controllers/ui/login.rs @@ -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 +) -> 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 +) -> impl IntoResponse { + Html( + app_state.templating_env.get_template("pages/home.html").unwrap() + .render(context!()) + .unwrap() + ) +} + + diff --git a/src/controllers/ui/mod.rs b/src/controllers/ui/mod.rs index 2eabef2..51ef0a3 100644 --- a/src/controllers/ui/mod.rs +++ b/src/controllers/ui/mod.rs @@ -1,3 +1,4 @@ +pub mod home; pub mod authorize; pub mod login; pub mod register; diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..45ca33a --- /dev/null +++ b/src/database.rs @@ -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> { + 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) +} + diff --git a/src/main.rs b/src/main.rs index 82caf68..f75cadf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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, - templating_env: Environment<'static> +fn get_config(path: String) -> Result { + 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, + database_path: Option, +} + +#[tokio::main] +async fn main() -> Result<()> { + cli::start_server_cli().await +} + +async fn get_app_context(start_app_config: StartAppConfig) -> Result<(Config, Pool)> { + 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)) } diff --git a/src/router.rs b/src/router.rs new file mode 100644 index 0000000..756ee7e --- /dev/null +++ b/src/router.rs @@ -0,0 +1,10 @@ +use axum::{routing::{get, post}, Router}; + +use crate::{controllers::ui, server::AppState}; + +pub fn build_router() -> Router { + Router::new() + .route("/", get(ui::home::home)) + .route("/login", get(ui::login::login_form)) + .route("/login", post(ui::login::perform_login)) +} diff --git a/src/server.rs b/src/server.rs index be266b9..72b11b1 100644 --- a/src/server.rs +++ b/src/server.rs @@ -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 { - 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, + templating_env: Environment<'static> +} + +pub async fn start_http_server(server_config: ServerConfig, config: Config, db_pool: Pool) -> 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(()) +} diff --git a/src/templates/layouts/base.html b/src/templates/layouts/base.html new file mode 100644 index 0000000..95532cf --- /dev/null +++ b/src/templates/layouts/base.html @@ -0,0 +1,22 @@ + + + + + + Minauth + + + + +
+ Minauth +
+
+ {% block body %}{% endblock %} +
+
+ Minauth {{ gl.instance.version }} +
+ + + diff --git a/src/templates/pages/home.html b/src/templates/pages/home.html new file mode 100644 index 0000000..cffdcd4 --- /dev/null +++ b/src/templates/pages/home.html @@ -0,0 +1,11 @@ +{% extends "layouts/base.html" %} +{% block body %} +

Bienvenue sur Minauth

+ +

+Minauth is free software under GPLv3 licence. + +You can find source code on a self-hosted forge repository. +

+{% endblock %} + diff --git a/src/templates/pages/login.html b/src/templates/pages/login.html new file mode 100644 index 0000000..e69de29