use basic_jwt::JWTPrivateKey; use clap::Parser; /// VirtWeb backend API #[derive(Parser, Debug, Clone)] #[clap(author, version, about, long_about = None)] pub struct AppConfig { /// Listen address #[clap(short, long, env, default_value = "0.0.0.0:8002")] pub listen_address: String, /// Website main origin #[clap(short, long, env, default_value = "http://localhost:3000")] pub website_origin: String, /// Proxy IP, might end with a star "*" #[clap(short, long, env)] pub proxy_ip: Option, /// Secret key, used to sign some resources. Must be randomly generated #[clap(short = 'S', long, env, default_value = "")] secret: String, /// Specify whether the cookie should be transmitted only over secure connections #[clap(long, env)] pub cookie_secure: bool, /// URL where the OpenID configuration can be found #[arg( long, env, default_value = "http://localhost:9001/.well-known/openid-configuration" )] pub oidc_configuration_url: String, /// Disable OpenID authentication #[arg(long, env)] pub disable_oidc: bool, /// OpenID client ID #[arg(long, env, default_value = "foo")] pub oidc_client_id: String, /// OpenID client secret #[arg(long, env, default_value = "bar")] pub oidc_client_secret: String, /// OpenID login redirect URL #[arg(long, env, default_value = "APP_ORIGIN/oidc_cb")] oidc_redirect_url: String, /// VirtWeb base URL #[arg(long, env, default_value = "http://localhost:8000")] pub virtweb_base_url: String, /// VirtWeb API token ID #[arg(long, env)] pub virtweb_token_id: String, /// VirtWeb API token private key #[arg(long, env)] virtweb_token_private_key: String, } lazy_static::lazy_static! { static ref ARGS: AppConfig = { AppConfig::parse() }; } impl AppConfig { /// Get parsed command line arguments pub fn get() -> &'static AppConfig { &ARGS } /// Get auth cookie domain pub fn cookie_domain(&self) -> Option { if cfg!(debug_assertions) { let domain = self.website_origin.split_once("://")?.1; Some( domain .split_once(':') .map(|s| s.0) .unwrap_or(domain) .to_string(), ) } else { // In release mode, the web app is hosted on the same origin as the API None } } /// Get app secret pub fn secret(&self) -> &str { let mut secret = self.secret.as_str(); if cfg!(debug_assertions) && secret.is_empty() { secret = "DEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEYDEBUGKEY"; } if secret.is_empty() { panic!("SECRET is undefined or too short (min 64 chars)!") } secret } /// Get OpenID providers configuration pub fn openid_provider(&self) -> OIDCProvider<'_> { OIDCProvider { client_id: self.oidc_client_id.as_str(), client_secret: self.oidc_client_secret.as_str(), configuration_url: self.oidc_configuration_url.as_str(), } } /// Get OIDC callback URL pub fn oidc_redirect_url(&self) -> String { self.oidc_redirect_url .replace("APP_ORIGIN", &self.website_origin) } /// Get VirtWeb token private key pub fn token_private_key(&self) -> JWTPrivateKey { JWTPrivateKey::ES384 { r#priv: self.virtweb_token_private_key.to_string(), } } } #[derive(Debug, Clone, serde::Serialize)] pub struct OIDCProvider<'a> { #[serde(skip_serializing)] pub client_id: &'a str, #[serde(skip_serializing)] pub client_secret: &'a str, #[serde(skip_serializing)] pub configuration_url: &'a str, } #[cfg(test)] mod test { use crate::app_config::AppConfig; #[test] fn verify_cli() { use clap::CommandFactory; AppConfig::command().debug_assert() } }