Skip to main content

colab_cli/auth/
storage.rs

1use std::fs;
2use std::path::PathBuf;
3
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6
7use crate::error::{ColabError, Result};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct StoredAccessToken {
11    pub access_token: String,
12    pub expires_at: DateTime<Utc>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct AccountInfo {
17    pub email: String,
18    pub name: String,
19}
20
21#[derive(Debug, Default, Serialize, Deserialize)]
22struct CredentialsFile {
23    refresh_token: Option<String>,
24    access_token: Option<StoredAccessToken>,
25    account: Option<AccountInfo>,
26}
27
28pub struct TokenStorage;
29
30impl TokenStorage {
31    fn credentials_path() -> Result<PathBuf> {
32        let base = dirs::data_local_dir()
33            .ok_or_else(|| ColabError::config("could not determine data directory"))?;
34        let dir = base.join("colab-cli");
35        fs::create_dir_all(&dir)?;
36        Ok(dir.join("credentials.json"))
37    }
38
39    fn read() -> Result<CredentialsFile> {
40        let path = Self::credentials_path()?;
41        match fs::read_to_string(&path) {
42            Ok(s) => Ok(serde_json::from_str(&s).unwrap_or_default()),
43            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(CredentialsFile::default()),
44            Err(e) => Err(ColabError::Io(e)),
45        }
46    }
47
48    fn write(creds: &CredentialsFile) -> Result<()> {
49        let path = Self::credentials_path()?;
50        let json = serde_json::to_string_pretty(creds)?;
51
52        let tmp = path.with_extension("tmp");
53        fs::write(&tmp, &json)?;
54
55        #[cfg(unix)]
56        {
57            use std::os::unix::fs::PermissionsExt;
58            fs::set_permissions(&tmp, fs::Permissions::from_mode(0o600))?;
59        }
60
61        fs::rename(&tmp, &path)?;
62        Ok(())
63    }
64
65    pub fn store_refresh_token(token: &str) -> Result<()> {
66        let mut creds = Self::read()?;
67        creds.refresh_token = Some(token.to_string());
68        Self::write(&creds)
69    }
70
71    pub fn get_refresh_token() -> Result<Option<String>> {
72        Ok(Self::read()?.refresh_token)
73    }
74
75    pub fn store_access_token(token: &str, expires_at: DateTime<Utc>) -> Result<()> {
76        let mut creds = Self::read()?;
77        creds.access_token = Some(StoredAccessToken {
78            access_token: token.to_string(),
79            expires_at,
80        });
81        Self::write(&creds)
82    }
83
84    pub fn get_access_token() -> Result<Option<StoredAccessToken>> {
85        Ok(Self::read()?.access_token)
86    }
87
88    pub fn store_account(info: &AccountInfo) -> Result<()> {
89        let mut creds = Self::read()?;
90        creds.account = Some(info.clone());
91        Self::write(&creds)
92    }
93
94    pub fn get_account() -> Result<Option<AccountInfo>> {
95        Ok(Self::read()?.account)
96    }
97
98    pub fn clear_all() -> Result<()> {
99        Self::write(&CredentialsFile::default())
100    }
101}