1use std::iter;
2use std::sync::Arc;
3use std::time::Duration;
4
5use axum::http::{HeaderMap, StatusCode};
6use base64::Engine;
7use base64::engine::general_purpose::STANDARD;
8use kellnr_common::account_name::UserName;
9use kellnr_common::plain_token::PlainToken;
10use kellnr_db::DbProvider;
11use kellnr_db::error::DbError;
12use kellnr_db::password::hash_token;
13use rand::distr::Alphanumeric;
14use rand::{RngExt, rng};
15use serde::Deserialize;
16use tokio::time::sleep;
17use tracing::warn;
18use utoipa::ToSchema;
19
20use crate::password::{PasswordAuthError, authenticate_password};
21use crate::state::AppStateData;
22use crate::token_cache::CachedTokenData;
23
24#[derive(Debug)]
26pub(crate) struct Token {
27 pub value: Option<PlainToken>,
29 pub user: UserName,
30 pub is_admin: bool,
31 pub is_read_only: bool,
32}
33
34pub fn generate_token() -> PlainToken {
35 let mut rng = rng();
36 iter::repeat(())
37 .map(|()| rng.sample(Alphanumeric))
38 .map(char::from)
39 .take(32)
40 .collect::<String>()
41 .into()
42}
43
44impl Token {
45 pub(crate) async fn from_header(
48 headers: &HeaderMap,
49 state: &AppStateData,
50 ) -> Result<Token, StatusCode> {
51 let (db, cache) = (&state.db, &state.token_cache);
52 let mut token = headers
53 .get("Authorization")
54 .ok_or(StatusCode::UNAUTHORIZED)?
55 .to_str()
56 .map_err(|_| StatusCode::BAD_REQUEST)?;
57
58 if let Some(credentials) = strip_scheme(token, "Basic") {
60 if state.settings.oauth2.enforced {
61 return Err(StatusCode::FORBIDDEN);
62 }
63
64 let decoded = STANDARD
65 .decode(credentials)
66 .map_err(|_| StatusCode::BAD_REQUEST)?;
67 let decoded_str = String::from_utf8(decoded).map_err(|_| StatusCode::BAD_REQUEST)?;
68 let (user, pwd) = decoded_str.split_once(':').ok_or(StatusCode::BAD_REQUEST)?;
69
70 return authenticate_basic(user, pwd, state).await;
71 }
72
73 if let Some(bearer) = strip_scheme(token, "Bearer") {
75 token = bearer;
76 }
77
78 if let Some(cached) = cache.get(token).await {
80 return Ok(Token {
81 value: Some(PlainToken::from(token)),
82 user: cached.user,
83 is_admin: cached.is_admin,
84 is_read_only: cached.is_read_only,
85 });
86 }
87
88 let generation = cache.generation();
92 let Ok(user) = get_user_with_retry(
93 db,
94 &PlainToken::from(token),
95 state.settings.registry.token_db_retry_count,
96 state.settings.registry.token_db_retry_delay_ms,
97 )
98 .await
99 else {
100 return Err(StatusCode::FORBIDDEN);
101 };
102
103 cache
105 .insert(
106 token.to_string(),
107 CachedTokenData {
108 user: user.name.clone(),
109 is_admin: user.is_admin,
110 is_read_only: user.is_read_only,
111 },
112 generation,
113 )
114 .await;
115
116 Ok(Token {
117 value: Some(PlainToken::from(token)),
118 user: user.name,
119 is_admin: user.is_admin,
120 is_read_only: user.is_read_only,
121 })
122 }
123}
124
125fn basic_auth_cache_key(user: &str, pwd: &str) -> String {
129 format!("basic:{}", hash_token(&format!("{user}:{pwd}")))
130}
131
132async fn authenticate_basic(
138 user: &str,
139 pwd: &str,
140 state: &AppStateData,
141) -> Result<Token, StatusCode> {
142 let cache = &state.token_cache;
143 let cache_key = basic_auth_cache_key(user, pwd);
144 if let Some(cached) = cache.get(&cache_key).await {
145 return Ok(Token {
146 value: None,
147 user: cached.user,
148 is_admin: cached.is_admin,
149 is_read_only: cached.is_read_only,
150 });
151 }
152
153 let user = UserName::new(user).map_err(|_| StatusCode::FORBIDDEN)?;
155 let generation = cache.generation();
156 let user = authenticate_password(state.db.as_ref(), &state.login_throttle, &user, pwd)
157 .await
158 .map_err(|e| match e {
159 PasswordAuthError::Throttled => StatusCode::TOO_MANY_REQUESTS,
160 PasswordAuthError::Invalid => StatusCode::FORBIDDEN,
161 })?;
162
163 cache
164 .insert(
165 cache_key,
166 CachedTokenData {
167 user: user.name.clone(),
168 is_admin: user.is_admin,
169 is_read_only: user.is_read_only,
170 },
171 generation,
172 )
173 .await;
174
175 Ok(Token {
176 value: None,
177 user: user.name,
178 is_admin: user.is_admin,
179 is_read_only: user.is_read_only,
180 })
181}
182
183async fn get_user_with_retry(
184 db: &Arc<dyn DbProvider>,
185 token: &PlainToken,
186 max_retries: u32,
187 delay_ms: u64,
188) -> Result<kellnr_db::User, DbError> {
189 let mut attempts = 0;
190
191 loop {
192 match db.get_user_from_token(token).await {
193 Ok(user) => return Ok(user),
194 Err(e) => {
195 if matches!(e, DbError::TokenNotFound | DbError::UserNotFound(_)) {
197 return Err(e);
198 }
199
200 attempts += 1;
201 if attempts > max_retries {
202 warn!(
203 "Failed to get user from token after {} retries: {}",
204 max_retries, e
205 );
206 return Err(e);
207 }
208
209 warn!(
210 "Transient DB error on attempt {}/{}, retrying in {}ms: {}",
211 attempts,
212 max_retries + 1,
213 delay_ms,
214 e
215 );
216 sleep(Duration::from_millis(delay_ms)).await;
217 }
218 }
219 }
220}
221
222#[derive(Deserialize, ToSchema)]
223pub struct NewTokenReqData {
224 pub name: String,
225}
226
227fn strip_scheme<'a>(value: &'a str, scheme: &str) -> Option<&'a str> {
230 let (prefix, rest) = value.split_at_checked(scheme.len())?;
231 let credentials = rest.strip_prefix(' ')?;
232 prefix.eq_ignore_ascii_case(scheme).then_some(credentials)
233}
234
235#[cfg(test)]
236mod tests {
237 use std::sync::atomic::{AtomicU32, Ordering};
238
239 use kellnr_db::error::DbError;
240 use kellnr_db::mock::MockDb;
241 use kellnr_db::{User, UserFlags};
242 use kellnr_settings::Settings;
243 use kellnr_test_db::TestDb;
244 use mockall::predicate::*;
245
246 use super::*;
247 use crate::login_throttle::LoginThrottle;
248 use crate::test_support::sqlite_test_state;
249 use crate::token_cache::TokenCacheManager;
250
251 fn test_user() -> User {
252 User {
253 id: kellnr_db::UserId::new(1),
254 name: UserName::new("test_user").unwrap(),
255 pwd: String::new(),
256 salt: String::new(),
257 is_admin: false,
258 is_read_only: false,
259 created: String::new(),
260 }
261 }
262
263 fn test_user_name() -> UserName {
264 UserName::new("test_user").unwrap()
265 }
266
267 fn test_settings() -> Arc<Settings> {
268 let mut settings = kellnr_settings::test_settings();
269 settings.registry.token_cache_enabled = true;
270 settings.registry.token_cache_ttl_seconds = 60;
271 settings.registry.token_cache_max_capacity = 100;
272 settings.registry.token_db_retry_count = 3;
273 settings.registry.token_db_retry_delay_ms = 1;
274 Arc::new(settings)
275 }
276
277 fn cache() -> Arc<TokenCacheManager> {
278 Arc::new(TokenCacheManager::new(true, Duration::from_secs(60), 100))
279 }
280
281 async fn db_state(
284 cache: &Arc<TokenCacheManager>,
285 settings: Arc<Settings>,
286 ) -> (AppStateData, TestDb) {
287 let (state, db) = sqlite_test_state().await;
288 db.add_user(&test_user_name(), "correct_password", UserFlags::default())
289 .await
290 .unwrap();
291 db.add_auth_token("test", &PlainToken::from("valid_token"), &test_user_name())
292 .await
293 .unwrap();
294 let state = AppStateData {
295 token_cache: cache.clone(),
296 settings,
297 ..state
298 };
299 (state, db)
300 }
301
302 fn sso_enforced_settings() -> Arc<Settings> {
304 let mut settings = Settings::clone(&test_settings());
305 settings.oauth2.enabled = true;
306 settings.oauth2.enforced = true;
307 Arc::new(settings)
308 }
309
310 fn basic_auth_header(user: &str, pwd: &str) -> HeaderMap {
311 let encoded = STANDARD.encode(format!("{user}:{pwd}"));
312 let mut headers = HeaderMap::new();
313 headers.insert("Authorization", format!("Basic {encoded}").parse().unwrap());
314 headers
315 }
316
317 fn bearer_header(token: &str) -> HeaderMap {
318 let mut headers = HeaderMap::new();
319 headers.insert("Authorization", format!("Bearer {token}").parse().unwrap());
320 headers
321 }
322
323 async fn change_pwd(db: &TestDb, pwd: &str) {
324 db.change_pwd(&test_user_name(), pwd).await.unwrap();
325 }
326
327 #[tokio::test]
332 async fn test_retry_succeeds_on_first_attempt() {
333 let (state, _db) = db_state(&cache(), test_settings()).await;
334
335 let result = get_user_with_retry(&state.db, &PlainToken::from("valid_token"), 3, 10).await;
336
337 assert_eq!(result.unwrap().name, "test_user");
338 }
339
340 #[tokio::test]
344 async fn test_retry_succeeds_after_transient_error() {
345 let call_count = Arc::new(AtomicU32::new(0));
347 let call_count_clone = call_count.clone();
348
349 let mut mock_db = MockDb::new();
350 mock_db
351 .expect_get_user_from_token()
352 .with(eq(PlainToken::from("token")))
353 .times(2)
354 .returning(move |_| {
355 let count = call_count_clone.fetch_add(1, Ordering::SeqCst);
356 if count == 0 {
357 Err(DbError::PostgresError(sea_orm::DbErr::ConnectionAcquire(
358 sea_orm::error::ConnAcquireErr::Timeout,
359 )))
360 } else {
361 Ok(test_user())
362 }
363 });
364
365 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
366 let result = get_user_with_retry(&db, &PlainToken::from("token"), 3, 1).await;
367
368 assert!(result.is_ok());
369 assert_eq!(call_count.load(Ordering::SeqCst), 2);
370 }
371
372 #[tokio::test]
374 async fn test_no_retry_on_token_not_found() {
375 let (state, _db) = db_state(&cache(), test_settings()).await;
376
377 let result = tokio::time::timeout(
378 Duration::from_secs(10),
379 get_user_with_retry(&state.db, &PlainToken::from("invalid_token"), 3, 60_000),
380 )
381 .await
382 .expect("lookup was retried");
383
384 assert!(matches!(result.unwrap_err(), DbError::TokenNotFound));
385 }
386
387 #[tokio::test]
388 async fn test_no_retry_on_user_not_found() {
389 let mut mock_db = MockDb::new();
391 mock_db
392 .expect_get_user_from_token()
393 .with(eq(PlainToken::from("orphan_token")))
394 .times(1)
395 .returning(|_| Err(DbError::UserNotFound("orphan".to_string())));
396
397 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
398 let result = get_user_with_retry(&db, &PlainToken::from("orphan_token"), 3, 10).await;
399
400 assert!(result.is_err());
401 assert!(matches!(result.unwrap_err(), DbError::UserNotFound(_)));
402 }
403
404 #[tokio::test]
405 async fn test_exhausts_retries_on_persistent_error() {
406 let call_count = Arc::new(AtomicU32::new(0));
408 let call_count_clone = call_count.clone();
409
410 let mut mock_db = MockDb::new();
411 mock_db
412 .expect_get_user_from_token()
413 .with(eq(PlainToken::from("token")))
414 .times(4) .returning(move |_| {
416 call_count_clone.fetch_add(1, Ordering::SeqCst);
417 Err(DbError::PostgresError(sea_orm::DbErr::ConnectionAcquire(
418 sea_orm::error::ConnAcquireErr::Timeout,
419 )))
420 });
421
422 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
423 let result = get_user_with_retry(&db, &PlainToken::from("token"), 3, 1).await;
424
425 assert!(result.is_err());
426 assert_eq!(call_count.load(Ordering::SeqCst), 4);
427 }
428
429 #[tokio::test]
430 async fn test_zero_retries_only_attempts_once() {
431 let call_count = Arc::new(AtomicU32::new(0));
433 let call_count_clone = call_count.clone();
434
435 let mut mock_db = MockDb::new();
436 mock_db
437 .expect_get_user_from_token()
438 .with(eq(PlainToken::from("token")))
439 .times(1)
440 .returning(move |_| {
441 call_count_clone.fetch_add(1, Ordering::SeqCst);
442 Err(DbError::PostgresError(sea_orm::DbErr::ConnectionAcquire(
443 sea_orm::error::ConnAcquireErr::Timeout,
444 )))
445 });
446
447 let db: Arc<dyn DbProvider> = Arc::new(mock_db);
448 let result = get_user_with_retry(&db, &PlainToken::from("token"), 0, 1).await;
449
450 assert!(result.is_err());
451 assert_eq!(call_count.load(Ordering::SeqCst), 1);
452 }
453
454 #[tokio::test]
461 async fn test_cache_hit_returns_cached_token() {
462 let cache = cache();
463 cache
464 .insert(
465 "cached_token".to_string(),
466 CachedTokenData {
467 user: UserName::new("cached_user").unwrap(),
468 is_admin: true,
469 is_read_only: false,
470 },
471 cache.generation(),
472 )
473 .await;
474 let (state, _db) = db_state(&cache, test_settings()).await;
475
476 let token = Token::from_header(&bearer_header("cached_token"), &state)
477 .await
478 .unwrap();
479
480 assert_eq!(token.user, "cached_user");
481 assert!(token.is_admin);
482 }
483
484 #[tokio::test]
485 async fn test_cache_miss_queries_db_and_caches() {
486 let cache = cache();
487 let (state, db) = db_state(&cache, test_settings()).await;
488 let db_user = UserName::new("db_user").unwrap();
489 let flags = UserFlags {
490 read_only: true,
491 ..UserFlags::default()
492 };
493 db.add_user(&db_user, "pwd", flags).await.unwrap();
494 db.add_auth_token("test", &PlainToken::from("new_token"), &db_user)
495 .await
496 .unwrap();
497
498 let token = Token::from_header(&bearer_header("new_token"), &state)
499 .await
500 .unwrap();
501
502 assert_eq!(token.user, "db_user");
503 assert!(token.is_read_only);
504 let cached = cache.get("new_token").await;
505 assert_eq!(cached.unwrap().user, "db_user");
506 }
507
508 #[tokio::test]
512 async fn test_invalidation_during_lookup_is_not_cached() {
513 let cache = cache();
514 let cache_in_db = cache.clone();
515
516 let mut mock_db = MockDb::new();
517 mock_db
518 .expect_get_user_from_token()
519 .with(eq(PlainToken::from("racy_token")))
520 .times(1)
521 .returning(move |_| {
522 cache_in_db.invalidate_all();
523 Ok(test_user())
524 });
525
526 let state = AppStateData {
527 db: Arc::new(mock_db),
528 token_cache: cache.clone(),
529 settings: test_settings(),
530 ..crate::test_support::test_state()
531 };
532
533 let token = Token::from_header(&bearer_header("racy_token"), &state)
534 .await
535 .unwrap();
536
537 assert_eq!(token.user, "test_user");
538 assert!(cache.get("racy_token").await.is_none());
539 }
540
541 #[tokio::test]
542 async fn test_cache_miss_with_invalid_token_returns_forbidden() {
543 let cache = cache();
544 let (state, _db) = db_state(&cache, test_settings()).await;
545
546 let result = Token::from_header(&bearer_header("bad_token"), &state).await;
547
548 assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
549 assert!(cache.get("bad_token").await.is_none());
550 }
551
552 #[tokio::test]
560 async fn test_basic_auth_rejected_when_sso_enforced() {
561 let (state, _db) = db_state(&cache(), sso_enforced_settings()).await;
562 let headers = basic_auth_header("test_user", "correct_password");
563
564 let result = Token::from_header(&headers, &state).await;
565
566 assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
567 }
568
569 #[tokio::test]
571 async fn test_lowercase_basic_auth_rejected_when_sso_enforced() {
572 let (state, _db) = db_state(&cache(), sso_enforced_settings()).await;
573 let encoded = STANDARD.encode("test_user:correct_password");
574 let mut headers = HeaderMap::new();
575 headers.insert("Authorization", format!("basic {encoded}").parse().unwrap());
576
577 let result = Token::from_header(&headers, &state).await;
578
579 assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
580 }
581
582 #[tokio::test]
585 async fn test_bearer_token_still_works_when_sso_enforced() {
586 let (state, _db) = db_state(&cache(), sso_enforced_settings()).await;
587
588 let token = Token::from_header(&bearer_header("valid_token"), &state)
589 .await
590 .expect("bearer tokens must remain valid under SSO enforcement");
591
592 assert_eq!(token.user, "test_user");
593 }
594
595 #[tokio::test]
597 async fn test_basic_auth_allowed_when_sso_not_enforced() {
598 let (state, _db) = db_state(&cache(), test_settings()).await;
599 let headers = basic_auth_header("test_user", "correct_password");
600
601 let token = Token::from_header(&headers, &state)
602 .await
603 .expect("basic auth should work when SSO is not enforced");
604
605 assert_eq!(token.user, "test_user");
606 }
607
608 #[tokio::test]
617 async fn test_basic_auth_success_is_cached() {
618 let (state, db) = db_state(&cache(), test_settings()).await;
619 let headers = basic_auth_header("test_user", "correct_password");
620
621 Token::from_header(&headers, &state).await.unwrap();
622 change_pwd(&db, "new_password").await;
623
624 for _ in 0..2 {
625 let token = Token::from_header(&headers, &state).await.unwrap();
626 assert_eq!(token.user, "test_user");
627 }
628 }
629
630 #[tokio::test]
633 async fn test_basic_auth_failure_is_not_cached() {
634 let (state, db) = db_state(&cache(), test_settings()).await;
635 let headers = basic_auth_header("test_user", "new_password");
636
637 let result = Token::from_header(&headers, &state).await;
638 assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
639 change_pwd(&db, "new_password").await;
640
641 let token = Token::from_header(&headers, &state).await.unwrap();
642 assert_eq!(token.user, "test_user");
643 }
644
645 #[tokio::test]
647 async fn test_basic_auth_cache_is_keyed_by_password() {
648 let (state, _db) = db_state(&cache(), test_settings()).await;
649
650 let ok = basic_auth_header("test_user", "correct_password");
651 Token::from_header(&ok, &state).await.unwrap();
652
653 let wrong = basic_auth_header("test_user", "wrong_password");
654 let result = Token::from_header(&wrong, &state).await;
655 assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
656 }
657
658 #[tokio::test]
661 async fn test_basic_auth_cache_respects_invalidation() {
662 let cache = cache();
663 let (state, db) = db_state(&cache, test_settings()).await;
664 let headers = basic_auth_header("test_user", "correct_password");
665
666 Token::from_header(&headers, &state).await.unwrap();
667 change_pwd(&db, "new_password").await;
668 cache.invalidate_all();
669
670 let result = Token::from_header(&headers, &state).await;
671 assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
672 }
673
674 #[tokio::test]
677 async fn test_basic_auth_failures_are_throttled() {
678 let (state, _db) = db_state(&cache(), test_settings()).await;
679 let state = AppStateData {
680 login_throttle: Arc::new(LoginThrottle::new(2, Duration::from_mins(5))),
681 ..state
682 };
683 let guess = basic_auth_header("test_user", "guess");
684
685 for _ in 0..2 {
686 let result = Token::from_header(&guess, &state).await;
687 assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
688 }
689 let valid = basic_auth_header("test_user", "correct_password");
690 let result = Token::from_header(&valid, &state).await;
691 assert_eq!(result.unwrap_err(), StatusCode::TOO_MANY_REQUESTS);
692 }
693
694 #[tokio::test]
697 async fn test_cached_basic_auth_bypasses_throttle() {
698 let (state, _db) = db_state(&cache(), test_settings()).await;
699 let state = AppStateData {
700 login_throttle: Arc::new(LoginThrottle::new(1, Duration::from_mins(5))),
701 ..state
702 };
703 let valid = basic_auth_header("test_user", "correct_password");
704 Token::from_header(&valid, &state).await.unwrap();
705
706 let guess = basic_auth_header("test_user", "guess");
707 assert_eq!(
708 Token::from_header(&guess, &state).await.unwrap_err(),
709 StatusCode::FORBIDDEN
710 );
711 assert_eq!(
712 Token::from_header(&guess, &state).await.unwrap_err(),
713 StatusCode::TOO_MANY_REQUESTS
714 );
715
716 let token = Token::from_header(&valid, &state).await.unwrap();
717 assert_eq!(token.user, "test_user");
718 }
719
720 #[test]
721 fn test_basic_auth_cache_key_does_not_contain_password() {
722 let key = basic_auth_cache_key("test_user", "secret");
723
724 assert!(key.starts_with("basic:"));
725 assert!(!key.contains("secret"));
726 assert_ne!(key, basic_auth_cache_key("test_user", "other"));
727 }
728
729 #[tokio::test]
730 async fn test_no_authorization_header_returns_unauthorized() {
731 let state = crate::test_support::test_state();
732
733 let result = Token::from_header(&HeaderMap::new(), &state).await;
734
735 assert_eq!(result.unwrap_err(), StatusCode::UNAUTHORIZED);
736 }
737
738 #[tokio::test]
740 async fn test_disabled_cache_always_queries_db() {
741 let cache = Arc::new(TokenCacheManager::new(false, Duration::from_secs(60), 100));
742 let (state, db) = db_state(&cache, test_settings()).await;
743 let headers = bearer_header("valid_token");
744
745 Token::from_header(&headers, &state).await.unwrap();
746 let tokens = db.get_auth_tokens(&test_user_name()).await.unwrap();
747 db.delete_auth_token(&test_user_name(), tokens[0].id)
748 .await
749 .unwrap();
750
751 let result = Token::from_header(&headers, &state).await;
752 assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
753 }
754
755 #[tokio::test]
756 async fn test_lowercase_bearer_prefix_works() {
757 let (state, _db) = db_state(&cache(), test_settings()).await;
758 let mut headers = HeaderMap::new();
759 headers.insert("Authorization", "bearer valid_token".parse().unwrap());
760
761 let token = Token::from_header(&headers, &state).await.unwrap();
762
763 assert_eq!(token.user, "test_user");
764 }
765
766 #[test]
767 fn auth_scheme_is_case_insensitive() {
768 for value in ["Bearer abc", "bearer abc", "BEARER abc", "bEaReR abc"] {
769 assert_eq!(strip_scheme(value, "Bearer"), Some("abc"), "{value}");
770 }
771 assert_eq!(strip_scheme("BASIC abc", "Basic"), Some("abc"));
772 assert_eq!(strip_scheme("Bearerabc", "Bearer"), None);
773 assert_eq!(strip_scheme("abc", "Bearer"), None);
774 assert_eq!(strip_scheme("Bäarer abc", "Bearer"), None);
775 }
776}