//! JWT Integration Tests //! //! Comprehensive integration tests for JWT token system including: //! - Token generation and validation //! - Token rotation and refresh //! - Token revocation and blacklist //! - Concurrent access patterns //! - Security edge cases #![allow(unused_imports, clippy::needless_borrows_for_generic_args)] use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; use control_center::auth::{ jwt::{BlacklistStats, JwtService, TokenType}, password::PasswordService, user::{User, UserRole, UserService}, AuthService, }; /// Generate RSA key pair for testing (pre-generated to avoid rand_core conflict) fn generate_test_keys() -> (Vec, Vec) { let private_pem = b"-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7F43HxrVfJJ+k DQMEjENGqJLnBn6MvJnCu93A4ZNKEEpPGX1Y6V+qiqLH5B7wNMIJ2QVnLjYjKZu5 K5LkVCH8N7N1s9QXiLI7TzQVEH2TQx3MzQFRmVzGlyVFxF0qMHf/mNlZvE0Hb9oI YdYnBW5k4TRmzrN4THEbWqLcGVVfMVFQqG1j/V3G5dTpV1sN8a9xqJG0dKNmVP2M H5JG3kQ8CxFqp7pRsGvqH9IVjB6KXj8L3VPQYJxIBKHGZMlsP9SVqH3vwqLLKcBj G2UvhkVYJ6ZLvYIj7LvZKrCBrP3h5Q7hYpXZqDGYd5dDwWFJ3xkVMkJMGR3D3VrK R1jvJTEhAgMBAAECggEAXV2TZjfRbhR0IaqU3bpSZvb+6uL+5OgWRO5VbG0XGFmv 1v+2F5hQEZYKKxZvTlF4IxN1J0YQRKt4BG6PZqN9+Uj8z7DVEDqiWLIZSEhUPzVu E3IKZaEqvGKvEwBglmfqnE6cEZ2Kb9aOx1a5N8UH5q7xQ2d4YxKfYwRBkDYEZmZp R4tEb0qfKFgIGmCfK5fNvJqXvH/f1xq9QW1qrGYqGAqh7bkxvXGfkRBXh7Q8sAXW B2XQ8L9CZV5lsP0F7n8nKvDVKfqZfGXlZJZQvJPF5L8p6g7V7uE+6bZlF4YZLRvW 2aq2xZR6bGCWxBpLb2lG1QU0aJLqVmVjAQYDM9PB0QKBgQDfhKn8F5aITVFJhVVu BN8F2hLRtFDfSPTN0JM1WZq/EqYdLjnlAY8L7kBBzNaKgOGBRqfO1lVv7DG4vPtX nrLaM3SU3F0lC8tVwqJTUvZS5/Pl3wqAZRlWbWm4Yp6gWXlGPTaWRpGRBOqB+rWP b8rX2SnZjvFlQZlZPfvHGWpkFQKBgQDYtvXAGNdj8hMZ6mMUKYv3YbF7F2xUlf1x H0f8I5QGPiZoqzNGJXCEGvNVNEUgR7LY6AeqWtLqRKJsQ5NYb2XzLhA2BVOV1BV2 Z1Y3s0V5XjFE4q7gWmEcLlWyEiKiRCRQC4T7qB+gUQ8lFBrGqQ4vZqE+7pQyYfCt 1yFQnvqrQQKBgQCcvCvE4wMhF2lYFxM4v8qCDXJ9nLMKjYPFn3C0YrP6Zn2Pm7r3 fJGDVBmN5wVLDXqZnSrmh0iBs7PdBqeHALF7jTj9X8tNe+hpXwQ/GWSF8b3q1b6k wZLhKpJQGpYH0M3l7B6rrM3Vr1E/DQjXvXdqXrL3vKfHQrEk3c9zVCkfTQKBgGVl qdvBYvdgWHvPzL3+vqvLPVKDnLjN6hXA2cJGaBNpOlm+bI5cJLLnWhJrjCkK+POo 0oPKvnO5qKQjRDWLVCJH3z8MxPjqV6Yk1hAg/4vqV3oQsJDfU3qiDPAHrBbKBlRm 4KYmEqIhqNYJqXN7nnQFOLYFxOaXKqe6HBh1g4QBAoGBAIRXPwKJhwIUfLJ8DQAO 9z9YH3eIiGkLxF8f7WZvVnNwMEpXl4IqN8N3qGXKJfXJI1YfBKXN7LOqCOVFGkVq JkVN8qPTwAKU8E2U3dGzQVPP2JmGPPNyZJxQJ3sYtKnLkz2YdEbKDhkG5wPDHKKu LfEK1YMqL/VvAJdIXxqZqDXf -----END PRIVATE KEY-----"; let public_pem = b"-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuxeNx8a1XySfpA0DBIxD RqiS5wZ+jLyZwrvdwOGTShBKTxl9WOlfqoqix+Qe8DTCCdkFZy42IymbhSuS5FQh /DezdLPUF4iyO080FRB9k0MdzM0BUZlcxpclRcRdKjB3/5jZWbxNB2/aCGHWJwVu ZOE0Zs6zeExxG1qi3BlVXzFRUKhtY/1dxuXU6Vdbl/GvcaiRtHSjZlT9jB+SRt5E PAsTaqe6UbBr6h/SFYweil4/C91T0GCcSAShxmTJbD/Ulah978KizynAYxtlL4ZF WCemS72CI+y72SqwgaZ94eUO4WKV2agxmHeXQ8FhSd8ZFTJCTBKDW91Kykdo7yUx IQIDAQAB -----END PUBLIC KEY-----"; ( private_pem.to_vec(), public_pem.to_vec(), ) } /// Create JWT service for testing fn create_jwt_service() -> JwtService { let (private_key, public_key) = generate_test_keys(); JwtService::new( &private_key, &public_key, "test-control-center", vec!["orchestrator".to_string(), "cli".to_string()], ) .unwrap() } #[test] fn test_token_pair_generation() { let jwt_service = create_jwt_service(); let token_pair = jwt_service .generate_token_pair("user123", "workspace1", "perm_hash_abc", None) .unwrap(); assert_eq!(token_pair.token_type, "Bearer"); assert!(token_pair.expires_in > 0); assert!(!token_pair.access_token.is_empty()); assert!(!token_pair.refresh_token.is_empty()); assert_ne!(token_pair.access_token, token_pair.refresh_token); } #[test] fn test_token_validation_success() { let jwt_service = create_jwt_service(); let token_pair = jwt_service .generate_token_pair("user123", "workspace1", "perm_hash_abc", None) .unwrap(); // Validate access token let access_claims = jwt_service .validate_token(&token_pair.access_token) .unwrap(); assert_eq!(access_claims.sub, "user123"); assert_eq!(access_claims.workspace, "workspace1"); assert_eq!(access_claims.token_type, TokenType::Access); // Validate refresh token let refresh_claims = jwt_service .validate_token(&token_pair.refresh_token) .unwrap(); assert_eq!(refresh_claims.sub, "user123"); assert_eq!(refresh_claims.token_type, TokenType::Refresh); } #[test] fn test_token_validation_with_metadata() { let jwt_service = create_jwt_service(); let mut metadata = HashMap::new(); metadata.insert("ip_address".to_string(), "192.168.1.100".to_string()); metadata.insert("user_agent".to_string(), "test-client/1.0".to_string()); metadata.insert("session_id".to_string(), "sess_xyz789".to_string()); let token_pair = jwt_service .generate_token_pair( "user456", "workspace2", "perm_hash_def", Some(metadata.clone()), ) .unwrap(); let claims = jwt_service .validate_token(&token_pair.access_token) .unwrap(); assert!(claims.metadata.is_some()); let token_metadata = claims.metadata.unwrap(); assert_eq!( token_metadata.get("ip_address"), Some(&"192.168.1.100".to_string()) ); assert_eq!( token_metadata.get("user_agent"), Some(&"test-client/1.0".to_string()) ); assert_eq!( token_metadata.get("session_id"), Some(&"sess_xyz789".to_string()) ); } #[test] fn test_token_rotation() { let jwt_service = create_jwt_service(); // Generate initial token pair let initial_pair = jwt_service .generate_token_pair("user123", "workspace1", "perm_hash_abc", None) .unwrap(); // Rotate using refresh token let rotated_pair = jwt_service .rotate_token(&initial_pair.refresh_token) .unwrap(); // New tokens should be different assert_ne!(rotated_pair.access_token, initial_pair.access_token); assert_ne!(rotated_pair.refresh_token, initial_pair.refresh_token); // New tokens should be valid let new_claims = jwt_service .validate_token(&rotated_pair.access_token) .unwrap(); assert_eq!(new_claims.sub, "user123"); assert_eq!(new_claims.workspace, "workspace1"); // Old refresh token should be revoked let old_refresh_validation = jwt_service.validate_token(&initial_pair.refresh_token); assert!(old_refresh_validation.is_err()); } #[test] fn test_token_revocation() { let jwt_service = create_jwt_service(); let token_pair = jwt_service .generate_token_pair("user123", "workspace1", "perm_hash_abc", None) .unwrap(); // Initially valid let claims = jwt_service .validate_token(&token_pair.access_token) .unwrap(); assert!(!jwt_service.is_revoked(&claims.jti).unwrap()); // Revoke token jwt_service.revoke_token(&claims.jti, claims.exp).unwrap(); // Check revoked assert!(jwt_service.is_revoked(&claims.jti).unwrap()); // Validation should fail let validation_result = jwt_service.validate_token(&token_pair.access_token); assert!(validation_result.is_err()); } #[test] fn test_blacklist_cleanup() { let jwt_service = create_jwt_service(); // Create expired token (simulate with past timestamp) let expired_jti = "expired_token_123"; let expired_exp = chrono::Utc::now().timestamp() - 3600; // 1 hour ago jwt_service.revoke_token(expired_jti, expired_exp).unwrap(); // Create valid token let token_pair = jwt_service .generate_token_pair("user123", "workspace1", "perm_hash_abc", None) .unwrap(); let claims = jwt_service .validate_token(&token_pair.access_token) .unwrap(); jwt_service.revoke_token(&claims.jti, claims.exp).unwrap(); // Check stats before cleanup let stats_before = jwt_service.blacklist_stats().unwrap(); assert_eq!(stats_before.total_revoked, 2); // Cleanup expired tokens let removed_count = jwt_service.cleanup_expired_tokens().unwrap(); assert_eq!(removed_count, 1); // Check stats after cleanup let stats_after = jwt_service.blacklist_stats().unwrap(); assert_eq!(stats_after.total_revoked, 1); assert_eq!(stats_after.active_revocations, 1); } #[test] fn test_multiple_rotations() { let jwt_service = create_jwt_service(); let mut current_pair = jwt_service .generate_token_pair("user123", "workspace1", "perm_hash_abc", None) .unwrap(); // Perform 5 rotations for i in 0..5 { let new_pair = jwt_service .rotate_token(¤t_pair.refresh_token) .unwrap(); // Verify new tokens work let claims = jwt_service.validate_token(&new_pair.access_token).unwrap(); assert_eq!(claims.sub, "user123"); // Old refresh token should fail let old_validation = jwt_service.validate_token(¤t_pair.refresh_token); assert!(old_validation.is_err(), "Rotation {} failed", i); current_pair = new_pair; } // Final tokens should still work let final_claims = jwt_service .validate_token(¤t_pair.access_token) .unwrap(); assert_eq!(final_claims.sub, "user123"); } #[test] fn test_extract_token_from_header() { let test_cases = vec![ ("Bearer abc123xyz", Ok("abc123xyz")), ("bearer abc123xyz", Err(())), // Wrong case ("Token abc123xyz", Err(())), // Wrong prefix ("Bearer", Err(())), // Missing token ("abc123xyz", Err(())), // No prefix ]; for (header, expected) in test_cases { let result = JwtService::extract_token_from_header(header); match expected { Ok(token) => assert_eq!(result.unwrap(), token), Err(_) => assert!(result.is_err()), } } } #[test] fn test_concurrent_token_operations() { use std::sync::Arc; use std::thread; let jwt_service = Arc::new(create_jwt_service()); let mut handles = vec![]; // Spawn 10 threads generating and validating tokens concurrently for i in 0..10 { let service = jwt_service.clone(); let handle = thread::spawn(move || { let user_id = format!("user{}", i); let workspace = format!("workspace{}", i); // Generate token pair let token_pair = service .generate_token_pair(&user_id, &workspace, "perm_hash", None) .unwrap(); // Validate access token let claims = service.validate_token(&token_pair.access_token).unwrap(); assert_eq!(claims.sub, user_id); assert_eq!(claims.workspace, workspace); // Rotate token let new_pair = service.rotate_token(&token_pair.refresh_token).unwrap(); let new_claims = service.validate_token(&new_pair.access_token).unwrap(); assert_eq!(new_claims.sub, user_id); }); handles.push(handle); } // Wait for all threads to complete for handle in handles { handle.join().unwrap(); } } #[test] fn test_token_expiry_detection() { let jwt_service = create_jwt_service(); let token_pair = jwt_service .generate_token_pair("user123", "workspace1", "perm_hash_abc", None) .unwrap(); let claims = jwt_service .validate_token(&token_pair.access_token) .unwrap(); // Token should not be expired immediately assert!(!claims.is_expired()); // Token should have remaining validity let remaining = claims.remaining_validity(); assert!(remaining.as_secs() > 0); } #[tokio::test] async fn test_full_auth_flow() { let jwt_service = create_jwt_service(); let password_service = PasswordService::new(); let user_service = UserService::new(); let auth_service = AuthService::new(jwt_service, password_service, user_service); // Create user let password = "SecurePassword123!"; let password_hash = auth_service.password.hash_password(password).unwrap(); let user = User::new( "testuser", "test@example.com", "Test User", password_hash, vec![UserRole::Developer], ); auth_service.user.create_user(user).await.unwrap(); // Login let token_pair = auth_service .login("testuser", password, "workspace1") .await .unwrap(); // Validate access token let claims = auth_service.validate(&token_pair.access_token).unwrap(); assert_eq!(claims.workspace, "workspace1"); // Rotate tokens let new_pair = auth_service.refresh(&token_pair.refresh_token).unwrap(); assert_ne!(new_pair.access_token, token_pair.access_token); // Logout auth_service.logout(&new_pair.access_token).await.unwrap(); // Token should be revoked let validation_result = auth_service.validate(&new_pair.access_token); assert!(validation_result.is_err()); } #[test] fn test_invalid_signature_detection() { let (private_key1, public_key1) = generate_test_keys(); let (private_key2, _) = generate_test_keys(); // Different key pair // Service 1 generates token let jwt_service1 = JwtService::new( &private_key1, &public_key1, "test-issuer", vec!["test-audience".to_string()], ) .unwrap(); let token_pair = jwt_service1 .generate_token_pair("user123", "workspace1", "perm_hash", None) .unwrap(); // Service 2 with different keys tries to validate let jwt_service2 = JwtService::new( &private_key2, &public_key1, "test-issuer", vec!["test-audience".to_string()], ) .unwrap(); // Should fail signature verification let validation_result = jwt_service2.validate_token(&token_pair.access_token); assert!(validation_result.is_err()); } #[test] fn test_blacklist_statistics() { let jwt_service = create_jwt_service(); // Generate and revoke multiple tokens for i in 0..5 { let token_pair = jwt_service .generate_token_pair(&format!("user{}", i), "workspace1", "perm_hash", None) .unwrap(); let claims = jwt_service .validate_token(&token_pair.access_token) .unwrap(); jwt_service.revoke_token(&claims.jti, claims.exp).unwrap(); } let stats = jwt_service.blacklist_stats().unwrap(); assert_eq!(stats.total_revoked, 5); assert_eq!(stats.active_revocations, 5); assert_eq!(stats.expired_tokens, 0); } #[test] fn test_token_cannot_rotate_with_access_token() { let jwt_service = create_jwt_service(); let token_pair = jwt_service .generate_token_pair("user123", "workspace1", "perm_hash_abc", None) .unwrap(); // Attempting to rotate with access token should fail let rotation_result = jwt_service.rotate_token(&token_pair.access_token); assert!(rotation_result.is_err()); }