1fn constant_time_eq(a: &str, b: &str) -> bool {
8 if a.len() != b.len() {
9 return false;
10 }
11 a.bytes()
12 .zip(b.bytes())
13 .fold(0, |acc, (x, y)| acc | (x ^ y))
14 == 0
15}
16
17pub struct BearerAuth {
19 token: String,
20}
21
22impl std::fmt::Debug for BearerAuth {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 f.debug_struct("BearerAuth")
26 .field("token", &"<redacted>")
27 .finish()
28 }
29}
30
31impl BearerAuth {
32 pub fn new(token: impl Into<String>) -> Self {
34 Self {
35 token: token.into(),
36 }
37 }
38
39 pub fn generate() -> Self {
51 Self::try_generate().expect("OS entropy source unavailable")
52 }
53
54 pub fn try_generate() -> crate::error::Result<Self> {
56 let mut bytes = [0u8; 16];
57 getrandom::fill(&mut bytes).map_err(|e| {
58 crate::error::KernelError::Config(format!("failed to read OS entropy for token: {e}"))
59 })?;
60 let mut token = String::with_capacity(32);
61 for b in bytes {
62 use std::fmt::Write;
63 let _ = write!(token, "{b:02x}");
64 }
65 Ok(Self { token })
66 }
67
68 pub fn validate(&self, header_value: &str) -> bool {
73 match header_value.split_once(' ') {
74 Some((scheme, token)) if scheme.eq_ignore_ascii_case("Bearer") => {
75 constant_time_eq(token.trim(), &self.token)
76 }
77 _ => false,
78 }
79 }
80
81 pub fn token(&self) -> &str {
83 &self.token
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn validate_correct_token() {
93 let auth = BearerAuth::new("my-secret-token");
94 assert!(auth.validate("Bearer my-secret-token"));
95 }
96
97 #[test]
98 fn reject_wrong_token() {
99 let auth = BearerAuth::new("correct");
100 assert!(!auth.validate("Bearer wrong"));
101 }
102
103 #[test]
104 fn reject_missing_prefix() {
105 let auth = BearerAuth::new("token");
106 assert!(!auth.validate("token"));
107 assert!(!auth.validate("Basic token"));
108 }
109
110 #[test]
111 fn scheme_match_is_case_insensitive() {
112 let auth = BearerAuth::new("token");
113 assert!(auth.validate("bearer token"));
114 assert!(auth.validate("BEARER token"));
115 assert!(auth.validate("Bearer token"));
116 }
117
118 #[test]
119 fn generate_produces_32_char_hex() {
120 let auth = BearerAuth::generate();
121 let token = auth.token();
122 assert_eq!(token.len(), 32);
123 assert!(token.chars().all(|c| c.is_ascii_hexdigit()));
124 }
125
126 #[test]
127 fn generate_unique() {
128 let a = BearerAuth::generate();
129 let b = BearerAuth::generate();
130 assert_ne!(a.token(), b.token());
131 }
132
133 #[test]
134 fn debug_never_prints_token() {
135 let auth = BearerAuth::new("super-secret-token");
136 let dbg = format!("{auth:?}");
137 assert!(!dbg.contains("super-secret-token"), "{dbg}");
138 }
139}