Skip to main content

tandem_core/
engine_api_token.rs

1use std::path::PathBuf;
2use uuid::Uuid;
3
4use crate::resolve_shared_paths;
5
6const TOKEN_SERVICE: &str = "ai.frumu.tandem";
7const TOKEN_ACCOUNT: &str = "engine_api_token";
8
9#[derive(Debug, Clone)]
10pub struct EngineApiTokenMaterial {
11    pub token: String,
12    pub backend: String,
13    pub file_path: PathBuf,
14}
15
16pub fn engine_api_token_file_path() -> PathBuf {
17    if let Ok(paths) = resolve_shared_paths() {
18        return paths
19            .canonical_root
20            .join("security")
21            .join("engine_api_token");
22    }
23    PathBuf::from(".tandem").join("engine_api_token")
24}
25
26fn new_token() -> String {
27    format!("tk_{}", Uuid::new_v4().simple())
28}
29
30fn keyring_entry() -> Option<keyring::Entry> {
31    keyring::Entry::new(TOKEN_SERVICE, TOKEN_ACCOUNT).ok()
32}
33
34fn read_file_token(path: &PathBuf) -> Option<String> {
35    let existing = std::fs::read_to_string(path).ok()?;
36    let token = existing.trim();
37    if token.is_empty() {
38        None
39    } else {
40        Some(token.to_string())
41    }
42}
43
44fn write_file_token(path: &PathBuf, token: &str) -> bool {
45    if let Some(parent) = path.parent() {
46        if std::fs::create_dir_all(parent).is_err() {
47            return false;
48        }
49    }
50    std::fs::write(path, token).is_ok()
51}
52
53pub fn load_or_create_engine_api_token() -> EngineApiTokenMaterial {
54    let file_path = engine_api_token_file_path();
55
56    if let Some(entry) = keyring_entry() {
57        if let Ok(token) = entry.get_password() {
58            let token = token.trim().to_string();
59            if !token.is_empty() {
60                return EngineApiTokenMaterial {
61                    token,
62                    backend: "keychain".to_string(),
63                    file_path,
64                };
65            }
66        }
67    }
68
69    if let Some(token) = read_file_token(&file_path) {
70        if let Some(entry) = keyring_entry() {
71            let _ = entry.set_password(&token);
72        }
73        return EngineApiTokenMaterial {
74            token,
75            backend: "file".to_string(),
76            file_path,
77        };
78    }
79
80    let token = new_token();
81    if let Some(entry) = keyring_entry() {
82        if entry.set_password(&token).is_ok() {
83            return EngineApiTokenMaterial {
84                token,
85                backend: "keychain".to_string(),
86                file_path,
87            };
88        }
89    }
90
91    let wrote_file = write_file_token(&file_path, &token);
92    EngineApiTokenMaterial {
93        token,
94        backend: if wrote_file {
95            "file".to_string()
96        } else {
97            "memory".to_string()
98        },
99        file_path,
100    }
101}