colab_cli/auth/
storage.rs1use 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}