1use std::iter;
2use std::sync::Arc;
3use std::time::Duration;
4
5use axum::extract::FromRequestParts;
6use axum::http::request::Parts;
7use axum::http::{HeaderMap, StatusCode};
8use base64::Engine;
9use base64::engine::general_purpose::STANDARD;
10use kellnr_appstate::AppStateData;
11use kellnr_common::token_cache::CachedTokenData;
12use kellnr_db::DbProvider;
13use kellnr_db::error::DbError;
14use kellnr_db::password::hash_token;
15use rand::distr::Alphanumeric;
16use rand::{RngExt, rng};
17use serde::Deserialize;
18use tokio::time::sleep;
19use tracing::warn;
20use utoipa::ToSchema;
21
22use crate::password::{PasswordAuthError, authenticate_password};
23
24#[derive(Debug)]
25pub struct Token {
26 pub value: String,
27 pub user: String,
28 pub is_admin: bool,
29 pub is_read_only: bool,
30}
31
32#[derive(Debug)]
34pub enum OptionToken {
35 None,
36 Some(Token),
37}
38
39pub fn generate_token() -> String {
40 let mut rng = rng();
41 iter::repeat(())
42 .map(|()| rng.sample(Alphanumeric))
43 .map(char::from)
44 .take(32)
45 .collect::<String>()
46}
47
48impl Token {
49 pub async fn from_header(
50 headers: &HeaderMap,
51 state: &AppStateData,
52 ) -> Result<Self, StatusCode> {
53 Self::extract_token(headers, state).await
54 }
55
56 async fn extract_token(headers: &HeaderMap, state: &AppStateData) -> Result<Token, StatusCode> {
57 let (db, cache) = (&state.db, &state.token_cache);
58 let mut token = headers
60 .get("Authorization")
61 .ok_or(StatusCode::UNAUTHORIZED)?
62 .to_str()
63 .map_err(|_| StatusCode::BAD_REQUEST)?;
64
65 if token.starts_with("Basic ") || token.starts_with("basic ") {
67 if state.settings.oauth2.enforced {
68 return Err(StatusCode::FORBIDDEN);
69 }
70
71 let decoded = STANDARD
72 .decode(&token[6..])
73 .map_err(|_| StatusCode::BAD_REQUEST)?;
74 let decoded_str = String::from_utf8(decoded).map_err(|_| StatusCode::BAD_REQUEST)?;
75 let (user, pwd) = decoded_str.split_once(':').ok_or(StatusCode::BAD_REQUEST)?;
76
77 return authenticate_basic(user, pwd, state).await;
78 }
79
80 if token.starts_with("Bearer ") || token.starts_with("bearer ") {
82 token = &token[7..];
83 }
84
85 if let Some(cached) = cache.get(token).await {
87 return Ok(Token {
88 value: token.to_string(),
89 user: cached.user,
90 is_admin: cached.is_admin,
91 is_read_only: cached.is_read_only,
92 });
93 }
94
95 let generation = cache.generation();
99 let Ok(user) = get_user_with_retry(
100 db,
101 token,
102 state.settings.registry.token_db_retry_count,
103 state.settings.registry.token_db_retry_delay_ms,
104 )
105 .await
106 else {
107 return Err(StatusCode::FORBIDDEN);
108 };
109
110 cache
112 .insert(
113 token.to_string(),
114 CachedTokenData {
115 user: user.name.clone(),
116 is_admin: user.is_admin,
117 is_read_only: user.is_read_only,
118 },
119 generation,
120 )
121 .await;
122
123 Ok(Token {
124 value: token.to_string(),
125 user: user.name,
126 is_admin: user.is_admin,
127 is_read_only: user.is_read_only,
128 })
129 }
130}
131
132fn basic_auth_cache_key(user: &str, pwd: &str) -> String {
136 format!("basic:{}", hash_token(&format!("{user}:{pwd}")))
137}
138
139async fn authenticate_basic(
145 user: &str,
146 pwd: &str,
147 state: &AppStateData,
148) -> Result<Token, StatusCode> {
149 let cache = &state.token_cache;
150 let cache_key = basic_auth_cache_key(user, pwd);
151 if let Some(cached) = cache.get(&cache_key).await {
152 return Ok(Token {
153 value: pwd.to_string(),
154 user: cached.user,
155 is_admin: cached.is_admin,
156 is_read_only: cached.is_read_only,
157 });
158 }
159
160 let generation = cache.generation();
161 let user = authenticate_password(state.db.as_ref(), &state.login_throttle, user, pwd)
162 .await
163 .map_err(|e| match e {
164 PasswordAuthError::Throttled => StatusCode::TOO_MANY_REQUESTS,
165 PasswordAuthError::Invalid => StatusCode::FORBIDDEN,
166 })?;
167
168 cache
169 .insert(
170 cache_key,
171 CachedTokenData {
172 user: user.name.clone(),
173 is_admin: user.is_admin,
174 is_read_only: user.is_read_only,
175 },
176 generation,
177 )
178 .await;
179
180 Ok(Token {
181 value: pwd.to_string(),
182 user: user.name,
183 is_admin: user.is_admin,
184 is_read_only: user.is_read_only,
185 })
186}
187
188async fn get_user_with_retry(
189 db: &Arc<dyn DbProvider>,
190 token: &str,
191 max_retries: u32,
192 delay_ms: u64,
193) -> Result<kellnr_db::User, DbError> {
194 let mut attempts = 0;
195
196 loop {
197 match db.get_user_from_token(token).await {
198 Ok(user) => return Ok(user),
199 Err(e) => {
200 if matches!(e, DbError::TokenNotFound | DbError::UserNotFound(_)) {
202 return Err(e);
203 }
204
205 attempts += 1;
206 if attempts > max_retries {
207 warn!(
208 "Failed to get user from token after {} retries: {}",
209 max_retries, e
210 );
211 return Err(e);
212 }
213
214 warn!(
215 "Transient DB error on attempt {}/{}, retrying in {}ms: {}",
216 attempts,
217 max_retries + 1,
218 delay_ms,
219 e
220 );
221 sleep(Duration::from_millis(delay_ms)).await;
222 }
223 }
224 }
225}
226
227impl FromRequestParts<AppStateData> for Token {
228 type Rejection = StatusCode;
229
230 async fn from_request_parts(
231 parts: &mut Parts,
232 state: &AppStateData,
233 ) -> Result<Self, Self::Rejection> {
234 Self::extract_token(&parts.headers, state).await
235 }
236}
237
238impl FromRequestParts<AppStateData> for OptionToken {
239 type Rejection = StatusCode;
240
241 async fn from_request_parts(
242 parts: &mut Parts,
243 state: &AppStateData,
244 ) -> Result<Self, Self::Rejection> {
245 match Token::extract_token(&parts.headers, state).await {
246 Ok(token) => Ok(OptionToken::Some(token)),
247 Err(StatusCode::UNAUTHORIZED) => Ok(OptionToken::None),
248 Err(status_code) => Err(status_code),
249 }
250 }
251}
252
253#[derive(Deserialize, ToSchema)]
254pub struct NewTokenReqData {
255 pub name: String,
256}
257
258#[cfg(test)]
259mod tests {
260 use std::sync::atomic::{AtomicU32, Ordering};
261
262 use kellnr_common::login_throttle::LoginThrottle;
263 use kellnr_common::token_cache::TokenCacheManager;
264 use kellnr_db::User;
265 use kellnr_db::error::DbError;
266 use kellnr_db::mock::MockDb;
267 use kellnr_settings::Settings;
268 use mockall::predicate::*;
269
270 use super::*;
271
272 fn test_user() -> User {
273 User {
274 id: 1,
275 name: "test_user".to_string(),
276 pwd: String::new(),
277 salt: String::new(),
278 is_admin: false,
279 is_read_only: false,
280 created: String::new(),
281 }
282 }
283
284 fn test_settings() -> Arc<Settings> {
285 Arc::new(Settings {
286 registry: kellnr_settings::Registry {
287 token_cache_enabled: true,
288 token_cache_ttl_seconds: 60,
289 token_cache_max_capacity: 100,
290 token_db_retry_count: 3,
291 token_db_retry_delay_ms: 1,
292 ..kellnr_settings::Registry::default()
293 },
294 ..Settings::default()
295 })
296 }
297
298 fn app_state(
299 db: &Arc<dyn DbProvider>,
300 cache: &Arc<TokenCacheManager>,
301 settings: &Arc<Settings>,
302 ) -> AppStateData {
303 AppStateData {
304 db: db.clone(),
305 token_cache: cache.clone(),
306 settings: settings.clone(),
307 ..kellnr_appstate::test_state()
308 }
309 }
310
311 fn sso_enforced_settings() -> Arc<Settings> {
313 let mut settings = Settings::clone(&test_settings());
314 settings.oauth2.enabled = true;
315 settings.oauth2.enforced = true;
316 Arc::new(settings)
317 }
318
319 fn basic_auth_header(user: &str, pwd: &str) -> HeaderMap {
320 let encoded = STANDARD.encode(format!("{user}:{pwd}"));
321 let mut headers = HeaderMap::new();
322 headers.insert("Authorization", format!("Basic {encoded}").parse().unwrap());
323 headers
324 }
325
326 #[tokio::test]
331 async fn test_retry_succeeds_on_first_attempt() {
332 let mut mock_db = MockDb::new();
334 mock_db
335 .expect_get_user_from_token()
336 .with(eq("valid_token"))
337 .times(1)
338 .returning(|_| Ok(test_user()));
339
340 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
341 let result = get_user_with_retry(&db, "valid_token", 3, 10).await;
342
343 assert!(result.is_ok());
344 assert_eq!(result.unwrap().name, "test_user");
345 }
346
347 #[tokio::test]
348 async fn test_retry_succeeds_after_transient_error() {
349 let call_count = Arc::new(AtomicU32::new(0));
351 let call_count_clone = call_count.clone();
352
353 let mut mock_db = MockDb::new();
354 mock_db
355 .expect_get_user_from_token()
356 .with(eq("token"))
357 .times(2)
358 .returning(move |_| {
359 let count = call_count_clone.fetch_add(1, Ordering::SeqCst);
360 if count == 0 {
361 Err(DbError::PostgresError(sea_orm::DbErr::ConnectionAcquire(
362 sea_orm::error::ConnAcquireErr::Timeout,
363 )))
364 } else {
365 Ok(test_user())
366 }
367 });
368
369 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
370 let result = get_user_with_retry(&db, "token", 3, 1).await;
371
372 assert!(result.is_ok());
373 assert_eq!(call_count.load(Ordering::SeqCst), 2);
374 }
375
376 #[tokio::test]
377 async fn test_no_retry_on_token_not_found() {
378 let mut mock_db = MockDb::new();
380 mock_db
381 .expect_get_user_from_token()
382 .with(eq("invalid_token"))
383 .times(1) .returning(|_| Err(DbError::TokenNotFound));
385
386 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
387 let result = get_user_with_retry(&db, "invalid_token", 3, 10).await;
388
389 assert!(result.is_err());
390 assert!(matches!(result.unwrap_err(), DbError::TokenNotFound));
391 }
392
393 #[tokio::test]
394 async fn test_no_retry_on_user_not_found() {
395 let mut mock_db = MockDb::new();
397 mock_db
398 .expect_get_user_from_token()
399 .with(eq("orphan_token"))
400 .times(1)
401 .returning(|_| Err(DbError::UserNotFound("orphan".to_string())));
402
403 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
404 let result = get_user_with_retry(&db, "orphan_token", 3, 10).await;
405
406 assert!(result.is_err());
407 assert!(matches!(result.unwrap_err(), DbError::UserNotFound(_)));
408 }
409
410 #[tokio::test]
411 async fn test_exhausts_retries_on_persistent_error() {
412 let call_count = Arc::new(AtomicU32::new(0));
414 let call_count_clone = call_count.clone();
415
416 let mut mock_db = MockDb::new();
417 mock_db
418 .expect_get_user_from_token()
419 .with(eq("token"))
420 .times(4) .returning(move |_| {
422 call_count_clone.fetch_add(1, Ordering::SeqCst);
423 Err(DbError::PostgresError(sea_orm::DbErr::ConnectionAcquire(
424 sea_orm::error::ConnAcquireErr::Timeout,
425 )))
426 });
427
428 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
429 let result = get_user_with_retry(&db, "token", 3, 1).await;
430
431 assert!(result.is_err());
432 assert_eq!(call_count.load(Ordering::SeqCst), 4);
433 }
434
435 #[tokio::test]
440 async fn test_cache_hit_returns_cached_token() {
441 let cache = Arc::new(TokenCacheManager::new(true, 60, 100));
443 cache
444 .insert(
445 "cached_token".to_string(),
446 CachedTokenData {
447 user: "cached_user".to_string(),
448 is_admin: true,
449 is_read_only: false,
450 },
451 cache.generation(),
452 )
453 .await;
454
455 let mock_db = MockDb::new(); let db: Arc<dyn DbProvider> = Arc::new(mock_db);
457 let settings = test_settings();
458
459 let mut headers = HeaderMap::new();
460 headers.insert("Authorization", "Bearer cached_token".parse().unwrap());
461
462 let result = Token::from_header(&headers, &app_state(&db, &cache, &settings)).await;
463
464 assert!(result.is_ok());
465 let token = result.unwrap();
466 assert_eq!(token.user, "cached_user");
467 assert!(token.is_admin);
468 }
469
470 #[tokio::test]
471 async fn test_cache_miss_queries_db_and_caches() {
472 let cache = Arc::new(TokenCacheManager::new(true, 60, 100));
473
474 let mut mock_db = MockDb::new();
475 mock_db
476 .expect_get_user_from_token()
477 .with(eq("new_token"))
478 .times(1)
479 .returning(|_| {
480 Ok(User {
481 id: 1,
482 name: "db_user".to_string(),
483 pwd: String::new(),
484 salt: String::new(),
485 is_admin: false,
486 is_read_only: true,
487 created: String::new(),
488 })
489 });
490
491 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
492 let settings = test_settings();
493
494 let mut headers = HeaderMap::new();
495 headers.insert("Authorization", "Bearer new_token".parse().unwrap());
496
497 let result = Token::from_header(&headers, &app_state(&db, &cache, &settings)).await;
498
499 assert!(result.is_ok());
500 let token = result.unwrap();
501 assert_eq!(token.user, "db_user");
502 assert!(token.is_read_only);
503
504 let cached = cache.get("new_token").await;
506 assert!(cached.is_some());
507 assert_eq!(cached.unwrap().user, "db_user");
508 }
509
510 #[tokio::test]
513 async fn test_invalidation_during_lookup_is_not_cached() {
514 let cache = Arc::new(TokenCacheManager::new(true, 60, 100));
515 let cache_in_db = cache.clone();
516
517 let mut mock_db = MockDb::new();
518 mock_db
519 .expect_get_user_from_token()
520 .with(eq("racy_token"))
521 .times(1)
522 .returning(move |_| {
523 cache_in_db.invalidate_all();
524 Ok(test_user())
525 });
526
527 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
528 let mut headers = HeaderMap::new();
529 headers.insert("Authorization", "Bearer racy_token".parse().unwrap());
530
531 let token = Token::from_header(&headers, &app_state(&db, &cache, &test_settings()))
532 .await
533 .unwrap();
534
535 assert_eq!(token.user, "test_user");
536 assert!(cache.get("racy_token").await.is_none());
537 }
538
539 #[tokio::test]
540 async fn test_cache_miss_with_invalid_token_returns_forbidden() {
541 let cache = Arc::new(TokenCacheManager::new(true, 60, 100));
542
543 let mut mock_db = MockDb::new();
544 mock_db
545 .expect_get_user_from_token()
546 .with(eq("bad_token"))
547 .times(1) .returning(|_| Err(DbError::TokenNotFound));
549
550 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
551 let settings = test_settings();
552
553 let mut headers = HeaderMap::new();
554 headers.insert("Authorization", "Bearer bad_token".parse().unwrap());
555
556 let result = Token::from_header(&headers, &app_state(&db, &cache, &settings)).await;
557
558 assert!(result.is_err());
559 assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
560
561 let cached = cache.get("bad_token").await;
563 assert!(cached.is_none());
564 }
565
566 #[tokio::test]
574 async fn test_basic_auth_rejected_when_sso_enforced() {
575 let cache = Arc::new(TokenCacheManager::new(true, 60, 100));
576
577 let mut mock_db = MockDb::new();
578 mock_db.expect_get_user().never();
579 mock_db.expect_authenticate_user().never();
580
581 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
582 let headers = basic_auth_header("test_user", "correct_password");
583
584 let result =
585 Token::from_header(&headers, &app_state(&db, &cache, &sso_enforced_settings())).await;
586
587 assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
588 }
589
590 #[tokio::test]
592 async fn test_lowercase_basic_auth_rejected_when_sso_enforced() {
593 let cache = Arc::new(TokenCacheManager::new(true, 60, 100));
594
595 let mut mock_db = MockDb::new();
596 mock_db.expect_get_user().never();
597 mock_db.expect_authenticate_user().never();
598
599 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
600 let encoded = STANDARD.encode("test_user:correct_password");
601 let mut headers = HeaderMap::new();
602 headers.insert("Authorization", format!("basic {encoded}").parse().unwrap());
603
604 let result =
605 Token::from_header(&headers, &app_state(&db, &cache, &sso_enforced_settings())).await;
606
607 assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
608 }
609
610 #[tokio::test]
613 async fn test_bearer_token_still_works_when_sso_enforced() {
614 let cache = Arc::new(TokenCacheManager::new(true, 60, 100));
615
616 let mut mock_db = MockDb::new();
617 mock_db
618 .expect_get_user_from_token()
619 .with(eq("valid_token"))
620 .times(1)
621 .returning(|_| Ok(test_user()));
622
623 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
624 let mut headers = HeaderMap::new();
625 headers.insert("Authorization", "Bearer valid_token".parse().unwrap());
626
627 let token = Token::from_header(&headers, &app_state(&db, &cache, &sso_enforced_settings()))
628 .await
629 .expect("bearer tokens must remain valid under SSO enforcement");
630
631 assert_eq!(token.user, "test_user");
632 }
633
634 #[tokio::test]
636 async fn test_basic_auth_allowed_when_sso_not_enforced() {
637 let cache = Arc::new(TokenCacheManager::new(true, 60, 100));
638
639 let mut mock_db = MockDb::new();
640 mock_db
641 .expect_authenticate_user()
642 .with(eq("test_user"), eq("correct_password"))
643 .times(1)
644 .returning(|_, _| Ok(test_user()));
645
646 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
647 let headers = basic_auth_header("test_user", "correct_password");
648
649 let token = Token::from_header(&headers, &app_state(&db, &cache, &test_settings()))
650 .await
651 .expect("basic auth should work when SSO is not enforced");
652
653 assert_eq!(token.user, "test_user");
654 }
655
656 #[tokio::test]
663 async fn test_basic_auth_success_is_cached() {
664 let cache = Arc::new(TokenCacheManager::new(true, 60, 100));
665
666 let mut mock_db = MockDb::new();
667 mock_db
668 .expect_authenticate_user()
669 .times(1)
670 .returning(|_, _| Ok(test_user()));
671
672 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
673 let headers = basic_auth_header("test_user", "correct_password");
674
675 for _ in 0..3 {
676 let token = Token::from_header(&headers, &app_state(&db, &cache, &test_settings()))
677 .await
678 .unwrap();
679 assert_eq!(token.user, "test_user");
680 }
681 }
682
683 #[tokio::test]
684 async fn test_basic_auth_failure_is_not_cached() {
685 let cache = Arc::new(TokenCacheManager::new(true, 60, 100));
686
687 let mut mock_db = MockDb::new();
688 mock_db
689 .expect_authenticate_user()
690 .times(2)
691 .returning(|_, _| Err(DbError::PasswordMismatch));
692
693 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
694 let headers = basic_auth_header("test_user", "wrong_password");
695
696 for _ in 0..2 {
697 let result =
698 Token::from_header(&headers, &app_state(&db, &cache, &test_settings())).await;
699 assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
700 }
701 }
702
703 #[tokio::test]
705 async fn test_basic_auth_cache_is_keyed_by_password() {
706 let cache = Arc::new(TokenCacheManager::new(true, 60, 100));
707
708 let mut mock_db = MockDb::new();
709 mock_db
710 .expect_authenticate_user()
711 .with(eq("test_user"), eq("correct_password"))
712 .times(1)
713 .returning(|_, _| Ok(test_user()));
714 mock_db
715 .expect_authenticate_user()
716 .with(eq("test_user"), eq("wrong_password"))
717 .times(1)
718 .returning(|_, _| Err(DbError::PasswordMismatch));
719
720 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
721
722 let ok = basic_auth_header("test_user", "correct_password");
723 Token::from_header(&ok, &app_state(&db, &cache, &test_settings()))
724 .await
725 .unwrap();
726
727 let wrong = basic_auth_header("test_user", "wrong_password");
728 let result = Token::from_header(&wrong, &app_state(&db, &cache, &test_settings())).await;
729 assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
730 }
731
732 #[tokio::test]
735 async fn test_basic_auth_cache_respects_invalidation() {
736 let cache = Arc::new(TokenCacheManager::new(true, 60, 100));
737
738 let mut mock_db = MockDb::new();
739 mock_db
740 .expect_authenticate_user()
741 .times(1)
742 .returning(|_, _| Ok(test_user()));
743 mock_db
744 .expect_authenticate_user()
745 .times(1)
746 .returning(|_, _| Err(DbError::PasswordMismatch));
747
748 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
749 let headers = basic_auth_header("test_user", "old_password");
750
751 Token::from_header(&headers, &app_state(&db, &cache, &test_settings()))
752 .await
753 .unwrap();
754 cache.invalidate_all();
755
756 let result = Token::from_header(&headers, &app_state(&db, &cache, &test_settings())).await;
757 assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
758 }
759
760 #[tokio::test]
763 async fn test_basic_auth_failures_are_throttled() {
764 let cache = Arc::new(TokenCacheManager::new(true, 60, 100));
765
766 let mut mock_db = MockDb::new();
767 mock_db
768 .expect_authenticate_user()
769 .times(2)
770 .returning(|_, _| Err(DbError::PasswordMismatch));
771
772 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
773 let state = AppStateData {
774 login_throttle: Arc::new(LoginThrottle::new(2, Duration::from_mins(5))),
775 ..app_state(&db, &cache, &test_settings())
776 };
777 let headers = basic_auth_header("test_user", "guess");
778
779 for _ in 0..2 {
780 let result = Token::from_header(&headers, &state).await;
781 assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
782 }
783 let result = Token::from_header(&headers, &state).await;
784 assert_eq!(result.unwrap_err(), StatusCode::TOO_MANY_REQUESTS);
785 }
786
787 #[tokio::test]
790 async fn test_cached_basic_auth_bypasses_throttle() {
791 let cache = Arc::new(TokenCacheManager::new(true, 60, 100));
792
793 let mut mock_db = MockDb::new();
794 mock_db
795 .expect_authenticate_user()
796 .with(eq("test_user"), eq("correct_password"))
797 .times(1)
798 .returning(|_, _| Ok(test_user()));
799 mock_db
800 .expect_authenticate_user()
801 .with(eq("test_user"), eq("guess"))
802 .times(1)
803 .returning(|_, _| Err(DbError::PasswordMismatch));
804
805 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
806 let state = AppStateData {
807 login_throttle: Arc::new(LoginThrottle::new(1, Duration::from_mins(5))),
808 ..app_state(&db, &cache, &test_settings())
809 };
810 let valid = basic_auth_header("test_user", "correct_password");
811 Token::from_header(&valid, &state).await.unwrap();
812
813 let guess = basic_auth_header("test_user", "guess");
814 assert_eq!(
815 Token::from_header(&guess, &state).await.unwrap_err(),
816 StatusCode::FORBIDDEN
817 );
818 assert_eq!(
819 Token::from_header(&guess, &state).await.unwrap_err(),
820 StatusCode::TOO_MANY_REQUESTS
821 );
822
823 let token = Token::from_header(&valid, &state).await.unwrap();
824 assert_eq!(token.user, "test_user");
825 }
826
827 #[test]
828 fn test_basic_auth_cache_key_does_not_contain_password() {
829 let key = basic_auth_cache_key("test_user", "secret");
830
831 assert!(key.starts_with("basic:"));
832 assert!(!key.contains("secret"));
833 assert_ne!(key, basic_auth_cache_key("test_user", "other"));
834 }
835
836 #[tokio::test]
837 async fn test_no_authorization_header_returns_unauthorized() {
838 let cache = Arc::new(TokenCacheManager::new(true, 60, 100));
839 let mock_db = MockDb::new();
840 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
841 let settings = test_settings();
842
843 let headers = HeaderMap::new(); let result = Token::from_header(&headers, &app_state(&db, &cache, &settings)).await;
846
847 assert!(result.is_err());
848 assert_eq!(result.unwrap_err(), StatusCode::UNAUTHORIZED);
849 }
850
851 #[tokio::test]
852 async fn test_disabled_cache_always_queries_db() {
853 let cache = Arc::new(TokenCacheManager::new(false, 60, 100)); let call_count = Arc::new(AtomicU32::new(0));
857 let call_count_clone = call_count.clone();
858
859 let mut mock_db = MockDb::new();
860 mock_db
861 .expect_get_user_from_token()
862 .with(eq("token"))
863 .times(2) .returning(move |_| {
865 call_count_clone.fetch_add(1, Ordering::SeqCst);
866 Ok(test_user())
867 });
868
869 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
870 let settings = test_settings();
871
872 let mut headers = HeaderMap::new();
873 headers.insert("Authorization", "Bearer token".parse().unwrap());
874
875 let _ = Token::from_header(&headers, &app_state(&db, &cache, &settings)).await;
877 let _ = Token::from_header(&headers, &app_state(&db, &cache, &settings)).await;
879
880 assert_eq!(call_count.load(Ordering::SeqCst), 2);
881 }
882
883 #[tokio::test]
884 async fn test_lowercase_bearer_prefix_works() {
885 let cache = Arc::new(TokenCacheManager::new(true, 60, 100));
887
888 let mut mock_db = MockDb::new();
889 mock_db
890 .expect_get_user_from_token()
891 .with(eq("lowercase_token"))
892 .times(1)
893 .returning(|_| Ok(test_user()));
894
895 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
896 let settings = test_settings();
897
898 let mut headers = HeaderMap::new();
899 headers.insert("Authorization", "bearer lowercase_token".parse().unwrap());
900
901 let result = Token::from_header(&headers, &app_state(&db, &cache, &settings)).await;
902
903 assert!(result.is_ok());
904 assert_eq!(result.unwrap().user, "test_user");
905 }
906
907 #[tokio::test]
908 async fn test_zero_retries_only_attempts_once() {
909 let call_count = Arc::new(AtomicU32::new(0));
911 let call_count_clone = call_count.clone();
912
913 let mut mock_db = MockDb::new();
914 mock_db
915 .expect_get_user_from_token()
916 .with(eq("token"))
917 .times(1)
918 .returning(move |_| {
919 call_count_clone.fetch_add(1, Ordering::SeqCst);
920 Err(DbError::PostgresError(sea_orm::DbErr::ConnectionAcquire(
921 sea_orm::error::ConnAcquireErr::Timeout,
922 )))
923 });
924
925 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
926 let result = get_user_with_retry(&db, "token", 0, 1).await;
927
928 assert!(result.is_err());
929 assert_eq!(call_count.load(Ordering::SeqCst), 1);
930 }
931}