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