AkurAI Build
Menu

Public repository / master

AkurAI-Drive / backend/src/config.rs

Latest change beff27fc3a062a220cde556e7bb28da49f6d04ea - Drive login: recover oidc.rs (IdP PKCE + introspection) from deployed binary by AkurAI Agent

use std::sync::LazyLock;

/// Process configuration, loaded once from the environment.
///
/// Everything is read from env via `std::env::var` — no secrets in code. On the
/// box these are injected by systemd from `EnvironmentFile=/etc/akurai-drive-api.env`.
/// The `aws` CLI inherits its credentials from the process environment directly; the
/// SigV4 presigner (`sigv4.rs`) is in-process and signs locally, so it also needs
/// `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` read into the config here.
pub struct Config {
    pub port: u16,
    pub listen_addr: String,
    pub db_path: String,
    /// Plain HTTP, same box, no TLS — used for back-channel /token and /userinfo.
    pub idp_internal_url: String,
    /// Public issuer — used to build the browser-facing /authorize redirect.
    pub idp_issuer: String,
    pub oidc_client_id: String,
    pub oidc_client_secret: String,
    pub oidc_redirect_uri: String,
    /// Groups allowed to sign in (`AKURAI_IDP_ALLOWED_GROUPS`, comma separated).
    /// Empty means every IdP identity may sign in.
    pub allowed_groups: Vec<String>,
    pub drive_public_url: String,
    pub drive_access_code: String,
    pub drive_admin_group: String,
    pub drive_bucket_prefix: String,
    pub aws_region: String,
    /// Used by the in-process SigV4 presigner (and shared with the `aws` CLI via env).
    pub aws_access_key_id: String,
    pub aws_secret_access_key: String,
    /// Audience the IdP puts in access tokens for this app (MCP resource server).
    pub mcp_audience: String,
    /// Stable deployment identifier used by the Framework telemetry contract.
    pub drive_installation_id: String,
}

static CONFIG: LazyLock<Config> = LazyLock::new(|| {
    let port = std::env::var("DRIVE_PORT")
        .ok()
        .and_then(|v| v.parse().ok())
        .unwrap_or(8096u16);

    Config {
        port,
        listen_addr: format!("127.0.0.1:{port}"),
        db_path: std::env::var("DRIVE_DB_PATH")
            .unwrap_or_else(|_| "./data/drive.sqlite".to_string()),
        idp_internal_url: env_first(&["AKURAI_IDP_INTERNAL_URL", "IDP_INTERNAL_URL"])
            .unwrap_or_else(|| "http://127.0.0.1:3500".to_string())
            .trim_end_matches('/')
            .to_string(),
        idp_issuer: env_first(&["AKURAI_IDP_ISSUER", "IDP_ISSUER"])
            .unwrap_or_else(|| "https://auth.olibuijr.com".to_string())
            .trim_end_matches('/')
            .to_string(),
        oidc_client_id: env_first(&["AKURAI_IDP_CLIENT_ID", "OIDC_CLIENT_ID"]).unwrap_or_default(),
        oidc_client_secret: env_first(&["AKURAI_IDP_CLIENT_SECRET", "OIDC_CLIENT_SECRET"])
            .unwrap_or_default(),
        oidc_redirect_uri: env_first(&["AKURAI_IDP_REDIRECT_URI", "OIDC_REDIRECT_URI"])
            .unwrap_or_else(|| "https://akurai-drive.olibuijr.com/auth/callback".to_string()),
        allowed_groups: parse_list(&std::env::var("AKURAI_IDP_ALLOWED_GROUPS").unwrap_or_default()),
        drive_public_url: env_first(&["AKURAI_PUBLIC_URL", "DRIVE_PUBLIC_URL"])
            .unwrap_or_else(|| "https://akurai-drive.olibuijr.com".to_string())
            .trim_end_matches('/')
            .to_string(),
        drive_access_code: std::env::var("DRIVE_ACCESS_CODE").unwrap_or_default(),
        drive_admin_group: std::env::var("DRIVE_ADMIN_GROUP")
            .unwrap_or_else(|_| "drive-admins".to_string()),
        drive_bucket_prefix: std::env::var("DRIVE_BUCKET_PREFIX")
            .unwrap_or_else(|_| "akurai-drive".to_string()),
        aws_region: std::env::var("AWS_DEFAULT_REGION").unwrap_or_else(|_| "us-east-1".to_string()),
        aws_access_key_id: std::env::var("AWS_ACCESS_KEY_ID").unwrap_or_default(),
        aws_secret_access_key: std::env::var("AWS_SECRET_ACCESS_KEY").unwrap_or_default(),
        mcp_audience: std::env::var("MCP_AUDIENCE").unwrap_or_else(|_| "akurai-drive".to_string()),
        drive_installation_id: std::env::var("DRIVE_INSTALLATION_ID")
            .unwrap_or_else(|_| "akurai-drive-api".to_string()),
    }
});

pub fn get() -> &'static Config {
    &CONFIG
}

/// First non-empty value among `names`. The deployed service uses the shared
/// `AKURAI_IDP_*` / `AKURAI_PUBLIC_URL` names; the pre-SSO names are still read
/// as fallbacks so an older env file keeps working.
fn env_first(names: &[&str]) -> Option<String> {
    first_non_empty(names.iter().map(|n| std::env::var(n).ok()))
}

fn first_non_empty(values: impl IntoIterator<Item = Option<String>>) -> Option<String> {
    values
        .into_iter()
        .flatten()
        .map(|v| v.trim().to_string())
        .find(|v| !v.is_empty())
}

/// Split a comma-separated list, dropping blanks.
pub fn parse_list(raw: &str) -> Vec<String> {
    raw.split(',')
        .map(str::trim)
        .filter(|s| !s.is_empty())
        .map(String::from)
        .collect()
}

/// Env settings the service cannot run without, reported by their primary name.
pub fn missing_required(cfg: &Config) -> Vec<&'static str> {
    [
        ("AKURAI_IDP_CLIENT_ID", cfg.oidc_client_id.is_empty()),
        (
            "AKURAI_IDP_CLIENT_SECRET",
            cfg.oidc_client_secret.is_empty(),
        ),
    ]
    .into_iter()
    .filter_map(|(name, missing)| missing.then_some(name))
    .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn first_non_empty_prefers_earliest_set_value() {
        let got = first_non_empty([None, Some(" ".into()), Some("b".into()), Some("c".into())]);
        assert_eq!(got.as_deref(), Some("b"));
        assert_eq!(first_non_empty([None, Some(String::new())]), None);
    }

    #[test]
    fn parse_list_splits_and_trims() {
        assert_eq!(parse_list(" a, ,b ,c"), vec!["a", "b", "c"]);
        assert!(parse_list("").is_empty());
    }
}