1use crate::mcp::error::{ClaudeCodeError, Result};
12
13pub const AUTH_HEADER: &str = "x-claude-code-ide-authorization";
17
18pub fn generate_token() -> Result<String> {
22 let mut bytes = [0u8; 16];
23 getrandom::getrandom(&mut bytes).map_err(|e| ClaudeCodeError::Random(e.to_string()))?;
24 let mut hex = String::with_capacity(32);
25 for b in bytes {
26 use std::fmt::Write as _;
27 let _ = write!(hex, "{b:02x}");
29 }
30 Ok(hex)
31}
32
33pub fn header_matches(expected: &str, provided: &str) -> bool {
37 constant_time_eq(expected.as_bytes(), provided.as_bytes())
38}
39
40fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
44 if a.len() != b.len() {
45 return false;
46 }
47 let mut diff = 0u8;
48 for (x, y) in a.iter().zip(b.iter()) {
49 diff |= x ^ y;
50 }
51 diff == 0
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn generated_token_is_32_hex_chars() {
60 let t = generate_token().expect("token");
61 assert_eq!(t.len(), 32);
62 assert!(t.chars().all(|c| c.is_ascii_hexdigit()));
63 }
64
65 #[test]
66 fn two_tokens_differ() {
67 let a = generate_token().expect("a");
69 let b = generate_token().expect("b");
70 assert_ne!(a, b);
71 }
72
73 #[test]
74 fn matching_token_accepts() {
75 let token = "deadbeefcafef00ddeadbeefcafef00d";
76 assert!(header_matches(token, token));
77 }
78
79 #[test]
80 fn wrong_token_rejects() {
81 let token = "deadbeefcafef00ddeadbeefcafef00d";
82 assert!(!header_matches(token, "deadbeefcafef00ddeadbeefcafef00e"));
83 }
84
85 #[test]
86 fn length_mismatch_rejects() {
87 assert!(!header_matches("short", "shorter-value"));
88 assert!(!header_matches("", "x"));
89 }
90
91 #[test]
92 fn empty_provided_rejects_nonempty_expected() {
93 assert!(!header_matches("token", ""));
94 }
95}