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 #[cfg(unix)]
58 {
59 use std::os::unix::fs::PermissionsExt;
60 std::fs::set_permissions(&self.path, std::fs::Permissions::from_mode(0o600))
61 .map_err(|e| AppError::Provider(format!(
62 "failed to set 0600 perms on {}: {e}", self.path.display(),
63 )))?;
64 }
65
66 Ok(())
67 }
68
69 fn load(&self) -> Option<OAuthTokens> {
70 let contents = std::fs::read_to_string(&self.path).ok()?;
71 serde_json::from_str(&contents).ok()
72 }
73
74 fn clear(&self) -> AppResult<()> {
75 if self.path.exists() {
76 std::fs::remove_file(&self.path).map_err(|e| {
77 AppError::Provider(format!(
78 "failed to remove credentials file {}: {e}",
79 self.path.display()
80 ))
81 })?;
82 }
83 Ok(())
84 }
85}