Skip to main content

stynx_code_auth/infrastructure/oauth/
token_store.rs

1use 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}