lean_ctx/core/
session_token.rs1use std::path::PathBuf;
8
9const TOKEN_BYTES: usize = 32;
10const TOKEN_FILE: &str = "session_token";
11
12pub fn generate_token() -> String {
14 let mut buf = [0u8; TOKEN_BYTES];
15 getrandom::fill(&mut buf).expect("getrandom failed");
16 bytes_to_hex(&buf)
17}
18
19fn bytes_to_hex(bytes: &[u8]) -> String {
20 let mut s = String::with_capacity(bytes.len() * 2);
21 for &b in bytes {
22 s.push(char::from_digit((b >> 4) as u32, 16).unwrap_or('0'));
23 s.push(char::from_digit((b & 0xf) as u32, 16).unwrap_or('0'));
24 }
25 s
26}
27
28pub fn resolve_proxy_token(env_var: &str) -> String {
35 if let Ok(val) = std::env::var(env_var) {
36 if !val.trim().is_empty() {
37 return val.trim().to_string();
38 }
39 }
40
41 let token_path = token_file_path();
42 if let Ok(existing) = std::fs::read_to_string(&token_path) {
43 let t = existing.trim().to_string();
44 if !t.is_empty() {
45 return t;
46 }
47 }
48
49 let token = generate_token();
50 write_token_file(&token_path, &token);
51 token
52}
53
54fn write_token_file(path: &PathBuf, token: &str) {
56 if let Some(parent) = path.parent() {
57 if let Err(e) = std::fs::create_dir_all(parent) {
58 tracing::error!("Failed to create token directory {}: {e}", parent.display());
59 return;
60 }
61 }
62 if let Err(e) = std::fs::write(path, token) {
63 tracing::error!("Failed to write session token to {}: {e}", path.display());
64 return;
65 }
66 set_restrictive_permissions(path);
67}
68
69fn token_file_path() -> PathBuf {
70 crate::core::data_dir::lean_ctx_data_dir()
71 .unwrap_or_else(|_| PathBuf::from(".lean-ctx"))
72 .join(TOKEN_FILE)
73}
74
75#[cfg(unix)]
76fn set_restrictive_permissions(path: &PathBuf) {
77 use std::os::unix::fs::PermissionsExt;
78 let perms = std::fs::Permissions::from_mode(0o600);
79 let _ = std::fs::set_permissions(path, perms);
80}
81
82#[cfg(not(unix))]
83fn set_restrictive_permissions(_path: &PathBuf) {
84 }
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn generated_token_is_64_hex_chars() {
93 let token = generate_token();
94 assert_eq!(token.len(), 64);
95 assert!(token.chars().all(|c| c.is_ascii_hexdigit()));
96 }
97
98 #[test]
99 fn generated_tokens_are_unique() {
100 let a = generate_token();
101 let b = generate_token();
102 assert_ne!(a, b);
103 }
104}