1use base64::Engine;
7use chrono;
8use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation};
9use serde_json::Value;
10use tracing::debug;
11
12use super::state::AuthState;
13use super::types::{AuthClaims, AuthResult};
14
15pub async fn authenticate_request(
25 state: &AuthState,
26 auth_header: &Option<String>,
27 api_key_header: &Option<String>,
28 api_key_query: &Option<String>,
29) -> AuthResult {
30 let mut last_failure: Option<AuthResult> = None;
31
32 if let Some(header) = auth_header {
34 if header.starts_with("Bearer ") {
35 if let Some(result) = authenticate_jwt(state, header).await {
36 if matches!(result, AuthResult::Success(_)) {
37 return result;
38 }
39 last_failure = Some(result);
40 }
41 } else if header.starts_with("Basic ") {
42 if let Some(result) = authenticate_basic(state, header) {
43 if matches!(result, AuthResult::Success(_)) {
44 return result;
45 }
46 last_failure = Some(result);
47 }
48 }
49 }
50
51 if let Some(header) = auth_header {
53 if header.starts_with("Bearer ") {
54 if let Some(result) = authenticate_oauth2(state, header).await {
55 if matches!(result, AuthResult::Success(_)) {
56 return result;
57 }
58 last_failure = Some(result);
59 }
60 }
61 }
62
63 if let Some(api_key) = api_key_header.as_ref().or(api_key_query.as_ref()) {
65 if let Some(result) = authenticate_api_key(state, api_key) {
66 if matches!(result, AuthResult::Success(_)) {
67 return result;
68 }
69 last_failure = Some(result);
70 }
71 }
72
73 last_failure.unwrap_or(AuthResult::None)
75}
76
77pub async fn authenticate_jwt(state: &AuthState, auth_header: &str) -> Option<AuthResult> {
79 let jwt_config = state.config.jwt.as_ref()?;
80
81 let token = auth_header.strip_prefix("Bearer ")?;
83
84 let header = match decode_header(token) {
86 Ok(h) => h,
87 Err(e) => {
88 debug!("Failed to decode JWT header: {}", e);
89 return Some(AuthResult::Failure("Invalid JWT format".to_string()));
90 }
91 };
92
93 let alg_str = match header.alg {
95 Algorithm::HS256 => "HS256",
96 Algorithm::HS384 => "HS384",
97 Algorithm::HS512 => "HS512",
98 Algorithm::RS256 => "RS256",
99 Algorithm::RS384 => "RS384",
100 Algorithm::RS512 => "RS512",
101 Algorithm::ES256 => "ES256",
102 Algorithm::ES384 => "ES384",
103 Algorithm::PS256 => "PS256",
104 Algorithm::PS384 => "PS384",
105 Algorithm::PS512 => "PS512",
106 _ => {
107 debug!("Unsupported JWT algorithm: {:?}", header.alg);
108 return Some(AuthResult::Failure("Unsupported JWT algorithm".to_string()));
109 }
110 };
111
112 if !jwt_config.algorithms.is_empty() && !jwt_config.algorithms.contains(&alg_str.to_string()) {
113 return Some(AuthResult::Failure(format!("Unsupported algorithm: {}", alg_str)));
114 }
115
116 let mut validation = Validation::new(header.alg);
118 if let Some(iss) = &jwt_config.issuer {
119 validation.set_issuer(&[iss]);
120 }
121 if let Some(aud) = &jwt_config.audience {
122 validation.set_audience(&[aud]);
123 }
124
125 let decoding_key = match header.alg {
127 Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => {
128 let secret = jwt_config
129 .secret
130 .as_ref()
131 .ok_or_else(|| AuthResult::Failure("JWT secret not configured".to_string()))
132 .ok()?;
133 DecodingKey::from_secret(secret.as_bytes())
134 }
135 Algorithm::RS256 | Algorithm::RS384 | Algorithm::RS512 => {
136 let key = jwt_config
137 .rsa_public_key
138 .as_ref()
139 .ok_or_else(|| AuthResult::Failure("RSA public key not configured".to_string()))
140 .ok()?;
141 DecodingKey::from_rsa_pem(key.as_bytes())
142 .map_err(|e| {
143 debug!("Failed to parse RSA key: {}", e);
144 AuthResult::Failure("Invalid RSA key configuration".to_string())
145 })
146 .ok()?
147 }
148 Algorithm::ES256 | Algorithm::ES384 => {
149 let key = jwt_config
150 .ecdsa_public_key
151 .as_ref()
152 .ok_or_else(|| AuthResult::Failure("ECDSA public key not configured".to_string()))
153 .ok()?;
154 DecodingKey::from_ec_pem(key.as_bytes())
155 .map_err(|e| {
156 debug!("Failed to parse ECDSA key: {}", e);
157 AuthResult::Failure("Invalid ECDSA key configuration".to_string())
158 })
159 .ok()?
160 }
161 _ => {
162 return Some(AuthResult::Failure("Unsupported algorithm".to_string()));
163 }
164 };
165
166 match decode::<Value>(token, &decoding_key, &validation) {
168 Ok(token_data) => {
169 let claims = token_data.claims;
170 let mut auth_claims = AuthClaims::new();
171
172 if let Some(sub) = claims.get("sub").and_then(|v| v.as_str()) {
174 auth_claims.sub = Some(sub.to_string());
175 }
176 if let Some(iss) = claims.get("iss").and_then(|v| v.as_str()) {
177 auth_claims.iss = Some(iss.to_string());
178 }
179 if let Some(aud) = claims.get("aud").and_then(|v| v.as_str()) {
180 auth_claims.aud = Some(aud.to_string());
181 }
182 if let Some(exp) = claims.get("exp").and_then(|v| v.as_i64()) {
183 auth_claims.exp = Some(exp);
184 }
185 if let Some(iat) = claims.get("iat").and_then(|v| v.as_i64()) {
186 auth_claims.iat = Some(iat);
187 }
188 if let Some(username) = claims
189 .get("username")
190 .or_else(|| claims.get("preferred_username"))
191 .and_then(|v| v.as_str())
192 {
193 auth_claims.username = Some(username.to_string());
194 }
195
196 if let Some(roles) = claims.get("roles").and_then(|v| v.as_array()) {
198 for role in roles {
199 if let Some(role_str) = role.as_str() {
200 auth_claims.roles.push(role_str.to_string());
201 }
202 }
203 }
204
205 for (key, value) in claims.as_object()? {
207 if ![
208 "sub",
209 "iss",
210 "aud",
211 "exp",
212 "iat",
213 "username",
214 "preferred_username",
215 "roles",
216 ]
217 .contains(&key.as_str())
218 {
219 auth_claims.custom.insert(key.clone(), value.clone());
220 }
221 }
222
223 Some(AuthResult::Success(auth_claims))
224 }
225 Err(e) => {
226 debug!("JWT validation failed: {}", e);
227 Some(AuthResult::Failure(format!("Invalid JWT token: {}", e)))
228 }
229 }
230}
231
232pub fn authenticate_basic(state: &AuthState, auth_header: &str) -> Option<AuthResult> {
234 let basic_config = state.config.basic_auth.as_ref()?;
235
236 let encoded = auth_header.strip_prefix("Basic ")?;
238 let decoded = match base64::engine::general_purpose::STANDARD.decode(encoded) {
239 Ok(d) => d,
240 Err(_) => return Some(AuthResult::Failure("Invalid base64 in Basic auth".to_string())),
241 };
242 let credentials = match String::from_utf8(decoded) {
243 Ok(c) => c,
244 Err(_) => {
245 return Some(AuthResult::Failure("Invalid UTF-8 in Basic auth credentials".to_string()))
246 }
247 };
248 let parts: Vec<&str> = credentials.splitn(2, ':').collect();
249 if parts.len() != 2 {
250 return Some(AuthResult::Failure("Invalid Basic auth format".to_string()));
251 }
252
253 let username = parts[0];
254 let password = parts[1];
255
256 if let Some(expected_password) = basic_config.credentials.get(username) {
258 if expected_password == password {
259 let mut claims = AuthClaims::new();
260 claims.username = Some(username.to_string());
261 return Some(AuthResult::Success(claims));
262 }
263 }
264
265 Some(AuthResult::Failure("Invalid credentials".to_string()))
266}
267
268async fn authenticate_oauth2(state: &AuthState, auth_header: &str) -> Option<AuthResult> {
270 let oauth2_config = state.config.oauth2.as_ref()?;
271
272 let token = auth_header.strip_prefix("Bearer ")?;
274
275 {
277 let cache = state.introspection_cache.read().await;
278 if let Some(cached) = cache.get(token) {
279 let now = chrono::Utc::now().timestamp();
280 if cached.expires_at > now {
281 return Some(cached.result.clone());
282 }
283 }
284 }
285
286 let client = reqwest::Client::new();
288 let response = match client
289 .post(&oauth2_config.introspection_url)
290 .basic_auth(&oauth2_config.client_id, Some(&oauth2_config.client_secret))
291 .form(&[
292 ("token", token),
293 (
294 "token_type_hint",
295 oauth2_config.token_type_hint.as_deref().unwrap_or("access_token"),
296 ),
297 ])
298 .send()
299 .await
300 {
301 Ok(resp) => resp,
302 Err(e) => {
303 debug!("Network error during OAuth2 introspection: {}", e);
304 return Some(AuthResult::NetworkError(format!(
305 "Failed to connect to introspection endpoint: {}",
306 e
307 )));
308 }
309 };
310
311 if !response.status().is_success() {
312 let status = response.status();
313 debug!("OAuth2 introspection server error: {}", status);
314 return Some(AuthResult::ServerError(format!(
315 "Introspection endpoint returned {}: {}",
316 status,
317 status.canonical_reason().unwrap_or("Unknown error")
318 )));
319 }
320
321 let introspection_result: Value = match response.json().await {
322 Ok(json) => json,
323 Err(e) => {
324 debug!("Failed to parse introspection response: {}", e);
325 return Some(AuthResult::ServerError(format!(
326 "Invalid JSON response from introspection endpoint: {}",
327 e
328 )));
329 }
330 };
331
332 let active = introspection_result.get("active").and_then(|v| v.as_bool()).unwrap_or(false);
334 if !active {
335 let cached_result = AuthResult::TokenInvalid("Token is not active".to_string());
336 let expires_at = chrono::Utc::now().timestamp() + 300; let cached = super::state::CachedIntrospection {
339 result: cached_result.clone(),
340 expires_at,
341 };
342 let mut cache = state.introspection_cache.write().await;
343 cache.insert(token.to_string(), cached);
344 return Some(cached_result);
345 }
346
347 if let Some(exp) = introspection_result.get("exp").and_then(|v| v.as_i64()) {
349 let now = chrono::Utc::now().timestamp();
350 if exp <= now {
351 let cached_result = AuthResult::TokenExpired;
352 let expires_at = chrono::Utc::now().timestamp() + 60; let cached = super::state::CachedIntrospection {
355 result: cached_result.clone(),
356 expires_at,
357 };
358 let mut cache = state.introspection_cache.write().await;
359 cache.insert(token.to_string(), cached);
360 return Some(cached_result);
361 }
362 }
363
364 let mut claims = AuthClaims::new();
366 if let Some(sub) = introspection_result.get("sub").and_then(|v| v.as_str()) {
367 claims.sub = Some(sub.to_string());
368 }
369 if let Some(username) = introspection_result.get("username").and_then(|v| v.as_str()) {
370 claims.username = Some(username.to_string());
371 }
372 if let Some(exp) = introspection_result.get("exp").and_then(|v| v.as_i64()) {
373 claims.exp = Some(exp);
374 }
375
376 let expires_at = claims.exp.unwrap_or(chrono::Utc::now().timestamp() + 3600);
378 let cached_result = AuthResult::Success(claims);
379 let cached = super::state::CachedIntrospection {
380 result: cached_result.clone(),
381 expires_at,
382 };
383 let mut cache = state.introspection_cache.write().await;
384 cache.insert(token.to_string(), cached);
385
386 Some(cached_result)
387}
388
389pub fn authenticate_api_key(state: &AuthState, api_key: &str) -> Option<AuthResult> {
391 let api_key_config = state.config.api_key.as_ref()?;
392
393 if api_key_config.keys.contains(&api_key.to_string()) {
394 let mut claims = AuthClaims::new();
395 claims.custom.insert("api_key".to_string(), Value::String(api_key.to_string()));
396 Some(AuthResult::Success(claims))
397 } else {
398 Some(AuthResult::Failure("Invalid API key".to_string()))
399 }
400}
401
402#[cfg(test)]
403mod tests {
404 use super::super::state::AuthState;
405 use super::*;
406 use base64::Engine;
407 use mockforge_core::config::{ApiKeyConfig, AuthConfig, BasicAuthConfig, JwtConfig};
408 use std::collections::HashMap;
409 use std::sync::Arc;
410 use tokio::sync::RwLock;
411
412 fn create_auth_state(config: AuthConfig) -> AuthState {
413 AuthState {
414 config,
415 spec: None,
416 oauth2_client: None,
417 introspection_cache: Arc::new(RwLock::new(HashMap::new())),
418 }
419 }
420
421 fn create_test_auth_state_with_jwt() -> AuthState {
422 let jwt_config = JwtConfig {
423 secret: Some("test-secret-key-for-jwt-authentication".to_string()),
424 rsa_public_key: None,
425 ecdsa_public_key: None,
426 algorithms: vec!["HS256".to_string()],
427 issuer: Some("test-issuer".to_string()),
428 audience: Some("test-audience".to_string()),
429 };
430
431 let auth_config = AuthConfig {
432 jwt: Some(jwt_config),
433 basic_auth: None,
434 oauth2: None,
435 api_key: None,
436 require_auth: false,
437 };
438
439 create_auth_state(auth_config)
440 }
441
442 fn create_test_auth_state_with_basic() -> AuthState {
443 let mut credentials = HashMap::new();
444 credentials.insert("testuser".to_string(), "testpass".to_string());
445 credentials.insert("admin".to_string(), "admin123".to_string());
446
447 let basic_config = BasicAuthConfig { credentials };
448
449 let auth_config = AuthConfig {
450 jwt: None,
451 basic_auth: Some(basic_config),
452 oauth2: None,
453 api_key: None,
454 require_auth: false,
455 };
456
457 create_auth_state(auth_config)
458 }
459
460 fn create_test_auth_state_with_api_key() -> AuthState {
461 let api_key_config = ApiKeyConfig {
462 header_name: "X-API-Key".to_string(),
463 query_name: None,
464 keys: vec![
465 "valid-api-key-123".to_string(),
466 "another-valid-key-456".to_string(),
467 ],
468 };
469
470 let auth_config = AuthConfig {
471 jwt: None,
472 basic_auth: None,
473 oauth2: None,
474 api_key: Some(api_key_config),
475 require_auth: false,
476 };
477
478 create_auth_state(auth_config)
479 }
480
481 #[tokio::test]
482 async fn test_authenticate_request_no_auth() {
483 let state = create_auth_state(AuthConfig {
484 jwt: None,
485 basic_auth: None,
486 oauth2: None,
487 api_key: None,
488 require_auth: false,
489 });
490
491 let result = authenticate_request(&state, &None, &None, &None).await;
492 assert!(matches!(result, AuthResult::None));
493 }
494
495 #[tokio::test]
496 async fn test_authenticate_jwt_valid() {
497 use jsonwebtoken::{encode, EncodingKey, Header};
498 use serde_json::json;
499
500 let state = create_test_auth_state_with_jwt();
501
502 let mut claims = serde_json::Map::new();
504 claims.insert("sub".to_string(), json!("user123"));
505 claims.insert("iss".to_string(), json!("test-issuer"));
506 claims.insert("aud".to_string(), json!("test-audience"));
507 claims.insert(
508 "exp".to_string(),
509 json!((chrono::Utc::now() + chrono::Duration::hours(1)).timestamp()),
510 );
511
512 let secret = "test-secret-key-for-jwt-authentication";
513 let token =
514 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
515 .unwrap();
516
517 let auth_header = format!("Bearer {}", token);
518 let result = authenticate_jwt(&state, &auth_header).await;
519
520 assert!(result.is_some());
521 match result.unwrap() {
522 AuthResult::Success(claims) => {
523 assert_eq!(claims.sub, Some("user123".to_string()));
524 assert_eq!(claims.iss, Some("test-issuer".to_string()));
525 assert_eq!(claims.aud, Some("test-audience".to_string()));
526 }
527 _ => panic!("Expected successful authentication"),
528 }
529 }
530
531 #[tokio::test]
532 async fn test_authenticate_jwt_invalid_format() {
533 let state = create_test_auth_state_with_jwt();
534 let auth_header = "Bearer invalid-token-format";
535 let result = authenticate_jwt(&state, &auth_header).await;
536
537 assert!(result.is_some());
538 assert!(matches!(result.unwrap(), AuthResult::Failure(_)));
539 }
540
541 #[tokio::test]
542 async fn test_authenticate_jwt_expired() {
543 use jsonwebtoken::{encode, EncodingKey, Header};
544 use serde_json::json;
545
546 let state = create_test_auth_state_with_jwt();
547
548 let mut claims = serde_json::Map::new();
550 claims.insert("sub".to_string(), json!("user123"));
551 claims.insert("iss".to_string(), json!("test-issuer"));
552 claims.insert("aud".to_string(), json!("test-audience"));
553 claims.insert(
554 "exp".to_string(),
555 json!((chrono::Utc::now() - chrono::Duration::hours(1)).timestamp()),
556 );
557
558 let secret = "test-secret-key-for-jwt-authentication";
559 let token =
560 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
561 .unwrap();
562
563 let auth_header = format!("Bearer {}", token);
564 let result = authenticate_jwt(&state, &auth_header).await;
565
566 assert!(result.is_some());
567 assert!(matches!(result.unwrap(), AuthResult::Failure(_)));
568 }
569
570 #[tokio::test]
571 async fn test_authenticate_jwt_wrong_issuer() {
572 use jsonwebtoken::{encode, EncodingKey, Header};
573 use serde_json::json;
574
575 let state = create_test_auth_state_with_jwt();
576
577 let mut claims = serde_json::Map::new();
579 claims.insert("sub".to_string(), json!("user123"));
580 claims.insert("iss".to_string(), json!("wrong-issuer"));
581 claims.insert("aud".to_string(), json!("test-audience"));
582 claims.insert(
583 "exp".to_string(),
584 json!((chrono::Utc::now() + chrono::Duration::hours(1)).timestamp()),
585 );
586
587 let secret = "test-secret-key-for-jwt-authentication";
588 let token =
589 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
590 .unwrap();
591
592 let auth_header = format!("Bearer {}", token);
593 let result = authenticate_jwt(&state, &auth_header).await;
594
595 assert!(result.is_some());
596 assert!(matches!(result.unwrap(), AuthResult::Failure(_)));
597 }
598
599 #[tokio::test]
600 async fn test_authenticate_jwt_with_roles() {
601 use jsonwebtoken::{encode, EncodingKey, Header};
602 use serde_json::json;
603
604 let state = create_test_auth_state_with_jwt();
605
606 let mut claims = serde_json::Map::new();
607 claims.insert("sub".to_string(), json!("user123"));
608 claims.insert("iss".to_string(), json!("test-issuer"));
609 claims.insert("aud".to_string(), json!("test-audience"));
610 claims.insert(
611 "exp".to_string(),
612 json!((chrono::Utc::now() + chrono::Duration::hours(1)).timestamp()),
613 );
614 claims.insert("roles".to_string(), json!(["admin", "user"]));
615 claims.insert("username".to_string(), json!("testuser"));
616
617 let secret = "test-secret-key-for-jwt-authentication";
618 let token =
619 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
620 .unwrap();
621
622 let auth_header = format!("Bearer {}", token);
623 let result = authenticate_jwt(&state, &auth_header).await;
624
625 assert!(result.is_some());
626 match result.unwrap() {
627 AuthResult::Success(claims) => {
628 assert_eq!(claims.username, Some("testuser".to_string()));
629 assert_eq!(claims.roles, vec!["admin", "user"]);
630 }
631 _ => panic!("Expected successful authentication"),
632 }
633 }
634
635 #[tokio::test]
636 async fn test_authenticate_jwt_no_config() {
637 let state = create_auth_state(AuthConfig {
638 jwt: None,
639 basic_auth: None,
640 oauth2: None,
641 api_key: None,
642 require_auth: false,
643 });
644
645 let auth_header = "Bearer some-token";
646 let result = authenticate_jwt(&state, &auth_header).await;
647
648 assert!(result.is_none());
649 }
650
651 #[test]
652 fn test_authenticate_basic_valid() {
653 let state = create_test_auth_state_with_basic();
654
655 let credentials = base64::engine::general_purpose::STANDARD.encode(b"testuser:testpass");
657 let auth_header = format!("Basic {}", credentials);
658
659 let result = authenticate_basic(&state, &auth_header);
660
661 assert!(result.is_some());
662 match result.unwrap() {
663 AuthResult::Success(claims) => {
664 assert_eq!(claims.username, Some("testuser".to_string()));
665 }
666 _ => panic!("Expected successful authentication"),
667 }
668 }
669
670 #[test]
671 fn test_authenticate_basic_invalid_credentials() {
672 let state = create_test_auth_state_with_basic();
673
674 let credentials = base64::engine::general_purpose::STANDARD.encode(b"testuser:wrongpass");
675 let auth_header = format!("Basic {}", credentials);
676
677 let result = authenticate_basic(&state, &auth_header);
678
679 assert!(result.is_some());
680 assert!(matches!(result.unwrap(), AuthResult::Failure(_)));
681 }
682
683 #[test]
684 fn test_authenticate_basic_invalid_format() {
685 let state = create_test_auth_state_with_basic();
686
687 let auth_header = "Basic invalid-base64!!!";
689
690 let result = authenticate_basic(&state, &auth_header);
691
692 assert!(result.is_some());
693 assert!(matches!(result.unwrap(), AuthResult::Failure(_)));
694 }
695
696 #[test]
697 fn test_authenticate_basic_missing_colon() {
698 let state = create_test_auth_state_with_basic();
699
700 let credentials =
702 base64::Engine::encode(&base64::engine::general_purpose::STANDARD, b"testuser");
703 let auth_header = format!("Basic {}", credentials);
704
705 let result = authenticate_basic(&state, &auth_header);
706
707 assert!(result.is_some());
708 assert!(matches!(result.unwrap(), AuthResult::Failure(_)));
709 }
710
711 #[test]
712 fn test_authenticate_basic_no_config() {
713 let state = create_auth_state(AuthConfig {
714 jwt: None,
715 basic_auth: None,
716 oauth2: None,
717 api_key: None,
718 require_auth: false,
719 });
720
721 let credentials = base64::engine::general_purpose::STANDARD.encode(b"user:pass");
722 let auth_header = format!("Basic {}", credentials);
723
724 let result = authenticate_basic(&state, &auth_header);
725
726 assert!(result.is_none());
727 }
728
729 #[test]
730 fn test_authenticate_api_key_valid() {
731 let state = create_test_auth_state_with_api_key();
732
733 let result = authenticate_api_key(&state, "valid-api-key-123");
734
735 assert!(result.is_some());
736 match result.unwrap() {
737 AuthResult::Success(claims) => {
738 assert_eq!(
739 claims.custom.get("api_key").and_then(|v| v.as_str()),
740 Some("valid-api-key-123")
741 );
742 }
743 _ => panic!("Expected successful authentication"),
744 }
745 }
746
747 #[test]
748 fn test_authenticate_api_key_invalid() {
749 let state = create_test_auth_state_with_api_key();
750
751 let result = authenticate_api_key(&state, "invalid-key");
752
753 assert!(result.is_some());
754 assert!(matches!(result.unwrap(), AuthResult::Failure(_)));
755 }
756
757 #[test]
758 fn test_authenticate_api_key_no_config() {
759 let state = create_auth_state(AuthConfig {
760 jwt: None,
761 basic_auth: None,
762 oauth2: None,
763 api_key: None,
764 require_auth: false,
765 });
766
767 let result = authenticate_api_key(&state, "some-key");
768
769 assert!(result.is_none());
770 }
771
772 #[tokio::test]
773 async fn test_authenticate_request_with_bearer_token() {
774 use jsonwebtoken::{encode, EncodingKey, Header};
775 use serde_json::json;
776
777 let state = create_test_auth_state_with_jwt();
778
779 let mut claims = serde_json::Map::new();
780 claims.insert("sub".to_string(), json!("user123"));
781 claims.insert("iss".to_string(), json!("test-issuer"));
782 claims.insert("aud".to_string(), json!("test-audience"));
783 claims.insert(
784 "exp".to_string(),
785 json!((chrono::Utc::now() + chrono::Duration::hours(1)).timestamp()),
786 );
787
788 let secret = "test-secret-key-for-jwt-authentication";
789 let token =
790 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
791 .unwrap();
792
793 let auth_header = Some(format!("Bearer {}", token));
794 let result = authenticate_request(&state, &auth_header, &None, &None).await;
795
796 assert!(matches!(result, AuthResult::Success(_)));
797 }
798
799 #[tokio::test]
800 async fn test_authenticate_request_with_basic_auth() {
801 let state = create_test_auth_state_with_basic();
802
803 let credentials = base64::engine::general_purpose::STANDARD.encode(b"testuser:testpass");
804 let auth_header = Some(format!("Basic {}", credentials));
805
806 let result = authenticate_request(&state, &auth_header, &None, &None).await;
807
808 assert!(matches!(result, AuthResult::Success(_)));
809 }
810
811 #[tokio::test]
812 async fn test_authenticate_request_with_api_key_header() {
813 let state = create_test_auth_state_with_api_key();
814
815 let result =
816 authenticate_request(&state, &None, &Some("valid-api-key-123".to_string()), &None)
817 .await;
818
819 assert!(matches!(result, AuthResult::Success(_)));
820 }
821
822 #[tokio::test]
823 async fn test_authenticate_request_with_api_key_query() {
824 let state = create_test_auth_state_with_api_key();
825
826 let result =
827 authenticate_request(&state, &None, &None, &Some("another-valid-key-456".to_string()))
828 .await;
829
830 assert!(matches!(result, AuthResult::Success(_)));
831 }
832
833 #[tokio::test]
834 async fn test_authenticate_request_priority() {
835 let jwt_config = JwtConfig {
837 secret: Some("test-secret".to_string()),
838 rsa_public_key: None,
839 ecdsa_public_key: None,
840 algorithms: vec!["HS256".to_string()],
841 issuer: None,
842 audience: None,
843 };
844
845 let api_key_config = ApiKeyConfig {
846 header_name: "X-API-Key".to_string(),
847 query_name: None,
848 keys: vec!["valid-key".to_string()],
849 };
850
851 let state = create_auth_state(AuthConfig {
852 jwt: Some(jwt_config),
853 basic_auth: None,
854 oauth2: None,
855 api_key: Some(api_key_config),
856 require_auth: false,
857 });
858
859 let auth_header = Some("Bearer invalid-token".to_string());
861 let api_key = Some("valid-key".to_string());
862
863 let result = authenticate_request(&state, &auth_header, &api_key, &None).await;
864
865 assert!(matches!(result, AuthResult::Success(_)));
867 }
868
869 #[tokio::test]
870 async fn test_authenticate_jwt_with_custom_claims() {
871 use jsonwebtoken::{encode, EncodingKey, Header};
872 use serde_json::json;
873
874 let state = create_test_auth_state_with_jwt();
875
876 let mut claims = serde_json::Map::new();
877 claims.insert("sub".to_string(), json!("user123"));
878 claims.insert("iss".to_string(), json!("test-issuer"));
879 claims.insert("aud".to_string(), json!("test-audience"));
880 claims.insert(
881 "exp".to_string(),
882 json!((chrono::Utc::now() + chrono::Duration::hours(1)).timestamp()),
883 );
884 claims.insert("custom_field".to_string(), json!("custom_value"));
885 claims.insert("department".to_string(), json!("engineering"));
886
887 let secret = "test-secret-key-for-jwt-authentication";
888 let token =
889 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
890 .unwrap();
891
892 let auth_header = format!("Bearer {}", token);
893 let result = authenticate_jwt(&state, &auth_header).await;
894
895 assert!(result.is_some());
896 match result.unwrap() {
897 AuthResult::Success(claims) => {
898 assert_eq!(
899 claims.custom.get("custom_field").and_then(|v| v.as_str()),
900 Some("custom_value")
901 );
902 assert_eq!(
903 claims.custom.get("department").and_then(|v| v.as_str()),
904 Some("engineering")
905 );
906 }
907 _ => panic!("Expected successful authentication"),
908 }
909 }
910
911 #[tokio::test]
912 async fn test_authenticate_jwt_with_preferred_username() {
913 use jsonwebtoken::{encode, EncodingKey, Header};
914 use serde_json::json;
915
916 let state = create_test_auth_state_with_jwt();
917
918 let mut claims = serde_json::Map::new();
919 claims.insert("sub".to_string(), json!("user123"));
920 claims.insert("iss".to_string(), json!("test-issuer"));
921 claims.insert("aud".to_string(), json!("test-audience"));
922 claims.insert(
923 "exp".to_string(),
924 json!((chrono::Utc::now() + chrono::Duration::hours(1)).timestamp()),
925 );
926 claims.insert("preferred_username".to_string(), json!("preferred_user"));
927
928 let secret = "test-secret-key-for-jwt-authentication";
929 let token =
930 encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()))
931 .unwrap();
932
933 let auth_header = format!("Bearer {}", token);
934 let result = authenticate_jwt(&state, &auth_header).await;
935
936 assert!(result.is_some());
937 match result.unwrap() {
938 AuthResult::Success(claims) => {
939 assert_eq!(claims.username, Some("preferred_user".to_string()));
940 }
941 _ => panic!("Expected successful authentication"),
942 }
943 }
944
945 #[test]
946 fn test_authenticate_basic_multiple_users() {
947 let state = create_test_auth_state_with_basic();
948
949 let creds1 = base64::engine::general_purpose::STANDARD.encode(b"testuser:testpass");
951 let result1 = authenticate_basic(&state, &format!("Basic {}", creds1));
952 assert!(matches!(result1.unwrap(), AuthResult::Success(_)));
953
954 let creds2 = base64::engine::general_purpose::STANDARD.encode(b"admin:admin123");
956 let result2 = authenticate_basic(&state, &format!("Basic {}", creds2));
957 assert!(matches!(result2.unwrap(), AuthResult::Success(_)));
958 }
959
960 #[test]
961 fn test_authenticate_basic_user_not_found() {
962 let state = create_test_auth_state_with_basic();
963
964 let credentials = base64::engine::general_purpose::STANDARD.encode(b"nonexistent:password");
965 let auth_header = format!("Basic {}", credentials);
966
967 let result = authenticate_basic(&state, &auth_header);
968
969 assert!(result.is_some());
970 assert!(matches!(result.unwrap(), AuthResult::Failure(_)));
971 }
972}