aiclient_api/auth/
token_store.rs1use super::{TokenData, TokenStore};
2use anyhow::{Context, Result};
3use async_trait::async_trait;
4use std::path::PathBuf;
5
6pub struct XdgTokenStore {
7 base_dir: PathBuf,
8}
9
10impl Default for XdgTokenStore {
11 fn default() -> Self {
12 Self::new(crate::util::xdg::config_dir())
13 }
14}
15
16impl XdgTokenStore {
17 pub fn new(base_dir: PathBuf) -> Self {
18 Self { base_dir }
19 }
20
21 fn token_path(&self, provider: &str) -> PathBuf {
22 self.base_dir.join(provider).join("token.json")
23 }
24}
25
26#[async_trait]
27impl TokenStore for XdgTokenStore {
28 async fn load(&self, provider: &str) -> Result<TokenData> {
29 let path = self.token_path(provider);
30 let content = tokio::fs::read_to_string(&path)
31 .await
32 .with_context(|| format!("No token found for provider: {}", provider))?;
33 let data: TokenData = serde_json::from_str(&content)
34 .with_context(|| format!("Invalid token file for: {}", provider))?;
35 Ok(data)
36 }
37
38 async fn save(&self, provider: &str, data: &TokenData) -> Result<()> {
39 let path = self.token_path(provider);
40 if let Some(parent) = path.parent() {
41 tokio::fs::create_dir_all(parent).await?;
42 }
43 let json = serde_json::to_string_pretty(data)?;
44 tokio::fs::write(&path, &json).await?;
45
46 #[cfg(unix)]
47 {
48 use std::os::unix::fs::PermissionsExt;
49 let perms = std::fs::Permissions::from_mode(0o600);
50 tokio::fs::set_permissions(&path, perms).await?;
51 }
52
53 Ok(())
54 }
55
56 async fn delete(&self, provider: &str) -> Result<()> {
57 let path = self.token_path(provider);
58 match tokio::fs::remove_file(&path).await {
59 Ok(()) => {}
60 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
61 Err(e) => return Err(e.into()),
62 }
63 let dir = self.base_dir.join(provider);
64 let _ = tokio::fs::remove_dir(&dir).await;
66 Ok(())
67 }
68
69 fn is_expired(&self, data: &TokenData) -> bool {
70 let now = chrono::Utc::now().timestamp();
71 match data {
72 TokenData::Copilot { expires_at, .. } => {
73 expires_at.is_some_and(|exp| now >= exp)
74 }
75 TokenData::Kiro { expires_at, .. } => now >= *expires_at,
76 }
77 }
78}