257 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			257 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
use clap::Parser;
 | 
						|
use s3::creds::Credentials;
 | 
						|
use s3::{Bucket, Region};
 | 
						|
 | 
						|
/// Money Manager 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:8000")]
 | 
						|
    pub listen_address: String,
 | 
						|
 | 
						|
    /// Website origin
 | 
						|
    #[clap(short, long, env, default_value = "http://localhost:5173")]
 | 
						|
    pub website_origin: String,
 | 
						|
 | 
						|
    /// Proxy IP, might end with a star "*"
 | 
						|
    #[clap(short, long, env)]
 | 
						|
    pub proxy_ip: Option<String>,
 | 
						|
 | 
						|
    /// Secret key, used to sign some resources. Must be randomly generated
 | 
						|
    #[clap(short = 'S', long, env, default_value = "")]
 | 
						|
    secret: String,
 | 
						|
 | 
						|
    /// Unsecure : for development, bypass authentication, using the account with the given
 | 
						|
    /// email address by default
 | 
						|
    #[clap(long, env)]
 | 
						|
    unsecure_auto_login_email: Option<String>,
 | 
						|
 | 
						|
    /// PostgreSQL database host
 | 
						|
    #[clap(long, env, default_value = "localhost")]
 | 
						|
    db_host: String,
 | 
						|
 | 
						|
    /// PostgreSQL database port
 | 
						|
    #[clap(long, env, default_value_t = 5432)]
 | 
						|
    db_port: u16,
 | 
						|
 | 
						|
    /// PostgreSQL username
 | 
						|
    #[clap(long, env, default_value = "user")]
 | 
						|
    db_username: String,
 | 
						|
 | 
						|
    /// PostgreSQL password
 | 
						|
    #[clap(long, env, default_value = "pass")]
 | 
						|
    db_password: String,
 | 
						|
 | 
						|
    /// PostgreSQL database name
 | 
						|
    #[clap(long, env, default_value = "moneymgr")]
 | 
						|
    db_name: String,
 | 
						|
 | 
						|
    /// URL where the OpenID configuration can be found
 | 
						|
    #[arg(
 | 
						|
        long,
 | 
						|
        env,
 | 
						|
        default_value = "http://127.0.0.1:9001/dex/.well-known/openid-configuration"
 | 
						|
    )]
 | 
						|
    pub oidc_configuration_url: String,
 | 
						|
 | 
						|
    /// OpenID provider name
 | 
						|
    #[arg(long, env, default_value = "3rd party provider")]
 | 
						|
    pub oidc_provider_name: String,
 | 
						|
 | 
						|
    /// 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,
 | 
						|
 | 
						|
    /// S3 Bucket name
 | 
						|
    #[arg(long, env, default_value = "moneymgr-data")]
 | 
						|
    s3_bucket_name: String,
 | 
						|
 | 
						|
    /// S3 region (if not using Minio)
 | 
						|
    #[arg(long, env, default_value = "eu-central-1")]
 | 
						|
    s3_region: String,
 | 
						|
 | 
						|
    /// S3 API endpoint
 | 
						|
    #[arg(long, env, default_value = "http://localhost:9000")]
 | 
						|
    s3_endpoint: String,
 | 
						|
 | 
						|
    /// S3 access key
 | 
						|
    #[arg(long, env, default_value = "topsecret")]
 | 
						|
    s3_access_key: String,
 | 
						|
 | 
						|
    /// S3 secret key
 | 
						|
    #[arg(long, env, default_value = "topsecret")]
 | 
						|
    s3_secret_key: String,
 | 
						|
 | 
						|
    /// S3 skip auto create bucket if not existing
 | 
						|
    #[arg(long, env)]
 | 
						|
    pub s3_skip_auto_create_bucket: bool,
 | 
						|
 | 
						|
    /// Directory where temporary files are stored
 | 
						|
    #[arg(long, env, default_value = "/tmp")]
 | 
						|
    pub temp_dir: String,
 | 
						|
 | 
						|
    /// Maintenance routine execution interval
 | 
						|
    #[arg(long, env, default_value_t = 3600)]
 | 
						|
    pub routine_interval: u64,
 | 
						|
 | 
						|
    /// Redis connection hostname
 | 
						|
    #[clap(long, env, default_value = "localhost")]
 | 
						|
    redis_hostname: String,
 | 
						|
 | 
						|
    /// Redis connection port
 | 
						|
    #[clap(long, env, default_value_t = 6379)]
 | 
						|
    redis_port: u16,
 | 
						|
 | 
						|
    /// Redis database number
 | 
						|
    #[clap(long, env, default_value_t = 0)]
 | 
						|
    redis_db_number: i64,
 | 
						|
 | 
						|
    /// Redis username
 | 
						|
    #[clap(long, env)]
 | 
						|
    redis_username: Option<String>,
 | 
						|
 | 
						|
    /// Redis password
 | 
						|
    #[clap(long, env, default_value = "secretredis")]
 | 
						|
    redis_password: String,
 | 
						|
 | 
						|
    /// Application download URL
 | 
						|
    #[clap(
 | 
						|
        long,
 | 
						|
        env,
 | 
						|
        default_value = "https://gitea.communiquons.org/pierre/MoneyMgr/releases/download/latest/moneymgr_mobile_arm64-v8a.apk"
 | 
						|
    )]
 | 
						|
    pub apk_download_url: 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 auto login email (if not empty)
 | 
						|
    pub fn unsecure_auto_login_email(&self) -> Option<&str> {
 | 
						|
        match self.unsecure_auto_login_email.as_deref() {
 | 
						|
            None | Some("") => None,
 | 
						|
            s => s,
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /// Check if auth is disabled
 | 
						|
    pub fn is_auth_disabled(&self) -> bool {
 | 
						|
        self.unsecure_auto_login_email().is_some()
 | 
						|
    }
 | 
						|
 | 
						|
    /// 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 full db connection chain
 | 
						|
    pub fn db_connection_chain(&self) -> String {
 | 
						|
        format!(
 | 
						|
            "postgres://{}:{}@{}:{}/{}",
 | 
						|
            self.db_username, self.db_password, self.db_host, self.db_port, self.db_name
 | 
						|
        )
 | 
						|
    }
 | 
						|
 | 
						|
    /// Get OpenID provider 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(),
 | 
						|
            name: self.oidc_provider_name.as_str(),
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /// Get OIDC callback URL
 | 
						|
    pub fn oidc_redirect_url(&self) -> String {
 | 
						|
        self.oidc_redirect_url
 | 
						|
            .replace("APP_ORIGIN", &self.website_origin)
 | 
						|
    }
 | 
						|
 | 
						|
    /// Get s3 credentials
 | 
						|
    pub fn s3_credentials(&self) -> anyhow::Result<Credentials> {
 | 
						|
        Ok(Credentials::new(
 | 
						|
            Some(&self.s3_access_key),
 | 
						|
            Some(&self.s3_secret_key),
 | 
						|
            None,
 | 
						|
            None,
 | 
						|
            None,
 | 
						|
        )?)
 | 
						|
    }
 | 
						|
 | 
						|
    /// Get S3 bucket
 | 
						|
    pub fn s3_bucket(&self) -> anyhow::Result<Box<Bucket>> {
 | 
						|
        Ok(Bucket::new(
 | 
						|
            &self.s3_bucket_name,
 | 
						|
            Region::Custom {
 | 
						|
                region: self.s3_region.to_string(),
 | 
						|
                endpoint: self.s3_endpoint.to_string(),
 | 
						|
            },
 | 
						|
            self.s3_credentials()?,
 | 
						|
        )?
 | 
						|
        .with_path_style())
 | 
						|
    }
 | 
						|
 | 
						|
    /// Get Redis connection configuration
 | 
						|
    pub fn redis_connection_string(&self) -> String {
 | 
						|
        format!(
 | 
						|
            "redis://{}:{}@{}:{}/{}",
 | 
						|
            self.redis_username.as_deref().unwrap_or(""),
 | 
						|
            self.redis_password,
 | 
						|
            self.redis_hostname,
 | 
						|
            self.redis_port,
 | 
						|
            self.redis_db_number
 | 
						|
        )
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#[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,
 | 
						|
    pub name: &'a str,
 | 
						|
}
 | 
						|
 | 
						|
#[cfg(test)]
 | 
						|
mod test {
 | 
						|
    use crate::app_config::AppConfig;
 | 
						|
 | 
						|
    #[test]
 | 
						|
    fn verify_cli() {
 | 
						|
        use clap::CommandFactory;
 | 
						|
        AppConfig::command().debug_assert()
 | 
						|
    }
 | 
						|
}
 |