1use rand::Rng;
2use std::fs;
3use std::path::PathBuf;
4use std::sync::Arc;
5use tokio::sync::RwLock;
6
7use crate::Result;
8
9#[derive(Debug, Clone)]
10pub struct AuthConfig {
11 pub token_path: PathBuf,
12 pub require_auth: bool,
13}
14
15impl Default for AuthConfig {
16 fn default() -> Self {
17 let token_path = dirs::home_dir()
18 .unwrap_or_else(|| PathBuf::from("."))
19 .join(".claude-utils")
20 .join("auth.token");
21
22 Self {
23 token_path,
24 require_auth: true,
25 }
26 }
27}
28
29pub struct AuthManager {
30 config: AuthConfig,
31 token: Arc<RwLock<Option<String>>>,
32}
33
34impl AuthManager {
35 pub async fn new(config: AuthConfig) -> Result<Self> {
36 let manager = Self {
37 config,
38 token: Arc::new(RwLock::new(None)),
39 };
40
41 manager.initialize().await?;
42 Ok(manager)
43 }
44
45 async fn initialize(&self) -> Result<()> {
46 if !self.config.require_auth {
47 return Ok(());
48 }
49
50 if let Some(parent) = self.config.token_path.parent() {
52 fs::create_dir_all(parent)?;
53 }
54
55 let token = if self.config.token_path.exists() {
57 fs::read_to_string(&self.config.token_path)?
58 .trim()
59 .to_string()
60 } else {
61 let new_token = self.generate_token();
62 self.save_token(&new_token)?;
63 new_token
64 };
65
66 *self.token.write().await = Some(token);
67 Ok(())
68 }
69
70 fn generate_token(&self) -> String {
71 let mut rng = rand::thread_rng();
72 let token_bytes: Vec<u8> = (0..32).map(|_| rng.gen()).collect();
73 hex::encode(token_bytes)
74 }
75
76 fn save_token(&self, token: &str) -> Result<()> {
77 fs::write(&self.config.token_path, token)?;
78
79 #[cfg(unix)]
81 {
82 use std::os::unix::fs::PermissionsExt;
83 let permissions = fs::Permissions::from_mode(0o600);
84 fs::set_permissions(&self.config.token_path, permissions)?;
85 }
86
87 Ok(())
88 }
89
90 pub async fn validate_token(&self, provided_token: Option<&str>) -> bool {
91 if !self.config.require_auth {
92 return true;
93 }
94
95 let stored_token = self.token.read().await;
96 match (&*stored_token, provided_token) {
97 (Some(stored), Some(provided)) => stored == provided,
98 _ => false,
99 }
100 }
101
102 pub async fn get_token(&self) -> Option<String> {
103 self.token.read().await.clone()
104 }
105}
106
107mod hex {
109 pub fn encode(bytes: Vec<u8>) -> String {
110 bytes.iter().map(|b| format!("{b:02x}")).collect::<String>()
111 }
112}