mockforge_http/
auth.rs

1//! Authentication middleware for MockForge HTTP server
2//!
3//! This module provides comprehensive authentication middleware that automatically
4//! validates requests against configured authentication schemes including:
5//! - Bearer tokens (including JWT)
6//! - Basic authentication
7//! - API keys
8//! - OAuth2 with token introspection
9
10// Re-export types from mockforge-core for convenience
11pub use mockforge_core::config::{
12    ApiKeyConfig, AuthConfig, BasicAuthConfig, JwtConfig, OAuth2Config,
13};
14
15// Sub-modules
16pub mod admin_auth;
17pub mod middleware;
18pub mod state;
19pub mod types;
20
21pub mod authenticator;
22pub mod jwks_converter;
23pub mod oauth2;
24pub mod oidc;
25pub mod risk_engine;
26pub mod token_lifecycle;
27
28// Re-export main types and functions for convenience
29pub use admin_auth::check_admin_auth;
30pub use authenticator::{authenticate_jwt, authenticate_request};
31pub use middleware::auth_middleware;
32pub use oauth2::create_oauth2_client;
33pub use state::AuthState;
34pub use types::{AuthClaims, AuthResult};
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39    use authenticator::{authenticate_api_key, authenticate_basic};
40    use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
41    use serde_json::json;
42    use std::collections::HashMap;
43    use std::sync::Arc;
44    use tokio::sync::RwLock;
45
46    #[test]
47    fn test_authenticate_basic_success() {
48        let mut credentials = HashMap::new();
49        credentials.insert("admin".to_string(), "password123".to_string());
50
51        let config = AuthConfig {
52            basic_auth: Some(BasicAuthConfig { credentials }),
53            ..Default::default()
54        };
55
56        let state = AuthState {
57            config,
58            spec: None,
59            oauth2_client: None,
60            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
61        };
62
63        let auth_header = "Basic YWRtaW46cGFzc3dvcmQxMjM="; // admin:password123 in base64
64        let result = authenticate_basic(&state, auth_header);
65
66        match result {
67            Some(AuthResult::Success(claims)) => {
68                assert_eq!(claims.username, Some("admin".to_string()));
69            }
70            _ => panic!("Expected successful authentication"),
71        }
72    }
73
74    #[test]
75    fn test_authenticate_basic_invalid_credentials() {
76        let mut credentials = HashMap::new();
77        credentials.insert("admin".to_string(), "password123".to_string());
78
79        let config = AuthConfig {
80            basic_auth: Some(BasicAuthConfig { credentials }),
81            ..Default::default()
82        };
83
84        let state = AuthState {
85            config,
86            spec: None,
87            oauth2_client: None,
88            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
89        };
90
91        let auth_header = "Basic d3Jvbmd1c2VyOndyb25ncGFzcw=="; // wronguser:wrongpass in base64
92        let result = authenticate_basic(&state, auth_header);
93
94        match result {
95            Some(AuthResult::Failure(_)) => {} // Expected
96            _ => panic!("Expected authentication failure"),
97        }
98    }
99
100    #[test]
101    fn test_authenticate_basic_invalid_format() {
102        let config = AuthConfig {
103            basic_auth: Some(BasicAuthConfig {
104                credentials: HashMap::new(),
105            }),
106            ..Default::default()
107        };
108
109        let state = AuthState {
110            config,
111            spec: None,
112            oauth2_client: None,
113            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
114        };
115
116        let auth_header = "Basic invalidbase64";
117        let result = authenticate_basic(&state, auth_header);
118
119        match result {
120            Some(AuthResult::Failure(_)) => {} // Expected
121            _ => panic!("Expected authentication failure"),
122        }
123    }
124
125    #[test]
126    fn test_authenticate_api_key_success() {
127        let config = AuthConfig {
128            api_key: Some(ApiKeyConfig {
129                header_name: "X-API-Key".to_string(),
130                query_name: None,
131                keys: vec!["valid-key-123".to_string()],
132            }),
133            ..Default::default()
134        };
135
136        let state = AuthState {
137            config,
138            spec: None,
139            oauth2_client: None,
140            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
141        };
142
143        let result = authenticate_api_key(&state, "valid-key-123");
144
145        match result {
146            Some(AuthResult::Success(claims)) => {
147                assert_eq!(
148                    claims.custom.get("api_key"),
149                    Some(&serde_json::Value::String("valid-key-123".to_string()))
150                );
151            }
152            _ => panic!("Expected successful authentication"),
153        }
154    }
155
156    #[test]
157    fn test_authenticate_api_key_invalid() {
158        let config = AuthConfig {
159            api_key: Some(ApiKeyConfig {
160                header_name: "X-API-Key".to_string(),
161                query_name: None,
162                keys: vec!["valid-key-123".to_string()],
163            }),
164            ..Default::default()
165        };
166
167        let state = AuthState {
168            config,
169            spec: None,
170            oauth2_client: None,
171            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
172        };
173
174        let result = authenticate_api_key(&state, "invalid-key");
175
176        match result {
177            Some(AuthResult::Failure(_)) => {} // Expected
178            _ => panic!("Expected authentication failure"),
179        }
180    }
181
182    #[tokio::test]
183    async fn test_authenticate_jwt_hs256_success() {
184        let secret = "my-secret-key";
185        let config = AuthConfig {
186            jwt: Some(JwtConfig {
187                secret: Some(secret.to_string()),
188                rsa_public_key: None,
189                ecdsa_public_key: None,
190                issuer: Some("test-issuer".to_string()),
191                audience: Some("test-audience".to_string()),
192                algorithms: vec!["HS256".to_string()],
193            }),
194            ..Default::default()
195        };
196
197        let state = AuthState {
198            config,
199            spec: None,
200            oauth2_client: None,
201            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
202        };
203
204        // Create a test JWT
205        let header = Header::new(Algorithm::HS256);
206        let claims = json!({
207            "sub": "user123",
208            "iss": "test-issuer",
209            "aud": "test-audience",
210            "exp": 2000000000, // Future timestamp
211            "iat": 1000000000,
212            "username": "testuser"
213        });
214
215        let token = encode(&header, &claims, &EncodingKey::from_secret(secret.as_bytes()))
216            .expect("Failed to create test JWT");
217
218        let auth_header = format!("Bearer {}", token);
219        let result = authenticate_jwt(&state, &auth_header).await;
220
221        match result {
222            Some(AuthResult::Success(claims)) => {
223                assert_eq!(claims.sub, Some("user123".to_string()));
224                assert_eq!(claims.iss, Some("test-issuer".to_string()));
225                assert_eq!(claims.aud, Some("test-audience".to_string()));
226                assert_eq!(claims.username, Some("testuser".to_string()));
227            }
228            _ => panic!("Expected successful authentication: {:?}", result),
229        }
230    }
231
232    #[tokio::test]
233    async fn test_authenticate_jwt_expired() {
234        let secret = "my-secret-key";
235        let config = AuthConfig {
236            jwt: Some(JwtConfig {
237                secret: Some(secret.to_string()),
238                rsa_public_key: None,
239                ecdsa_public_key: None,
240                issuer: None,
241                audience: None,
242                algorithms: vec!["HS256".to_string()],
243            }),
244            ..Default::default()
245        };
246
247        let state = AuthState {
248            config,
249            spec: None,
250            oauth2_client: None,
251            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
252        };
253
254        // Create an expired JWT
255        let header = Header::new(Algorithm::HS256);
256        let claims = json!({
257            "sub": "user123",
258            "exp": 1000000000, // Past timestamp
259            "iat": 900000000
260        });
261
262        let token = encode(&header, &claims, &EncodingKey::from_secret(secret.as_bytes()))
263            .expect("Failed to create test JWT");
264
265        let auth_header = format!("Bearer {}", token);
266        let result = authenticate_jwt(&state, &auth_header).await;
267
268        match result {
269            Some(AuthResult::Failure(_)) => {} // Expected
270            _ => panic!("Expected authentication failure for expired token"),
271        }
272    }
273
274    #[tokio::test]
275    async fn test_authenticate_jwt_invalid_signature() {
276        let config = AuthConfig {
277            jwt: Some(JwtConfig {
278                secret: Some("correct-secret".to_string()),
279                rsa_public_key: None,
280                ecdsa_public_key: None,
281                issuer: None,
282                audience: None,
283                algorithms: vec!["HS256".to_string()],
284            }),
285            ..Default::default()
286        };
287
288        let state = AuthState {
289            config,
290            spec: None,
291            oauth2_client: None,
292            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
293        };
294
295        // Create JWT with wrong secret
296        let header = Header::new(Algorithm::HS256);
297        let claims = json!({
298            "sub": "user123",
299            "exp": 2000000000
300        });
301
302        let token = encode(&header, &claims, &EncodingKey::from_secret("wrong-secret".as_bytes()))
303            .expect("Failed to create test JWT");
304
305        let auth_header = format!("Bearer {}", token);
306        let result = authenticate_jwt(&state, &auth_header).await;
307
308        match result {
309            Some(AuthResult::Failure(_)) => {} // Expected
310            _ => panic!("Expected authentication failure for invalid signature"),
311        }
312    }
313
314    #[tokio::test]
315    async fn test_authenticate_request_no_auth_when_optional() {
316        let config = AuthConfig {
317            require_auth: false,
318            ..Default::default()
319        };
320
321        let state = AuthState {
322            config,
323            spec: None,
324            oauth2_client: None,
325            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
326        };
327
328        let result = authenticate_request(&state, &None, &None, &None).await;
329
330        match result {
331            AuthResult::None => {} // Expected when auth is optional
332            _ => panic!("Expected no authentication required"),
333        }
334    }
335
336    #[tokio::test]
337    async fn test_authenticate_request_no_auth_when_required() {
338        let config = AuthConfig {
339            require_auth: true,
340            ..Default::default()
341        };
342
343        let state = AuthState {
344            config,
345            spec: None,
346            oauth2_client: None,
347            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
348        };
349
350        let result = authenticate_request(&state, &None, &None, &None).await;
351
352        match result {
353            AuthResult::None => {} // This will be handled by the middleware
354            _ => panic!("Expected no authentication provided"),
355        }
356    }
357
358    #[tokio::test]
359    async fn test_authenticate_request_with_valid_api_key() {
360        let config = AuthConfig {
361            api_key: Some(ApiKeyConfig {
362                header_name: "X-API-Key".to_string(),
363                query_name: None,
364                keys: vec!["valid-key".to_string()],
365            }),
366            ..Default::default()
367        };
368
369        let state = AuthState {
370            config,
371            spec: None,
372            oauth2_client: None,
373            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
374        };
375
376        let api_key_header = Some("valid-key".to_string());
377        let result = authenticate_request(&state, &None, &api_key_header, &None).await;
378
379        match result {
380            AuthResult::Success(_) => {} // Expected
381            _ => panic!("Expected successful authentication with API key"),
382        }
383    }
384
385    #[test]
386    fn test_create_oauth2_client_success() {
387        let config = OAuth2Config {
388            client_id: "test-client".to_string(),
389            client_secret: "test-secret".to_string(),
390            introspection_url: "https://example.com/introspect".to_string(),
391            auth_url: Some("https://example.com/auth".to_string()),
392            token_url: Some("https://example.com/token".to_string()),
393            token_type_hint: Some("access_token".to_string()),
394        };
395
396        let result = create_oauth2_client(&config);
397        assert!(result.is_ok());
398    }
399
400    #[test]
401    fn test_create_oauth2_client_invalid_url() {
402        let config = OAuth2Config {
403            client_id: "test-client".to_string(),
404            client_secret: "test-secret".to_string(),
405            introspection_url: "https://example.com/introspect".to_string(),
406            auth_url: Some("not-a-valid-url".to_string()),
407            token_url: None,
408            token_type_hint: None,
409        };
410
411        let result = create_oauth2_client(&config);
412        assert!(result.is_err());
413    }
414
415    #[test]
416    fn test_auth_config_default() {
417        let config = AuthConfig::default();
418        assert!(!config.require_auth);
419        assert!(config.jwt.is_none());
420        assert!(config.oauth2.is_none());
421        assert!(config.basic_auth.is_none());
422        assert!(config.api_key.is_some()); // Default API key config is created
423    }
424
425    #[test]
426    fn test_auth_claims_new() {
427        let claims = AuthClaims::new();
428        assert!(claims.sub.is_none());
429        assert!(claims.iss.is_none());
430        assert!(claims.aud.is_none());
431        assert!(claims.exp.is_none());
432        assert!(claims.iat.is_none());
433        assert!(claims.username.is_none());
434        assert!(claims.roles.is_empty());
435        assert!(claims.custom.is_empty());
436    }
437}