stynx_code_auth/infrastructure/oauth/
token_store.rs1use stynx_code_errors::{AppError, AppResult};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct OAuthTokens {
6 pub access_token: String,
7 pub refresh_token: Option<String>,
8 pub expires_at: Option<u64>,
9 pub token_type: String,
10}
11
12pub trait TokenStore {
13 fn save(&self, tokens: &OAuthTokens) -> AppResult<()>;
14 fn load(&self) -> Option<OAuthTokens>;
15 fn clear(&self) -> AppResult<()>;
16}
17
18pub struct FileTokenStore {
19 path: std::path::PathBuf,
20}
21
22impl FileTokenStore {
23 pub fn new() -> AppResult<Self> {
24 let home = std::env::var("HOME")
25 .or_else(|_| std::env::var("USERPROFILE"))
26 .map_err(|_| AppError::Provider("cannot determine home directory".to_string()))?;
27
28 let path = std::path::PathBuf::from(home)
29 .join(".claude")
30 .join(".credentials.json");
31
32 Ok(Self { path })
33 }
34}
35
36impl TokenStore for FileTokenStore {
37 fn save(&self, tokens: &OAuthTokens) -> AppResult<()> {
38 if let Some(parent) = self.path.parent() {
39 std::fs::create_dir_all(parent).map_err(|e| {
40 AppError::Provider(format!(
41 "failed to create credentials directory {}: {e}",
42 parent.display()
43 ))
44 })?;
45 }
46
47 let json = serde_json::to_string_pretty(tokens)
48 .map_err(|e| AppError::Provider(format!("failed to serialize tokens: {e}")))?;
49
50 std::fs::write(&self.path, json).map_err(|e| {
51 AppError::Provider(format!(
52 "failed to write credentials to {}: {e}",
53 self.path.display()
54 ))
55 })?;
56
57 Ok(())
58 }
59
60 fn load(&self) -> Option<OAuthTokens> {
61 let contents = std::fs::read_to_string(&self.path).ok()?;
62 serde_json::from_str(&contents).ok()
63 }
64
65 fn clear(&self) -> AppResult<()> {
66 if self.path.exists() {
67 std::fs::remove_file(&self.path).map_err(|e| {
68 AppError::Provider(format!(
69 "failed to remove credentials file {}: {e}",
70 self.path.display()
71 ))
72 })?;
73 }
74 Ok(())
75 }
76}