use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
pub mod anthropic;
pub mod storage;
pub mod github_copilot;
pub mod manager;
pub use anthropic::AnthropicAuth;
pub use github_copilot::{GitHubCopilotAuth, GitHubCopilotAuthResult};
pub use manager::AuthManager;
#[async_trait]
pub trait Auth: Send + Sync {
fn provider_id(&self) -> &str;
async fn get_credentials(&self) -> crate::Result<AuthCredentials>;
async fn set_credentials(&self, credentials: AuthCredentials) -> crate::Result<()>;
async fn remove_credentials(&self) -> crate::Result<()>;
async fn has_credentials(&self) -> bool;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum AuthCredentials {
ApiKey {
key: String,
},
OAuth {
access_token: String,
refresh_token: Option<String>,
expires_at: Option<u64>,
},
Custom {
data: HashMap<String, serde_json::Value>,
},
}
impl AuthCredentials {
pub fn is_expired(&self) -> bool {
match self {
AuthCredentials::OAuth { expires_at: Some(exp), .. } => {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
now >= *exp
}
_ => false,
}
}
pub fn api_key(key: impl Into<String>) -> Self {
Self::ApiKey { key: key.into() }
}
pub fn oauth(
access_token: impl Into<String>,
refresh_token: Option<impl Into<String>>,
expires_at: Option<u64>,
) -> Self {
Self::OAuth {
access_token: access_token.into(),
refresh_token: refresh_token.map(|t| t.into()),
expires_at,
}
}
}
#[async_trait]
pub trait AuthStorage: Send + Sync {
async fn get(&self, provider_id: &str) -> crate::Result<Option<AuthCredentials>>;
async fn set(&self, provider_id: &str, credentials: AuthCredentials) -> crate::Result<()>;
async fn remove(&self, provider_id: &str) -> crate::Result<()>;
async fn list(&self) -> crate::Result<Vec<String>>;
}
pub struct FileAuthStorage {
path: PathBuf,
}
impl FileAuthStorage {
pub fn new(path: PathBuf) -> Self {
Self { path }
}
pub fn default_with_result() -> crate::Result<Self> {
let home_dir = dirs::home_dir()
.ok_or_else(|| crate::Error::Storage(crate::storage::StorageError::Other("Could not determine home directory".to_string())))?;
let path = home_dir.join(".code-mesh").join("auth.json");
Ok(Self::new(path))
}
}
impl Default for FileAuthStorage {
fn default() -> Self {
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
let path = home_dir.join(".code-mesh").join("auth.json");
Self::new(path)
}
}