1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/// Auto-generated session tokens for proxy/HTTP server security.
///
/// Security principle: Least Action — minimize attack surface by always having
/// a token, even when the user hasn't explicitly configured one. The token is
/// written to a file with restrictive permissions so authorized local clients
/// can discover it.
use std::path::PathBuf;
const TOKEN_BYTES: usize = 32;
const TOKEN_FILE: &str = "session_token";
/// Generate a cryptographically random hex token.
pub fn generate_token() -> String {
let mut buf = [0u8; TOKEN_BYTES];
getrandom::fill(&mut buf).expect("getrandom failed");
bytes_to_hex(&buf)
}
fn bytes_to_hex(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for &b in bytes {
s.push(char::from_digit((b >> 4) as u32, 16).unwrap_or('0'));
s.push(char::from_digit((b & 0xf) as u32, 16).unwrap_or('0'));
}
s
}
/// Resolve or generate the proxy/HTTP session token.
///
/// Priority:
/// 1. Explicit env var (user override)
/// 2. Existing token file (persistence across restarts)
/// 3. Generate new + write to file
pub fn resolve_proxy_token(env_var: &str) -> String {
if let Ok(val) = std::env::var(env_var) {
if !val.trim().is_empty() {
return val.trim().to_string();
}
}
let token_path = token_file_path();
if let Ok(existing) = std::fs::read_to_string(&token_path) {
let t = existing.trim().to_string();
if !t.is_empty() {
return t;
}
}
let token = generate_token();
write_token_file(&token_path, &token);
token
}
/// Write token to file with restrictive permissions (0600 on Unix).
fn write_token_file(path: &PathBuf, token: &str) {
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
let _ = std::fs::write(path, token);
set_restrictive_permissions(path);
}
fn token_file_path() -> PathBuf {
crate::core::data_dir::lean_ctx_data_dir()
.unwrap_or_else(|_| PathBuf::from(".lean-ctx"))
.join(TOKEN_FILE)
}
#[cfg(unix)]
fn set_restrictive_permissions(path: &PathBuf) {
use std::os::unix::fs::PermissionsExt;
let perms = std::fs::Permissions::from_mode(0o600);
let _ = std::fs::set_permissions(path, perms);
}
#[cfg(not(unix))]
fn set_restrictive_permissions(_path: &PathBuf) {
// Windows: rely on user-scoped directories
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generated_token_is_64_hex_chars() {
let token = generate_token();
assert_eq!(token.len(), 64);
assert!(token.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn generated_tokens_are_unique() {
let a = generate_token();
let b = generate_token();
assert_ne!(a, b);
}
}