tandem_core/
engine_api_token.rs1use 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}