1pub mod hash;
7
8use base64::{Engine, engine::general_purpose::STANDARD};
9
10#[derive(Debug, Clone)]
12pub struct Credential {
13 username: String,
14 password: String,
15}
16
17impl Credential {
18 pub fn new(username: String, password: String) -> Self {
20 Self { username, password }
21 }
22
23 pub fn parse(value: &str) -> Option<Self> {
29 let (user, pass) = value.split_once(':')?;
30 if user.is_empty() {
31 return None;
32 }
33 Some(Self::new(user.to_string(), pass.to_string()))
34 }
35
36 pub fn username(&self) -> &str {
38 &self.username
39 }
40
41 pub fn password(&self) -> &str {
43 &self.password
44 }
45}
46
47#[derive(Debug, Clone)]
49pub struct Credentials {
50 entries: Vec<Credential>,
51}
52
53impl Credentials {
54 pub fn new() -> Self {
56 Self {
57 entries: Vec::new(),
58 }
59 }
60
61 pub fn from_entries(entries: Vec<Credential>) -> Self {
63 Self { entries }
64 }
65
66 pub fn is_empty(&self) -> bool {
68 self.entries.is_empty()
69 }
70
71 pub fn len(&self) -> usize {
73 self.entries.len()
74 }
75
76 pub fn iter(&self) -> impl Iterator<Item = &Credential> {
78 self.entries.iter()
79 }
80
81 pub fn verify(&self, auth_header: &str) -> bool {
86 let Some(encoded) = auth_header.strip_prefix("Basic ") else {
87 return false;
88 };
89
90 let Ok(decoded) = STANDARD.decode(encoded.trim()) else {
91 return false;
92 };
93
94 let Ok(decoded_str) = String::from_utf8(decoded) else {
95 return false;
96 };
97
98 let Some((user, password)) = decoded_str.split_once(':') else {
100 return false;
101 };
102
103 self.check(user, password)
104 }
105
106 fn check(&self, username: &str, password: &str) -> bool {
109 let mut found = false;
110 for cred in &self.entries {
111 let user_match = hash::constant_time_eq(cred.username.as_bytes(), username.as_bytes());
113 let pass_match = hash::verify(password, &cred.password);
115 if user_match && pass_match {
116 found = true;
117 }
118 }
119 found
120 }
121}
122
123impl Default for Credentials {
124 fn default() -> Self {
125 Self::new()
126 }
127}
128
129pub fn check_basic_auth(auth_header: Option<&str>, credentials: &Credentials) -> bool {
139 if credentials.is_empty() {
140 return true;
142 }
143
144 match auth_header {
145 Some(header) => credentials.verify(header),
146 None => false,
147 }
148}
149
150pub fn check_bearer_token(auth_header: Option<&str>, expected_token: Option<&str>) -> bool {
160 let Some(expected) = expected_token else {
161 return true;
163 };
164
165 if expected.is_empty() {
166 return true;
168 }
169
170 let Some(header) = auth_header else {
171 return false;
172 };
173
174 let Some(token) = header.strip_prefix("Bearer ") else {
175 return false;
176 };
177
178 hash::constant_time_eq(token.trim().as_bytes(), expected.as_bytes())
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_credential_parse_valid() {
188 let cred = Credential::parse("admin:secret").unwrap();
189 assert_eq!(cred.username(), "admin");
190 assert_eq!(cred.password(), "secret");
191 }
192
193 #[test]
194 fn test_credential_parse_with_colon_in_password() {
195 let cred = Credential::parse("admin:sec:ret").unwrap();
196 assert_eq!(cred.username(), "admin");
197 assert_eq!(cred.password(), "sec:ret");
198 }
199
200 #[test]
201 fn test_credential_parse_no_colon() {
202 assert!(Credential::parse("invalid").is_none());
203 }
204
205 #[test]
206 fn test_credential_parse_empty_user() {
207 assert!(Credential::parse(":password").is_none());
208 }
209
210 #[test]
211 fn test_credentials_empty() {
212 let creds = Credentials::new();
213 assert!(creds.is_empty());
214 assert_eq!(creds.len(), 0);
215 }
216
217 #[test]
218 fn test_credentials_from_entries() {
219 let entries = vec![
220 Credential::new("admin".to_string(), "secret".to_string()),
221 Credential::new("user".to_string(), "pass".to_string()),
222 ];
223 let creds = Credentials::from_entries(entries);
224 assert!(!creds.is_empty());
225 assert_eq!(creds.len(), 2);
226 }
227
228 #[test]
229 fn test_verify_plain_text() {
230 let creds = Credentials::from_entries(vec![Credential::new(
231 "admin".to_string(),
232 "secret".to_string(),
233 )]);
234
235 let header = format!("Basic {}", STANDARD.encode("admin:secret"));
236 assert!(creds.verify(&header));
237 }
238
239 #[test]
240 fn test_verify_wrong_password() {
241 let creds = Credentials::from_entries(vec![Credential::new(
242 "admin".to_string(),
243 "secret".to_string(),
244 )]);
245
246 let header = format!("Basic {}", STANDARD.encode("admin:wrong"));
247 assert!(!creds.verify(&header));
248 }
249
250 #[test]
251 fn test_verify_wrong_user() {
252 let creds = Credentials::from_entries(vec![Credential::new(
253 "admin".to_string(),
254 "secret".to_string(),
255 )]);
256
257 let header = format!("Basic {}", STANDARD.encode("wrong:secret"));
258 assert!(!creds.verify(&header));
259 }
260
261 #[test]
262 fn test_verify_bcrypt() {
263 let hash = "$2y$05$bvIG6Nmid91Mu9RcmmWZfO5HJIMCT8riNW0hEp8f6/FuA2/mHZFpe";
265 let creds =
266 Credentials::from_entries(vec![Credential::new("user".to_string(), hash.to_string())]);
267
268 let header = format!("Basic {}", STANDARD.encode("user:password"));
269 assert!(creds.verify(&header));
270 }
271
272 #[test]
273 fn test_verify_sha1() {
274 let hash = "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=";
276 let creds =
277 Credentials::from_entries(vec![Credential::new("user".to_string(), hash.to_string())]);
278
279 let header = format!("Basic {}", STANDARD.encode("user:password"));
280 assert!(creds.verify(&header));
281 }
282
283 #[test]
284 fn test_verify_apr1() {
285 let hash = "$apr1$lZL6V/ci$eIMz/iKDkbtys/uU7LEK00";
287 let creds =
288 Credentials::from_entries(vec![Credential::new("user".to_string(), hash.to_string())]);
289
290 let header = format!("Basic {}", STANDARD.encode("user:password"));
291 assert!(creds.verify(&header));
292 }
293
294 #[test]
295 fn test_verify_multiple_credentials() {
296 let creds = Credentials::from_entries(vec![
297 Credential::new("admin".to_string(), "admin123".to_string()),
298 Credential::new("user1".to_string(), "pass1".to_string()),
299 Credential::new("user2".to_string(), "pass2".to_string()),
300 ]);
301
302 assert!(creds.verify(&format!("Basic {}", STANDARD.encode("admin:admin123"))));
303 assert!(creds.verify(&format!("Basic {}", STANDARD.encode("user1:pass1"))));
304 assert!(creds.verify(&format!("Basic {}", STANDARD.encode("user2:pass2"))));
305 assert!(!creds.verify(&format!("Basic {}", STANDARD.encode("unknown:pass"))));
306 }
307
308 #[test]
309 fn test_verify_invalid_base64() {
310 let creds = Credentials::from_entries(vec![Credential::new(
311 "admin".to_string(),
312 "secret".to_string(),
313 )]);
314 assert!(!creds.verify("Basic not-valid-base64!!!"));
315 }
316
317 #[test]
318 fn test_verify_non_basic_auth() {
319 let creds = Credentials::from_entries(vec![Credential::new(
320 "admin".to_string(),
321 "secret".to_string(),
322 )]);
323 assert!(!creds.verify("Bearer some-token"));
324 }
325
326 #[test]
327 fn test_verify_missing_colon_in_decoded() {
328 let creds = Credentials::from_entries(vec![Credential::new(
329 "admin".to_string(),
330 "secret".to_string(),
331 )]);
332 let header = format!("Basic {}", STANDARD.encode("no-colon-here"));
333 assert!(!creds.verify(&header));
334 }
335
336 #[test]
337 fn test_check_basic_auth_no_credentials() {
338 let creds = Credentials::new();
339 assert!(check_basic_auth(None, &creds));
341 assert!(check_basic_auth(Some("Basic anything"), &creds));
342 }
343
344 #[test]
345 fn test_check_basic_auth_with_credentials() {
346 let creds = Credentials::from_entries(vec![Credential::new(
347 "admin".to_string(),
348 "secret".to_string(),
349 )]);
350
351 assert!(!check_basic_auth(None, &creds));
353
354 let valid_header = format!("Basic {}", STANDARD.encode("admin:secret"));
356 assert!(check_basic_auth(Some(&valid_header), &creds));
357
358 let invalid_header = format!("Basic {}", STANDARD.encode("admin:wrong"));
360 assert!(!check_basic_auth(Some(&invalid_header), &creds));
361 }
362
363 #[test]
368 fn test_check_bearer_token_no_token_configured() {
369 assert!(check_bearer_token(None, None));
371 assert!(check_bearer_token(Some("Bearer anything"), None));
372 }
373
374 #[test]
375 fn test_check_bearer_token_empty_token() {
376 assert!(check_bearer_token(None, Some("")));
378 assert!(check_bearer_token(Some("Bearer anything"), Some("")));
379 }
380
381 #[test]
382 fn test_check_bearer_token_valid() {
383 let token = "my-secret-token";
384 let header = "Bearer my-secret-token";
385 assert!(check_bearer_token(Some(header), Some(token)));
386 }
387
388 #[test]
389 fn test_check_bearer_token_valid_with_whitespace() {
390 let token = "my-secret-token";
391 let header = "Bearer my-secret-token ";
392 assert!(check_bearer_token(Some(header), Some(token)));
393 }
394
395 #[test]
396 fn test_check_bearer_token_invalid() {
397 let token = "my-secret-token";
398 let header = "Bearer wrong-token";
399 assert!(!check_bearer_token(Some(header), Some(token)));
400 }
401
402 #[test]
403 fn test_check_bearer_token_no_header() {
404 let token = "my-secret-token";
405 assert!(!check_bearer_token(None, Some(token)));
406 }
407
408 #[test]
409 fn test_check_bearer_token_basic_auth_header() {
410 let token = "my-secret-token";
412 let header = format!("Basic {}", STANDARD.encode("user:pass"));
413 assert!(!check_bearer_token(Some(&header), Some(token)));
414 }
415
416 #[test]
417 fn test_check_bearer_token_case_sensitive() {
418 let token = "MySecretToken";
419 assert!(check_bearer_token(
420 Some("Bearer MySecretToken"),
421 Some(token)
422 ));
423 assert!(!check_bearer_token(
424 Some("Bearer mysecrettoken"),
425 Some(token)
426 ));
427 }
428}