1use std::net::IpAddr;
2use subtle::ConstantTimeEq;
3
4pub fn verify_token(provided: &str, expected: &str) -> bool {
5 if expected.is_empty() {
6 return false;
7 }
8 const BUF_SIZE: usize = 136; let pb = provided.as_bytes();
12 let eb = expected.as_bytes();
13 if pb.len() > 128 || eb.len() > 128 {
14 return false;
15 }
16 let mut p = [0u8; BUF_SIZE];
17 let mut e = [0u8; BUF_SIZE];
18 p[..pb.len()].copy_from_slice(pb);
19 e[..eb.len()].copy_from_slice(eb);
20 p[128..].copy_from_slice(&(pb.len() as u64).to_le_bytes());
21 e[128..].copy_from_slice(&(eb.len() as u64).to_le_bytes());
22 p.ct_eq(&e).into()
23}
24
25pub fn is_loopback(addr: &IpAddr) -> bool {
26 addr.is_loopback()
27}
28
29pub fn is_origin_allowed(origin: &str, allowed: &[String]) -> bool {
30 allowed.iter().any(|a| a == "*" || a == origin)
31}
32
33pub fn extract_bearer_token(header_value: &str) -> Option<&str> {
34 header_value.strip_prefix("Bearer ")
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40
41 #[test]
42 fn test_verify_token_matching() {
43 assert!(verify_token("abc123", "abc123"));
44 }
45
46 #[test]
47 fn test_verify_token_different() {
48 assert!(!verify_token("abc123", "xyz789"));
49 }
50
51 #[test]
52 fn test_verify_token_different_lengths() {
53 assert!(!verify_token("short", "much-longer-token"));
54 }
55
56 #[test]
57 fn test_verify_token_empty_expected_always_false() {
58 assert!(!verify_token("", ""));
59 assert!(!verify_token("", "notempty"));
60 assert!(!verify_token("anything", ""));
61 }
62
63 #[test]
64 fn test_loopback_ipv4() {
65 let addr: IpAddr = "127.0.0.1".parse().unwrap();
66 assert!(is_loopback(&addr));
67 }
68
69 #[test]
70 fn test_loopback_ipv6() {
71 let addr: IpAddr = "::1".parse().unwrap();
72 assert!(is_loopback(&addr));
73 }
74
75 #[test]
76 fn test_not_loopback_0000() {
77 let addr: IpAddr = "0.0.0.0".parse().unwrap();
78 assert!(!is_loopback(&addr));
79 }
80
81 #[test]
82 fn test_not_loopback_ipv6_unspecified() {
83 let addr: IpAddr = "::".parse().unwrap();
84 assert!(!is_loopback(&addr));
85 }
86
87 #[test]
88 fn test_not_loopback_lan() {
89 let addr: IpAddr = "192.168.1.1".parse().unwrap();
90 assert!(!is_loopback(&addr));
91 }
92
93 #[test]
94 fn test_origin_allowed_exact_match() {
95 let allowed = vec!["https://claude.ai".into(), "https://chatgpt.com".into()];
96 assert!(is_origin_allowed("https://claude.ai", &allowed));
97 }
98
99 #[test]
100 fn test_origin_not_allowed() {
101 let allowed = vec!["https://claude.ai".into()];
102 assert!(!is_origin_allowed("https://evil.com", &allowed));
103 }
104
105 #[test]
106 fn test_origin_wildcard_allows_all() {
107 let allowed = vec!["*".into()];
108 assert!(is_origin_allowed("https://anything.com", &allowed));
109 }
110
111 #[test]
112 fn test_origin_empty_list_denies_all() {
113 let allowed: Vec<String> = vec![];
114 assert!(!is_origin_allowed("https://claude.ai", &allowed));
115 }
116
117 #[test]
118 fn test_extract_bearer_valid() {
119 assert_eq!(
120 extract_bearer_token("Bearer mytoken123"),
121 Some("mytoken123")
122 );
123 }
124
125 #[test]
126 fn test_extract_bearer_no_prefix() {
127 assert_eq!(extract_bearer_token("mytoken123"), None);
128 }
129
130 #[test]
131 fn test_extract_bearer_wrong_scheme() {
132 assert_eq!(extract_bearer_token("Basic abc123"), None);
133 }
134
135 #[test]
136 fn test_extract_bearer_case_sensitive() {
137 assert_eq!(extract_bearer_token("bearer mytoken"), None);
138 }
139
140 #[test]
141 fn test_extract_bearer_empty_token() {
142 assert_eq!(extract_bearer_token("Bearer "), Some(""));
143 }
144}