use std::fs;
use std::path::PathBuf;
use crate::error::{Result, ToriiError};
const ENV_VAR: &str = "TORII_API_KEY";
const FILE_NAME: &str = "auth.toml";
#[derive(Debug, Clone, Default)]
pub struct ApiKey {
pub key: String,
pub endpoint: String,
}
pub fn default_endpoint() -> String {
std::env::var("TORII_API_ENDPOINT")
.unwrap_or_else(|_| "https://api.gitorii.com".to_string())
}
pub fn load() -> Option<ApiKey> {
if let Ok(env_key) = std::env::var(ENV_VAR) {
if !env_key.is_empty() {
return Some(ApiKey {
key: env_key,
endpoint: default_endpoint(),
});
}
}
let path = config_path()?;
if !path.exists() {
return None;
}
let text = fs::read_to_string(&path).ok()?;
parse(&text)
}
pub fn save(key: &str, endpoint: &str) -> Result<()> {
let path = config_path().ok_or_else(|| {
ToriiError::InvalidConfig("could not resolve config dir".to_string())
})?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.map_err(|e| ToriiError::InvalidConfig(format!("create dir: {}", e)))?;
}
let content = format!(
"# torii API key — generated by `torii auth login`. Do not share.\n\
key = \"{}\"\n\
endpoint = \"{}\"\n",
key, endpoint,
);
fs::write(&path, content)
.map_err(|e| ToriiError::InvalidConfig(format!("write {}: {}", path.display(), e)))?;
restrict_permissions(&path);
Ok(())
}
pub fn delete() -> Result<()> {
let Some(path) = config_path() else {
return Ok(());
};
if path.exists() {
fs::remove_file(&path)
.map_err(|e| ToriiError::InvalidConfig(format!("remove {}: {}", path.display(), e)))?;
}
Ok(())
}
fn config_path() -> Option<PathBuf> {
dirs::config_dir().map(|d| d.join("torii").join(FILE_NAME))
}
fn parse(text: &str) -> Option<ApiKey> {
let mut key = String::new();
let mut endpoint = default_endpoint();
for line in text.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let (k, v) = line.split_once('=')?;
let k = k.trim();
let v = v.trim().trim_matches('"').to_string();
match k {
"key" => key = v,
"endpoint" => endpoint = v,
_ => {}
}
}
if key.is_empty() {
None
} else {
Some(ApiKey { key, endpoint })
}
}
#[cfg(unix)]
fn restrict_permissions(path: &std::path::Path) {
use std::os::unix::fs::PermissionsExt;
let _ = fs::set_permissions(path, fs::Permissions::from_mode(0o600));
}
#[cfg(not(unix))]
fn restrict_permissions(_: &std::path::Path) {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_minimal() {
let k = parse("key = \"gitorii_sk_abc\"").unwrap();
assert_eq!(k.key, "gitorii_sk_abc");
}
#[test]
fn parse_with_endpoint() {
let k = parse("key = \"x\"\nendpoint = \"http://localhost:8080\"").unwrap();
assert_eq!(k.endpoint, "http://localhost:8080");
}
#[test]
fn parse_empty_returns_none() {
assert!(parse("").is_none());
assert!(parse("# comment only").is_none());
}
#[test]
fn parse_ignores_unknown_keys() {
let k = parse("key = \"x\"\nrandom = \"y\"").unwrap();
assert_eq!(k.key, "x");
}
}