// src/config/storage/storage.rs use serde::{Deserialize, Serialize}; use std::fs::{self, File}; use std::io::Write; use std::path::PathBuf; use anyhow::{Context, Result}; use tracing::{error, info}; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; pub const APP_NAME: &str = "komp_ac_client"; pub const TOKEN_FILE_NAME: &str = "auth.token"; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct StoredAuthData { pub access_token: String, pub user_id: String, pub role: String, pub username: String, } pub fn get_token_storage_path() -> Result { let state_dir = dirs::state_dir() .or_else(|| dirs::home_dir().map(|home| home.join(".local").join("state"))) .ok_or_else(|| anyhow::anyhow!("Could not determine state directory"))?; let app_state_dir = state_dir.join(APP_NAME); fs::create_dir_all(&app_state_dir) .with_context(|| format!("Failed to create app state directory at {:?}", app_state_dir))?; Ok(app_state_dir.join(TOKEN_FILE_NAME)) } pub fn save_auth_data(data: &StoredAuthData) -> Result<()> { let path = get_token_storage_path()?; let json_data = serde_json::to_string(data) .context("Failed to serialize auth data")?; let mut file = File::create(&path) .with_context(|| format!("Failed to create token file at {:?}", path))?; file.write_all(json_data.as_bytes()) .context("Failed to write token data to file")?; // Set file permissions to 600 (owner read/write only) on Unix #[cfg(unix)] { file.set_permissions(std::fs::Permissions::from_mode(0o600)) .context("Failed to set token file permissions")?; } info!("Auth data saved to {:?}", path); Ok(()) } pub fn load_auth_data() -> Result> { let path = get_token_storage_path()?; if !path.exists() { info!("Token file not found at {:?}", path); return Ok(None); } let json_data = fs::read_to_string(&path) .with_context(|| format!("Failed to read token file at {:?}", path))?; if json_data.trim().is_empty() { info!("Token file is empty at {:?}", path); return Ok(None); } match serde_json::from_str::(&json_data) { Ok(data) => { info!("Auth data loaded from {:?}", path); Ok(Some(data)) } Err(e) => { error!("Failed to deserialize token data from {:?}: {}. Deleting corrupt file.", path, e); if let Err(del_e) = fs::remove_file(&path) { error!("Failed to delete corrupt token file: {}", del_e); } Ok(None) } } } pub fn delete_auth_data() -> Result<()> { let path = get_token_storage_path()?; if path.exists() { fs::remove_file(&path) .with_context(|| format!("Failed to delete token file at {:?}", path))?; info!("Token file deleted from {:?}", path); } else { info!("Token file not found for deletion at {:?}", path); } Ok(()) }