mockforge_http/auth/
authenticator.rs

1//! Authentication methods and logic
2//!
3//! This module contains the core authentication logic for different
4//! authentication schemes: JWT, Basic Auth, OAuth2, and API keys.
5
6use 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
15/// Authenticate a request using various methods
16///
17/// Tries authentication methods in priority order:
18/// 1. JWT (Bearer token)
19/// 2. Basic Auth
20/// 3. OAuth2 token introspection
21/// 4. API Key
22///
23/// Returns success on first successful auth, or continues to try other methods.
24pub 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    // Try JWT/Bearer token first
33    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    // Try OAuth2 token introspection
52    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    // Try API key authentication
64    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    // Return last failure if any auth was attempted, otherwise None
74    last_failure.unwrap_or(AuthResult::None)
75}
76
77/// Authenticate using JWT
78pub async fn authenticate_jwt(state: &AuthState, auth_header: &str) -> Option<AuthResult> {
79    let jwt_config = state.config.jwt.as_ref()?;
80
81    // Extract token from header
82    let token = auth_header.strip_prefix("Bearer ")?;
83
84    // Try to decode header to determine algorithm
85    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    // Check if algorithm is supported
94    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    // Create validation
117    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    // Create decoding key based on algorithm
126    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    // Decode and validate token
167    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            // Extract standard claims
173            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            // Extract roles
197            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            // Store custom claims
206            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
232/// Authenticate using Basic Auth
233pub fn authenticate_basic(state: &AuthState, auth_header: &str) -> Option<AuthResult> {
234    let basic_config = state.config.basic_auth.as_ref()?;
235
236    // Extract credentials from header
237    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    // Check credentials
257    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
268/// Authenticate using OAuth2 token introspection
269async fn authenticate_oauth2(state: &AuthState, auth_header: &str) -> Option<AuthResult> {
270    let oauth2_config = state.config.oauth2.as_ref()?;
271
272    // Extract token
273    let token = auth_header.strip_prefix("Bearer ")?;
274
275    // Check cache first
276    {
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    // Perform token introspection
287    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    // Check if token is active
333    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        // Cache inactive tokens for a shorter time to avoid repeated checks
337        let expires_at = chrono::Utc::now().timestamp() + 300; // 5 minutes
338        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    // Check if token is expired
348    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            // Cache expired tokens for a short time
353            let expires_at = chrono::Utc::now().timestamp() + 60; // 1 minute
354            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    // Extract claims from introspection response
365    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    // Cache successful result - use token expiration or default to 1 hour
377    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
389/// Authenticate using API key
390pub 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        // Create a valid JWT token
503        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        // Create an expired JWT token
549        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        // Create a token with wrong issuer
578        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        // Encode "testuser:testpass" in base64
656        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        // Invalid base64
688        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        // Encode credentials without colon
701        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        // JWT should be tried first, then basic auth, then API key
836        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        // Provide both invalid JWT and valid API key
860        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        // Should try JWT first and fail, then try API key and succeed
866        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        // Test first user
950        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        // Test second user
955        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}