Skip to main content

amaters_server/
auth.rs

1//! Authentication module
2//!
3//! This module provides authentication services for the server:
4//! - mTLS (Mutual TLS) client certificate validation
5//! - JWT (JSON Web Token) authentication
6//! - API key authentication
7//!
8//! Security model:
9//! - Secure by default (deny unless explicitly allowed)
10//! - Multiple authentication methods can be enabled simultaneously
11//! - Authentication results in a validated identity (Principal)
12
13use crate::config::{ApiKeySettings, AuthSettings, JwtSettings, MtlsSettings};
14use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
15use constant_time_eq::constant_time_eq;
16use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode, decode_header};
17use serde::{Deserialize, Serialize};
18use sha2::{Digest, Sha256};
19use std::collections::HashMap;
20use std::fs;
21use std::path::Path;
22use std::sync::Arc;
23use thiserror::Error;
24use tracing::{debug, info, warn};
25use x509_parser::prelude::*;
26
27/// Authentication errors
28#[derive(Error, Debug)]
29pub enum AuthError {
30    #[error("Authentication failed: {0}")]
31    AuthenticationFailed(String),
32
33    #[error("Invalid credentials")]
34    InvalidCredentials,
35
36    #[error("Certificate validation failed: {0}")]
37    CertificateError(String),
38
39    #[error("JWT validation failed: {0}")]
40    JwtError(String),
41
42    #[error("API key validation failed: {0}")]
43    ApiKeyError(String),
44
45    #[error("Configuration error: {0}")]
46    ConfigError(String),
47
48    #[error("IO error: {0}")]
49    Io(#[from] std::io::Error),
50
51    #[error("JSON error: {0}")]
52    Json(#[from] serde_json::Error),
53
54    #[error("No authentication provided")]
55    NoAuthProvided,
56
57    #[error("Authentication method not enabled: {0}")]
58    MethodNotEnabled(String),
59}
60
61pub type AuthResult<T> = Result<T, AuthError>;
62
63/// Authenticated principal (user identity)
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65pub struct Principal {
66    /// Unique identifier for the user
67    pub id: String,
68
69    /// Username or common name
70    pub name: String,
71
72    /// Authentication method used
73    pub auth_method: AuthMethod,
74
75    /// Additional attributes (roles, groups, etc.)
76    pub attributes: HashMap<String, String>,
77}
78
79impl Principal {
80    /// Create a new principal
81    pub fn new(id: String, name: String, auth_method: AuthMethod) -> Self {
82        Self {
83            id,
84            name,
85            auth_method,
86            attributes: HashMap::new(),
87        }
88    }
89
90    /// Add an attribute to the principal
91    pub fn with_attribute(mut self, key: String, value: String) -> Self {
92        self.attributes.insert(key, value);
93        self
94    }
95
96    /// Get an attribute value
97    pub fn get_attribute(&self, key: &str) -> Option<&String> {
98        self.attributes.get(key)
99    }
100
101    /// Check if principal has a specific role
102    pub fn has_role(&self, role: &str) -> bool {
103        self.get_attribute("role")
104            .map(|r| r == role)
105            .unwrap_or(false)
106    }
107}
108
109/// Authentication method used
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
111pub enum AuthMethod {
112    /// mTLS client certificate
113    MutualTls,
114    /// JWT token
115    Jwt,
116    /// API key
117    ApiKey,
118}
119
120impl std::fmt::Display for AuthMethod {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        match self {
123            AuthMethod::MutualTls => write!(f, "mTLS"),
124            AuthMethod::Jwt => write!(f, "JWT"),
125            AuthMethod::ApiKey => write!(f, "API Key"),
126        }
127    }
128}
129
130/// JWT claims structure
131#[derive(Debug, Serialize, Deserialize)]
132struct JwtClaims {
133    /// Subject (user ID)
134    sub: String,
135    /// Expiration time
136    exp: usize,
137    /// Issued at
138    iat: Option<usize>,
139    /// Issuer
140    iss: Option<String>,
141    /// Audience
142    aud: Option<String>,
143    /// User name
144    name: Option<String>,
145    /// Roles
146    roles: Option<Vec<String>>,
147    /// Custom attributes
148    #[serde(flatten)]
149    attributes: HashMap<String, serde_json::Value>,
150}
151
152/// API key entry
153#[derive(Debug, Clone, Serialize, Deserialize)]
154struct ApiKeyEntry {
155    /// Key ID
156    id: String,
157    /// Key name/description
158    name: String,
159    /// Hashed key value (if hashing enabled)
160    #[serde(skip_serializing_if = "Option::is_none")]
161    key_hash: Option<String>,
162    /// Plain key value (if hashing disabled)
163    #[serde(skip_serializing_if = "Option::is_none")]
164    key: Option<String>,
165    /// User ID
166    user_id: String,
167    /// Roles
168    #[serde(default)]
169    roles: Vec<String>,
170    /// Additional attributes
171    #[serde(default)]
172    attributes: HashMap<String, String>,
173}
174
175/// Authentication service
176pub struct Authenticator {
177    config: Arc<AuthSettings>,
178    mtls_validator: Option<MtlsValidator>,
179    jwt_validator: Option<JwtValidator>,
180    api_key_validator: Option<ApiKeyValidator>,
181}
182
183impl Authenticator {
184    /// Create a new authenticator
185    pub fn new(config: AuthSettings) -> AuthResult<Self> {
186        let config = Arc::new(config);
187
188        // Initialize mTLS validator
189        let mtls_validator = if config.mtls.enabled {
190            Some(MtlsValidator::new(config.mtls.clone())?)
191        } else {
192            None
193        };
194
195        // Initialize JWT validator
196        let jwt_validator = if config.jwt.enabled {
197            Some(JwtValidator::new(config.jwt.clone())?)
198        } else {
199            None
200        };
201
202        // Initialize API key validator
203        let api_key_validator = if config.api_key.enabled {
204            Some(ApiKeyValidator::new(config.api_key.clone())?)
205        } else {
206            None
207        };
208
209        Ok(Self {
210            config,
211            mtls_validator,
212            jwt_validator,
213            api_key_validator,
214        })
215    }
216
217    /// Authenticate using client certificate
218    pub fn authenticate_certificate(&self, cert_der: &[u8]) -> AuthResult<Principal> {
219        if !self.config.methods.contains(&"mtls".to_string()) {
220            return Err(AuthError::MethodNotEnabled("mTLS".to_string()));
221        }
222
223        let validator = self
224            .mtls_validator
225            .as_ref()
226            .ok_or_else(|| AuthError::MethodNotEnabled("mTLS".to_string()))?;
227
228        validator.validate_certificate(cert_der)
229    }
230
231    /// Authenticate using JWT token
232    pub fn authenticate_jwt(&self, token: &str) -> AuthResult<Principal> {
233        if !self.config.methods.contains(&"jwt".to_string()) {
234            return Err(AuthError::MethodNotEnabled("JWT".to_string()));
235        }
236
237        let validator = self
238            .jwt_validator
239            .as_ref()
240            .ok_or_else(|| AuthError::MethodNotEnabled("JWT".to_string()))?;
241
242        validator.validate_token(token)
243    }
244
245    /// Authenticate using API key
246    pub fn authenticate_api_key(&self, key: &str) -> AuthResult<Principal> {
247        if !self.config.methods.contains(&"api_key".to_string()) {
248            return Err(AuthError::MethodNotEnabled("API Key".to_string()));
249        }
250
251        let validator = self
252            .api_key_validator
253            .as_ref()
254            .ok_or_else(|| AuthError::MethodNotEnabled("API Key".to_string()))?;
255
256        validator.validate_key(key)
257    }
258
259    /// Check if authentication is enabled
260    pub fn is_enabled(&self) -> bool {
261        self.config.enabled
262    }
263
264    /// Check if a specific method is enabled
265    pub fn is_method_enabled(&self, method: &str) -> bool {
266        self.config.methods.contains(&method.to_string())
267    }
268}
269
270/// mTLS certificate validator
271struct MtlsValidator {
272    config: MtlsSettings,
273    ca_certs: Vec<Vec<u8>>,
274}
275
276impl MtlsValidator {
277    fn new(config: MtlsSettings) -> AuthResult<Self> {
278        let ca_certs = if let Some(ref ca_dir) = config.ca_certs_dir {
279            Self::load_ca_certificates(ca_dir)?
280        } else {
281            Vec::new()
282        };
283
284        Ok(Self { config, ca_certs })
285    }
286
287    fn load_ca_certificates(dir: &Path) -> AuthResult<Vec<Vec<u8>>> {
288        let mut certs = Vec::new();
289
290        if !dir.exists() {
291            return Err(AuthError::ConfigError(format!(
292                "CA certificates directory does not exist: {}",
293                dir.display()
294            )));
295        }
296
297        for entry_result in fs::read_dir(dir)? {
298            let entry = entry_result?;
299            let path = entry.path();
300
301            if path.is_file() {
302                if let Some(ext) = path.extension() {
303                    if ext == "crt" || ext == "pem" || ext == "der" {
304                        let cert_data = fs::read(&path)?;
305                        certs.push(cert_data);
306                        debug!("Loaded CA certificate: {}", path.display());
307                    }
308                }
309            }
310        }
311
312        info!("Loaded {} CA certificates", certs.len());
313        Ok(certs)
314    }
315
316    fn validate_certificate(&self, cert_der: &[u8]) -> AuthResult<Principal> {
317        // Parse the certificate
318        let (_, cert) = X509Certificate::from_der(cert_der).map_err(|e| {
319            AuthError::CertificateError(format!("Failed to parse certificate: {}", e))
320        })?;
321
322        // Verify certificate validity period
323        let now = std::time::SystemTime::now();
324        let not_before = cert.validity().not_before.to_datetime();
325        let not_after = cert.validity().not_after.to_datetime();
326
327        if now < not_before {
328            return Err(AuthError::CertificateError(
329                "Certificate not yet valid".to_string(),
330            ));
331        }
332
333        if now > not_after {
334            return Err(AuthError::CertificateError(
335                "Certificate has expired".to_string(),
336            ));
337        }
338
339        // Extract subject information
340        let subject = cert.subject();
341        let cn = subject
342            .iter_common_name()
343            .next()
344            .and_then(|cn| cn.as_str().ok())
345            .ok_or_else(|| AuthError::CertificateError("No CN in certificate".to_string()))?;
346
347        let organization = subject
348            .iter_organization()
349            .next()
350            .and_then(|o| o.as_str().ok());
351
352        // Verify organization if restrictions are configured
353        if !self.config.allowed_organizations.is_empty() {
354            let org = organization.ok_or_else(|| {
355                AuthError::CertificateError("Certificate has no organization".to_string())
356            })?;
357
358            if !self.config.allowed_organizations.contains(&org.to_string()) {
359                return Err(AuthError::CertificateError(format!(
360                    "Organization '{}' not allowed",
361                    org
362                )));
363            }
364        }
365
366        // Create principal
367        let mut principal = Principal::new(cn.to_string(), cn.to_string(), AuthMethod::MutualTls);
368
369        if let Some(org) = organization {
370            principal = principal.with_attribute("organization".to_string(), org.to_string());
371        }
372
373        debug!("Successfully authenticated certificate for user: {}", cn);
374        Ok(principal)
375    }
376}
377
378/// JWT token validator
379struct JwtValidator {
380    config: JwtSettings,
381    decoding_key: DecodingKey,
382    validation: Validation,
383}
384
385impl std::fmt::Debug for JwtValidator {
386    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387        f.debug_struct("JwtValidator")
388            .field("config", &self.config)
389            .field("decoding_key", &"<redacted>")
390            .field("validation", &"<validation>")
391            .finish()
392    }
393}
394
395impl JwtValidator {
396    fn new(config: JwtSettings) -> AuthResult<Self> {
397        let algorithm = match config.algorithm.as_str() {
398            "HS256" => Algorithm::HS256,
399            "HS384" => Algorithm::HS384,
400            "HS512" => Algorithm::HS512,
401            "RS256" => Algorithm::RS256,
402            "RS384" => Algorithm::RS384,
403            "RS512" => Algorithm::RS512,
404            "ES256" => Algorithm::ES256,
405            "ES384" => Algorithm::ES384,
406            "EdDSA" => Algorithm::EdDSA,
407            _ => {
408                return Err(AuthError::ConfigError(format!(
409                    "Unsupported JWT algorithm: {}",
410                    config.algorithm
411                )));
412            }
413        };
414
415        let decoding_key = match algorithm {
416            Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => {
417                let secret = config.secret.as_ref().ok_or_else(|| {
418                    AuthError::ConfigError("JWT secret not configured".to_string())
419                })?;
420                DecodingKey::from_secret(secret.as_bytes())
421            }
422            Algorithm::RS256 | Algorithm::RS384 | Algorithm::RS512 => {
423                let public_key_path = config.public_key_path.as_ref().ok_or_else(|| {
424                    AuthError::ConfigError("JWT RSA public key path not configured".to_string())
425                })?;
426                let pem = fs::read_to_string(public_key_path)?;
427                DecodingKey::from_rsa_pem(pem.as_bytes()).map_err(|e| {
428                    AuthError::ConfigError(format!("Failed to load RSA public key: {}", e))
429                })?
430            }
431            Algorithm::ES256 | Algorithm::ES384 => {
432                let ec_key_path = config.ec_public_key_path.as_ref().ok_or_else(|| {
433                    AuthError::ConfigError("JWT EC public key path not configured".to_string())
434                })?;
435                let pem = fs::read_to_string(ec_key_path)?;
436                DecodingKey::from_ec_pem(pem.as_bytes()).map_err(|e| {
437                    AuthError::ConfigError(format!("Failed to load EC public key: {}", e))
438                })?
439            }
440            Algorithm::EdDSA => {
441                let ed_key_path = config.ed_public_key_path.as_ref().ok_or_else(|| {
442                    AuthError::ConfigError("JWT Ed25519 public key path not configured".to_string())
443                })?;
444                let pem = fs::read_to_string(ed_key_path)?;
445                DecodingKey::from_ed_pem(pem.as_bytes()).map_err(|e| {
446                    AuthError::ConfigError(format!("Failed to load Ed25519 public key: {}", e))
447                })?
448            }
449            _ => {
450                return Err(AuthError::ConfigError(
451                    "Algorithm not implemented".to_string(),
452                ));
453            }
454        };
455
456        let mut validation = Validation::new(algorithm);
457        validation.validate_exp = true;
458
459        if let Some(ref issuer) = config.issuer {
460            validation.set_issuer(&[issuer]);
461        }
462
463        if let Some(ref audience) = config.audience {
464            validation.set_audience(&[audience]);
465        }
466
467        Ok(Self {
468            config,
469            decoding_key,
470            validation,
471        })
472    }
473
474    fn validate_token(&self, token: &str) -> AuthResult<Principal> {
475        // Decode and validate the token
476        let token_data = decode::<JwtClaims>(token, &self.decoding_key, &self.validation)
477            .map_err(|e| AuthError::JwtError(format!("Token validation failed: {}", e)))?;
478
479        let claims = token_data.claims;
480
481        // Create principal
482        let name = claims.name.unwrap_or_else(|| claims.sub.clone());
483        let mut principal = Principal::new(claims.sub, name, AuthMethod::Jwt);
484
485        // Add roles
486        if let Some(roles) = claims.roles {
487            principal = principal.with_attribute("roles".to_string(), roles.join(","));
488        }
489
490        // Add custom attributes
491        for (key, value) in claims.attributes {
492            if let Some(s) = value.as_str() {
493                principal = principal.with_attribute(key, s.to_string());
494            }
495        }
496
497        debug!(
498            "Successfully authenticated JWT token for user: {}",
499            principal.name
500        );
501        Ok(principal)
502    }
503}
504
505/// API key validator
506struct ApiKeyValidator {
507    config: ApiKeySettings,
508    keys: HashMap<String, ApiKeyEntry>,
509}
510
511impl ApiKeyValidator {
512    fn new(config: ApiKeySettings) -> AuthResult<Self> {
513        let keys_file = config
514            .keys_file
515            .as_ref()
516            .ok_or_else(|| AuthError::ConfigError("API keys file not configured".to_string()))?;
517
518        let keys = Self::load_keys(keys_file, config.hash_keys)?;
519
520        info!("Loaded {} API keys", keys.len());
521
522        Ok(Self { config, keys })
523    }
524
525    fn load_keys(path: &Path, hash_keys: bool) -> AuthResult<HashMap<String, ApiKeyEntry>> {
526        if !path.exists() {
527            return Err(AuthError::ConfigError(format!(
528                "API keys file does not exist: {}",
529                path.display()
530            )));
531        }
532
533        let contents = fs::read_to_string(path)?;
534        let entries: Vec<ApiKeyEntry> = serde_json::from_str(&contents)?;
535
536        let mut keys = HashMap::new();
537        for entry in entries {
538            let key_value = if hash_keys {
539                entry
540                    .key_hash
541                    .clone()
542                    .ok_or_else(|| AuthError::ConfigError("Missing key_hash".to_string()))?
543            } else {
544                entry
545                    .key
546                    .clone()
547                    .ok_or_else(|| AuthError::ConfigError("Missing key".to_string()))?
548            };
549
550            keys.insert(key_value, entry);
551        }
552
553        Ok(keys)
554    }
555
556    fn validate_key(&self, key: &str) -> AuthResult<Principal> {
557        let entry = if self.config.hash_keys {
558            // Compare SHA-256 hashes (fixed 43-char base64 output) via a
559            // normal HashMap lookup — hash comparisons are constant-length
560            // and the SHA-256 pre-image resistance prevents oracle attacks.
561            let hash = Self::hash_key(key);
562            self.keys.get(&hash).ok_or(AuthError::InvalidCredentials)?
563        } else {
564            // Raw key path: constant-time byte comparison to prevent timing
565            // side-channel attacks that could reveal the stored key value.
566            // Linear scan ensures the lookup time is independent of where a
567            // matching key appears in the map (O(n) over all stored keys).
568            let key_bytes = key.as_bytes();
569            self.keys
570                .iter()
571                .find(|(stored, _)| constant_time_eq(stored.as_bytes(), key_bytes))
572                .map(|(_, e)| e)
573                .ok_or(AuthError::InvalidCredentials)?
574        };
575
576        // Create principal
577        let mut principal = Principal::new(
578            entry.user_id.clone(),
579            entry.name.clone(),
580            AuthMethod::ApiKey,
581        );
582
583        // Add roles
584        if !entry.roles.is_empty() {
585            principal = principal.with_attribute("roles".to_string(), entry.roles.join(","));
586        }
587
588        // Add custom attributes
589        for (key, value) in &entry.attributes {
590            principal = principal.with_attribute(key.clone(), value.clone());
591        }
592
593        debug!(
594            "Successfully authenticated API key for user: {}",
595            entry.user_id
596        );
597        Ok(principal)
598    }
599
600    fn hash_key(key: &str) -> String {
601        let mut hasher = Sha256::new();
602        hasher.update(key.as_bytes());
603        let result = hasher.finalize();
604        BASE64.encode(result)
605    }
606}
607
608#[cfg(test)]
609mod tests {
610    use super::*;
611    use jsonwebtoken::{EncodingKey, Header, encode};
612    use std::env;
613    use std::io::Write;
614
615    /// Helper: create a JwtSettings for HMAC-based algorithms
616    fn hmac_jwt_settings(algorithm: &str, secret: &str) -> JwtSettings {
617        JwtSettings {
618            enabled: true,
619            secret: Some(secret.to_string()),
620            public_key_path: None,
621            ec_public_key_path: None,
622            ed_public_key_path: None,
623            algorithm: algorithm.to_string(),
624            expiration_secs: 3600,
625            issuer: None,
626            audience: None,
627        }
628    }
629
630    /// Helper: create JWT claims with expiration in the future
631    fn make_claims(sub: &str) -> JwtClaims {
632        let now = std::time::SystemTime::now()
633            .duration_since(std::time::UNIX_EPOCH)
634            .expect("system time before epoch")
635            .as_secs() as usize;
636        JwtClaims {
637            sub: sub.to_string(),
638            exp: now + 3600,
639            iat: Some(now),
640            iss: None,
641            aud: None,
642            name: Some(format!("User {}", sub)),
643            roles: Some(vec!["admin".to_string()]),
644            attributes: HashMap::new(),
645        }
646    }
647
648    /// Helper: create expired JWT claims
649    fn make_expired_claims(sub: &str) -> JwtClaims {
650        let now = std::time::SystemTime::now()
651            .duration_since(std::time::UNIX_EPOCH)
652            .expect("system time before epoch")
653            .as_secs() as usize;
654        JwtClaims {
655            sub: sub.to_string(),
656            exp: now.saturating_sub(3600), // expired 1 hour ago
657            iat: Some(now.saturating_sub(7200)),
658            iss: None,
659            aud: None,
660            name: Some(format!("User {}", sub)),
661            roles: None,
662            attributes: HashMap::new(),
663        }
664    }
665
666    /// Helper: write content to a temporary file and return the path
667    fn write_temp_key(name: &str, content: &[u8]) -> std::path::PathBuf {
668        let dir = std::env::temp_dir().join("amaters_jwt_test");
669        std::fs::create_dir_all(&dir).expect("failed to create temp dir");
670        let path = dir.join(name);
671        let mut file = std::fs::File::create(&path).expect("failed to create temp file");
672        file.write_all(content).expect("failed to write temp file");
673        path
674    }
675
676    /// Generate RSA key pair PEM bytes (private, public) using openssl-like approach
677    /// We use the jsonwebtoken crate's own key parsing to ensure compatibility.
678    /// These are pre-generated 2048-bit RSA test keys.
679    fn rsa_test_keys() -> (&'static [u8], &'static [u8]) {
680        // Pre-generated 2048-bit RSA key pair for testing
681        (
682            include_bytes!("../tests/fixtures/rsa_private.pem"),
683            include_bytes!("../tests/fixtures/rsa_public.pem"),
684        )
685    }
686
687    fn ec256_test_keys() -> (&'static [u8], &'static [u8]) {
688        (
689            include_bytes!("../tests/fixtures/ec256_private.pem"),
690            include_bytes!("../tests/fixtures/ec256_public.pem"),
691        )
692    }
693
694    fn ec384_test_keys() -> (&'static [u8], &'static [u8]) {
695        (
696            include_bytes!("../tests/fixtures/ec384_private.pem"),
697            include_bytes!("../tests/fixtures/ec384_public.pem"),
698        )
699    }
700
701    fn ed25519_test_keys() -> (&'static [u8], &'static [u8]) {
702        (
703            include_bytes!("../tests/fixtures/ed25519_private.pem"),
704            include_bytes!("../tests/fixtures/ed25519_public.pem"),
705        )
706    }
707
708    #[test]
709    fn test_principal_creation() {
710        let principal = Principal::new(
711            "user123".to_string(),
712            "John Doe".to_string(),
713            AuthMethod::Jwt,
714        );
715
716        assert_eq!(principal.id, "user123");
717        assert_eq!(principal.name, "John Doe");
718        assert_eq!(principal.auth_method, AuthMethod::Jwt);
719    }
720
721    #[test]
722    fn test_principal_attributes() {
723        let principal = Principal::new(
724            "user123".to_string(),
725            "John Doe".to_string(),
726            AuthMethod::Jwt,
727        )
728        .with_attribute("role".to_string(), "admin".to_string())
729        .with_attribute("department".to_string(), "Engineering".to_string());
730
731        assert_eq!(principal.get_attribute("role"), Some(&"admin".to_string()));
732        assert!(principal.has_role("admin"));
733        assert!(!principal.has_role("user"));
734    }
735
736    #[test]
737    fn test_api_key_hashing() {
738        let key = "test-api-key-12345";
739        let hash1 = ApiKeyValidator::hash_key(key);
740        let hash2 = ApiKeyValidator::hash_key(key);
741
742        assert_eq!(hash1, hash2); // Same key produces same hash
743        assert!(!hash1.is_empty());
744    }
745
746    #[test]
747    fn test_authenticator_creation() {
748        let config = AuthSettings {
749            enabled: true,
750            methods: vec!["mtls".to_string()],
751            mtls: MtlsSettings {
752                enabled: true,
753                ca_certs_dir: Some(env::temp_dir()),
754                crl_path: None,
755                verify_cn: true,
756                allowed_organizations: Vec::new(),
757            },
758            jwt: JwtSettings::default(),
759            api_key: ApiKeySettings::default(),
760            reject_unauthenticated: true,
761        };
762
763        let auth_result = Authenticator::new(config);
764        assert!(auth_result.is_ok());
765    }
766
767    #[test]
768    fn test_jwt_validator_creation_missing_secret() {
769        let config = JwtSettings {
770            enabled: true,
771            secret: None,
772            public_key_path: None,
773            ec_public_key_path: None,
774            ed_public_key_path: None,
775            algorithm: "HS256".to_string(),
776            expiration_secs: 3600,
777            issuer: None,
778            audience: None,
779        };
780
781        let result = JwtValidator::new(config);
782        assert!(result.is_err());
783    }
784
785    #[test]
786    fn test_auth_method_display() {
787        assert_eq!(format!("{}", AuthMethod::MutualTls), "mTLS");
788        assert_eq!(format!("{}", AuthMethod::Jwt), "JWT");
789        assert_eq!(format!("{}", AuthMethod::ApiKey), "API Key");
790    }
791
792    // ---- JWT algorithm validation tests ----
793
794    #[test]
795    fn test_jwt_hs256_validation() {
796        let secret = "super-secret-key-for-hs256-testing";
797        let config = hmac_jwt_settings("HS256", secret);
798        let validator = JwtValidator::new(config).expect("HS256 validator creation failed");
799
800        let claims = make_claims("user-hs256");
801        let header = Header::new(Algorithm::HS256);
802        let encoding_key = EncodingKey::from_secret(secret.as_bytes());
803        let token = encode(&header, &claims, &encoding_key).expect("HS256 token encoding failed");
804
805        let principal = validator
806            .validate_token(&token)
807            .expect("HS256 token validation failed");
808        assert_eq!(principal.id, "user-hs256");
809        assert_eq!(principal.auth_method, AuthMethod::Jwt);
810    }
811
812    #[test]
813    fn test_jwt_rs256_validation() {
814        let (private_pem, public_pem) = rsa_test_keys();
815        let pub_path = write_temp_key("rs256_pub.pem", public_pem);
816
817        let config = JwtSettings {
818            enabled: true,
819            secret: None,
820            public_key_path: Some(pub_path),
821            ec_public_key_path: None,
822            ed_public_key_path: None,
823            algorithm: "RS256".to_string(),
824            expiration_secs: 3600,
825            issuer: None,
826            audience: None,
827        };
828        let validator = JwtValidator::new(config).expect("RS256 validator creation failed");
829
830        let claims = make_claims("user-rs256");
831        let header = Header::new(Algorithm::RS256);
832        let encoding_key =
833            EncodingKey::from_rsa_pem(private_pem).expect("RS256 encoding key creation failed");
834        let token = encode(&header, &claims, &encoding_key).expect("RS256 token encoding failed");
835
836        let principal = validator
837            .validate_token(&token)
838            .expect("RS256 token validation failed");
839        assert_eq!(principal.id, "user-rs256");
840    }
841
842    #[test]
843    fn test_jwt_rs384_validation() {
844        let (private_pem, public_pem) = rsa_test_keys();
845        let pub_path = write_temp_key("rs384_pub.pem", public_pem);
846
847        let config = JwtSettings {
848            enabled: true,
849            secret: None,
850            public_key_path: Some(pub_path),
851            ec_public_key_path: None,
852            ed_public_key_path: None,
853            algorithm: "RS384".to_string(),
854            expiration_secs: 3600,
855            issuer: None,
856            audience: None,
857        };
858        let validator = JwtValidator::new(config).expect("RS384 validator creation failed");
859
860        let claims = make_claims("user-rs384");
861        let header = Header::new(Algorithm::RS384);
862        let encoding_key =
863            EncodingKey::from_rsa_pem(private_pem).expect("RS384 encoding key creation failed");
864        let token = encode(&header, &claims, &encoding_key).expect("RS384 token encoding failed");
865
866        let principal = validator
867            .validate_token(&token)
868            .expect("RS384 token validation failed");
869        assert_eq!(principal.id, "user-rs384");
870    }
871
872    #[test]
873    fn test_jwt_rs512_validation() {
874        let (private_pem, public_pem) = rsa_test_keys();
875        let pub_path = write_temp_key("rs512_pub.pem", public_pem);
876
877        let config = JwtSettings {
878            enabled: true,
879            secret: None,
880            public_key_path: Some(pub_path),
881            ec_public_key_path: None,
882            ed_public_key_path: None,
883            algorithm: "RS512".to_string(),
884            expiration_secs: 3600,
885            issuer: None,
886            audience: None,
887        };
888        let validator = JwtValidator::new(config).expect("RS512 validator creation failed");
889
890        let claims = make_claims("user-rs512");
891        let header = Header::new(Algorithm::RS512);
892        let encoding_key =
893            EncodingKey::from_rsa_pem(private_pem).expect("RS512 encoding key creation failed");
894        let token = encode(&header, &claims, &encoding_key).expect("RS512 token encoding failed");
895
896        let principal = validator
897            .validate_token(&token)
898            .expect("RS512 token validation failed");
899        assert_eq!(principal.id, "user-rs512");
900    }
901
902    #[test]
903    fn test_jwt_es256_validation() {
904        let (private_pem, public_pem) = ec256_test_keys();
905        let pub_path = write_temp_key("es256_pub.pem", public_pem);
906
907        let config = JwtSettings {
908            enabled: true,
909            secret: None,
910            public_key_path: None,
911            ec_public_key_path: Some(pub_path),
912            ed_public_key_path: None,
913            algorithm: "ES256".to_string(),
914            expiration_secs: 3600,
915            issuer: None,
916            audience: None,
917        };
918        let validator = JwtValidator::new(config).expect("ES256 validator creation failed");
919
920        let claims = make_claims("user-es256");
921        let header = Header::new(Algorithm::ES256);
922        let encoding_key =
923            EncodingKey::from_ec_pem(private_pem).expect("ES256 encoding key creation failed");
924        let token = encode(&header, &claims, &encoding_key).expect("ES256 token encoding failed");
925
926        let principal = validator
927            .validate_token(&token)
928            .expect("ES256 token validation failed");
929        assert_eq!(principal.id, "user-es256");
930    }
931
932    #[test]
933    fn test_jwt_es384_validation() {
934        let (private_pem, public_pem) = ec384_test_keys();
935        let pub_path = write_temp_key("es384_pub.pem", public_pem);
936
937        let config = JwtSettings {
938            enabled: true,
939            secret: None,
940            public_key_path: None,
941            ec_public_key_path: Some(pub_path),
942            ed_public_key_path: None,
943            algorithm: "ES384".to_string(),
944            expiration_secs: 3600,
945            issuer: None,
946            audience: None,
947        };
948        let validator = JwtValidator::new(config).expect("ES384 validator creation failed");
949
950        let claims = make_claims("user-es384");
951        let header = Header::new(Algorithm::ES384);
952        let encoding_key =
953            EncodingKey::from_ec_pem(private_pem).expect("ES384 encoding key creation failed");
954        let token = encode(&header, &claims, &encoding_key).expect("ES384 token encoding failed");
955
956        let principal = validator
957            .validate_token(&token)
958            .expect("ES384 token validation failed");
959        assert_eq!(principal.id, "user-es384");
960    }
961
962    #[test]
963    fn test_jwt_eddsa_validation() {
964        let (private_pem, public_pem) = ed25519_test_keys();
965        let pub_path = write_temp_key("eddsa_pub.pem", public_pem);
966
967        let config = JwtSettings {
968            enabled: true,
969            secret: None,
970            public_key_path: None,
971            ec_public_key_path: None,
972            ed_public_key_path: Some(pub_path),
973            algorithm: "EdDSA".to_string(),
974            expiration_secs: 3600,
975            issuer: None,
976            audience: None,
977        };
978        let validator = JwtValidator::new(config).expect("EdDSA validator creation failed");
979
980        let claims = make_claims("user-eddsa");
981        let header = Header::new(Algorithm::EdDSA);
982        let encoding_key =
983            EncodingKey::from_ed_pem(private_pem).expect("EdDSA encoding key creation failed");
984        let token = encode(&header, &claims, &encoding_key).expect("EdDSA token encoding failed");
985
986        let principal = validator
987            .validate_token(&token)
988            .expect("EdDSA token validation failed");
989        assert_eq!(principal.id, "user-eddsa");
990    }
991
992    #[test]
993    fn test_jwt_algorithm_mismatch() {
994        // Create an RS256 token but try to validate with HS256
995        let secret = "test-mismatch-secret";
996        let config = hmac_jwt_settings("HS256", secret);
997        let validator = JwtValidator::new(config).expect("HS256 validator creation failed");
998
999        let (private_pem, _) = rsa_test_keys();
1000        let claims = make_claims("user-mismatch");
1001        let header = Header::new(Algorithm::RS256);
1002        let encoding_key =
1003            EncodingKey::from_rsa_pem(private_pem).expect("RS256 encoding key creation failed");
1004        let token = encode(&header, &claims, &encoding_key).expect("RS256 token encoding failed");
1005
1006        let result = validator.validate_token(&token);
1007        assert!(result.is_err(), "Algorithm mismatch should fail validation");
1008    }
1009
1010    #[test]
1011    fn test_jwt_missing_ec_key_path() {
1012        let config = JwtSettings {
1013            enabled: true,
1014            secret: None,
1015            public_key_path: None,
1016            ec_public_key_path: None, // Not configured
1017            ed_public_key_path: None,
1018            algorithm: "ES256".to_string(),
1019            expiration_secs: 3600,
1020            issuer: None,
1021            audience: None,
1022        };
1023
1024        let result = JwtValidator::new(config);
1025        assert!(result.is_err());
1026        let err_msg = format!("{}", result.expect_err("should be an error"));
1027        assert!(
1028            err_msg.contains("EC public key path not configured"),
1029            "Expected EC key path error, got: {}",
1030            err_msg
1031        );
1032    }
1033
1034    #[test]
1035    fn test_jwt_missing_ed_key_path() {
1036        let config = JwtSettings {
1037            enabled: true,
1038            secret: None,
1039            public_key_path: None,
1040            ec_public_key_path: None,
1041            ed_public_key_path: None, // Not configured
1042            algorithm: "EdDSA".to_string(),
1043            expiration_secs: 3600,
1044            issuer: None,
1045            audience: None,
1046        };
1047
1048        let result = JwtValidator::new(config);
1049        assert!(result.is_err());
1050        let err_msg = format!("{}", result.expect_err("should be an error"));
1051        assert!(
1052            err_msg.contains("Ed25519 public key path not configured"),
1053            "Expected Ed25519 key path error, got: {}",
1054            err_msg
1055        );
1056    }
1057
1058    #[test]
1059    fn test_jwt_invalid_ec_key() {
1060        // Write corrupt PEM content
1061        let corrupt_path = write_temp_key("corrupt_ec.pem", b"NOT A VALID PEM KEY");
1062
1063        let config = JwtSettings {
1064            enabled: true,
1065            secret: None,
1066            public_key_path: None,
1067            ec_public_key_path: Some(corrupt_path),
1068            ed_public_key_path: None,
1069            algorithm: "ES256".to_string(),
1070            expiration_secs: 3600,
1071            issuer: None,
1072            audience: None,
1073        };
1074
1075        let result = JwtValidator::new(config);
1076        assert!(result.is_err(), "Corrupt EC key should fail");
1077        let err_msg = format!("{}", result.expect_err("should be an error"));
1078        assert!(
1079            err_msg.contains("Failed to load EC public key"),
1080            "Expected EC key load error, got: {}",
1081            err_msg
1082        );
1083    }
1084
1085    #[test]
1086    fn test_jwt_expired_token() {
1087        let secret = "expiration-test-secret";
1088        let config = hmac_jwt_settings("HS256", secret);
1089        let validator = JwtValidator::new(config).expect("HS256 validator creation failed");
1090
1091        let claims = make_expired_claims("user-expired");
1092        let header = Header::new(Algorithm::HS256);
1093        let encoding_key = EncodingKey::from_secret(secret.as_bytes());
1094        let token = encode(&header, &claims, &encoding_key).expect("Expired token encoding failed");
1095
1096        let result = validator.validate_token(&token);
1097        assert!(result.is_err(), "Expired token should fail validation");
1098        let err_msg = format!("{}", result.expect_err("should be an error"));
1099        assert!(
1100            err_msg.contains("Token validation failed"),
1101            "Expected token validation error, got: {}",
1102            err_msg
1103        );
1104    }
1105
1106    #[test]
1107    fn test_jwt_unsupported_algorithm() {
1108        let config = JwtSettings {
1109            enabled: true,
1110            secret: Some("secret".to_string()),
1111            public_key_path: None,
1112            ec_public_key_path: None,
1113            ed_public_key_path: None,
1114            algorithm: "UNSUPPORTED".to_string(),
1115            expiration_secs: 3600,
1116            issuer: None,
1117            audience: None,
1118        };
1119
1120        let result = JwtValidator::new(config);
1121        assert!(result.is_err());
1122        let err_msg = format!("{}", result.expect_err("should be an error"));
1123        assert!(
1124            err_msg.contains("Unsupported JWT algorithm"),
1125            "Expected unsupported algorithm error, got: {}",
1126            err_msg
1127        );
1128    }
1129}