tovuk 0.1.69

Deploy Rust workers, static frontends, and worker-static apps to Tovuk.
use super::{
    super::{
        args::CliOptions,
        constants::{SESSION_DIR, SESSION_FILE},
        errors::{Result, agent_error, internal_error},
    },
    keychain::{read_keychain_token, write_keychain_token},
};
use std::{
    env, fs,
    path::{Path, PathBuf},
};

pub(super) fn read_stored_token(cli: &CliOptions) -> String {
    if !cli.token.trim().is_empty() {
        return cli.token.trim().to_owned();
    }
    if let Ok(token) = env::var("TOVUK_TOKEN") {
        if !token.trim().is_empty() {
            return token.trim().to_owned();
        }
    }
    let keychain = read_keychain_token();
    if !keychain.is_empty() {
        return keychain;
    }
    let user_token = read_token_file(&user_session_path());
    if !user_token.is_empty() {
        return user_token;
    }
    read_token_file(&home_dir().join(SESSION_DIR).join(SESSION_FILE))
}

pub(super) fn write_session_token(token: &str) -> Result<()> {
    let clean_token = token.trim();
    if clean_token.is_empty() {
        return Err(agent_error(
            "login_failed",
            "Tovuk session token is empty.",
            "Run `tovuk login` again and complete the browser login.",
            false,
        ));
    }
    if write_keychain_token(clean_token) {
        return Ok(());
    }
    write_token_file(&user_session_path(), clean_token)
}

fn read_token_file(path: &Path) -> String {
    fs::read_to_string(path)
        .map(|source| source.trim().to_owned())
        .unwrap_or_default()
}

fn write_token_file(path: &Path, token: &str) -> Result<()> {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent).map_err(|error| internal_error(error.to_string()))?;
        set_private_dir(parent);
    }
    fs::write(path, format!("{token}\n")).map_err(|error| internal_error(error.to_string()))?;
    set_private_file(path);
    Ok(())
}

fn user_session_path() -> PathBuf {
    if cfg!(windows) {
        if let Ok(appdata) = env::var("APPDATA") {
            return PathBuf::from(appdata).join("Tovuk").join(SESSION_FILE);
        }
    }
    env::var_os("XDG_CONFIG_HOME").map_or_else(
        || home_dir().join(".config").join("tovuk").join(SESSION_FILE),
        |path| PathBuf::from(path).join("tovuk").join(SESSION_FILE),
    )
}

fn home_dir() -> PathBuf {
    env::var_os("HOME")
        .or_else(|| env::var_os("USERPROFILE"))
        .map_or_else(|| PathBuf::from("."), PathBuf::from)
}

#[cfg(unix)]
fn set_private_file(path: &Path) {
    use std::os::unix::fs::PermissionsExt;
    let _ignore = fs::set_permissions(path, fs::Permissions::from_mode(0o600));
}

#[cfg(not(unix))]
fn set_private_file(_path: &Path) {}

#[cfg(unix)]
fn set_private_dir(path: &Path) {
    use std::os::unix::fs::PermissionsExt;
    let _ignore = fs::set_permissions(path, fs::Permissions::from_mode(0o700));
}

#[cfg(not(unix))]
fn set_private_dir(_path: &Path) {}