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