1pub use mockforge_core::config::{
12 ApiKeyConfig, AuthConfig, BasicAuthConfig, JwtConfig, OAuth2Config,
13};
14
15pub mod admin_auth;
17pub mod middleware;
18pub mod state;
19pub mod types;
20
21pub mod authenticator;
22pub mod oauth2;
23
24pub 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="; 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=="; let result = authenticate_basic(&state, auth_header);
89
90 match result {
91 Some(AuthResult::Failure(_)) => {} _ => 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(_)) => {} _ => 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(_)) => {} _ => 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 let header = Header::new(Algorithm::HS256);
202 let claims = json!({
203 "sub": "user123",
204 "iss": "test-issuer",
205 "aud": "test-audience",
206 "exp": 2000000000, "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 let header = Header::new(Algorithm::HS256);
252 let claims = json!({
253 "sub": "user123",
254 "exp": 1000000000, "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(_)) => {} _ => 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 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(_)) => {} _ => 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 => {} _ => 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 => {} _ => 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(_) => {} _ => 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()); }
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}