From 1f852ef219a701c660ebfe60e0cc7dbf06cc043c Mon Sep 17 00:00:00 2001 From: Simon Bruder Date: Wed, 24 Jul 2024 11:45:06 +0200 Subject: [PATCH] Use clap for configuration --- Cargo.lock | 55 +++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/config.rs | 34 +++++++++++++++++++++++++++ src/frontend/auth.rs | 8 +++---- src/lib.rs | 3 +++ src/main.rs | 28 +++++++++++----------- 6 files changed, 109 insertions(+), 20 deletions(-) create mode 100644 src/config.rs diff --git a/Cargo.lock b/Cargo.lock index 99c9ad8..44fd886 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -540,6 +540,46 @@ dependencies = [ "inout", ] +[[package]] +name = "clap" +version = "4.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6b81fb3c84f5563d509c59b5a48d935f689e993afa90fe39047f05adef9142" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca6706fd5224857d9ac5eb9355f6683563cc0541c7cd9d014043b57cbec78ac" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "clap_lex" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" + [[package]] name = "colorchoice" version = "1.0.1" @@ -1016,6 +1056,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hex" version = "0.4.3" @@ -1170,6 +1216,7 @@ dependencies = [ "actix-web", "barcoders", "base64 0.22.1", + "clap", "datamatrix", "enum-iterator", "env_logger", @@ -2042,7 +2089,7 @@ checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" dependencies = [ "dotenvy", "either", - "heck", + "heck 0.4.1", "hex", "once_cell", "proc-macro2", @@ -2180,6 +2227,12 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index a3e63ac..39c2d2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ actix-session = { version = "0.9.0", features = ["cookie-session"] } actix-web = { version = "4.8.0", features = ["cookies"] } barcoders = { version = "2.0.0", default-features = false, features = ["std"] } base64 = "0.22.1" +clap = { version = "4.5.10", features = ["derive", "env"] } datamatrix = "0.3.1" enum-iterator = "2.1.0" env_logger = "0.11.3" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..d191998 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2024 Simon Bruder +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use std::net::{IpAddr, Ipv6Addr}; + +use clap::Parser; + +/// A lightweight inventory management system +#[derive(Clone, Parser, Debug)] +#[command(version, about)] +pub struct Config { + /// Database URL of PostgreSQL database + #[arg(long, env)] + pub database_url: String, + + /// Secret key for encrypting session cookie + /// + /// Can be generated with head -c 64 /dev/urandom | base64 -w0 + #[arg(long, env)] + pub secret_key: Option, + + /// Address for HTTP server to listen on + #[arg(long, env, default_value_t = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)))] + pub listen_address: std::net::IpAddr, + + /// Port for HTTP server to listen on + #[arg(long, env, default_value_t = 8080)] + pub listen_port: u16, + + /// Superuser password + #[arg(long, env)] + pub superuser_password: String, +} diff --git a/src/frontend/auth.rs b/src/frontend/auth.rs index d2e4a79..e038037 100644 --- a/src/frontend/auth.rs +++ b/src/frontend/auth.rs @@ -59,12 +59,10 @@ async fn login( req: HttpRequest, form: web::Form, query: web::Query, + config: web::Data, ) -> Result { - // Very basic authentication for now (only password, hardcoded in environment variable) - if form.password - == std::env::var("SUPERUSER_PASSWORD") - .map_err(|_| error::ErrorInternalServerError("login disabled (no password set)"))? - { + // Very basic authentication for now (only password, hardcoded in configuration) + if form.password == config.superuser_password { Identity::login(&req.extensions(), "superuser".into()) .map_err(error::ErrorInternalServerError)?; Ok( diff --git a/src/lib.rs b/src/lib.rs index d9ec056..50b8e1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,9 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later +mod config; pub mod frontend; pub mod label; pub mod middleware; + +pub use config::Config; diff --git a/src/main.rs b/src/main.rs index b483eb7..35fbb34 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,17 +2,18 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -use std::env; - use actix_identity::IdentityMiddleware; use actix_session::{storage::CookieSessionStore, SessionMiddleware}; use actix_web::middleware::ErrorHandlers; use actix_web::{cookie::Key, http::StatusCode, web, App, HttpResponse, HttpServer}; use base64::prelude::{Engine as _, BASE64_STANDARD}; +use clap::Parser; use log::{info, warn}; use mime_guess::from_path; use rust_embed::Embed; +use li7y::Config; + #[derive(Embed)] #[folder = "static"] struct Static; @@ -21,39 +22,38 @@ struct Static; async fn main() -> std::io::Result<()> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + let config = Config::parse(); + // generate a secret key with head -c 64 /dev/urandom | base64 -w0 - let secret_key = match env::var("SECRET_KEY") { - Ok(encoded) => Key::from( + let secret_key = match config.secret_key { + Some(ref encoded) => Key::from( &BASE64_STANDARD .decode(encoded) .expect("failed to decode base64 in SECRET_KEY"), ), - Err(_) => { + None => { warn!("SECRET_KEY was not specified, using randomly generated key"); Key::generate() } }; - let pool: sqlx::PgPool = sqlx::Pool::::connect( - &env::var("DATABASE_URL").expect("DATABASE_URL must be set"), - ) - .await - .expect("failed to connect to database"); + let pool: sqlx::PgPool = sqlx::Pool::::connect(&config.database_url) + .await + .expect("failed to connect to database"); sqlx::migrate!() .run(&pool) .await .expect("failed to run migrations"); - let address = env::var("LISTEN_ADDRESS").unwrap_or("::1".to_string()); - let port = env::var("LISTEN_PORT").map_or(8080, |s| { - s.parse::().expect("failed to parse LISTEN_PORT") - }); + let address = config.listen_address; + let port = config.listen_port; info!("Starting on {address}:{port}"); HttpServer::new(move || { App::new() + .app_data(web::Data::new(config.clone())) .app_data(web::Data::new(pool.clone())) .service(web::scope("/static").route( "/{_:.*}",