#![allow(unused_variables)] //! Comprehensive Authentication Edge Case Tests //! //! This test suite focuses on edge cases and security scenarios for the //! API Gateway authentication system. Coverage areas: //! //! 1. JWT Token Edge Cases: //! - Expired tokens //! - Malformed tokens //! - Invalid signatures //! - Missing required claims //! - Token format validation //! - Token refresh scenarios //! //! 2. Session Management: //! - Session timeout //! - Concurrent sessions //! - Session invalidation //! - Session renewal //! //! 3. Rate Limiting Edge Cases: //! - Per-user limits //! - Per-IP limits //! - Burst handling //! - Rate limit reset //! - Concurrent rate limit checks //! //! 4. Request Routing: //! - Backend service failures //! - Circuit breaker activation //! - Timeout handling //! - Service discovery //! - Load balancing #[path = "common/mod.rs"] mod common; use anyhow::Result; use common::{ cleanup_redis, generate_expired_token, generate_invalid_signature_token, generate_test_token, wait_for_redis, TestJwtConfig, }; use jsonwebtoken::{encode, EncodingKey, Header}; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use tonic::{metadata::MetadataValue, Request}; use uuid::Uuid; use api::auth::{ AuditLogger, AuthInterceptor, AuthzService, Jti, JwtClaims, JwtService, RateLimiter, RevocationService, }; const REDIS_URL: &str = "redis://localhost:6379"; /// Setup test authentication components async fn setup_auth_components() -> Result { wait_for_redis(REDIS_URL, 50).await?; cleanup_redis(REDIS_URL).await?; let config = TestJwtConfig::default(); let jwt_service = JwtService::new(config.secret, config.issuer, config.audience); let revocation_service = RevocationService::new(REDIS_URL).await?; let authz_service = AuthzService::new(); let rate_limiter = RateLimiter::new(100).map_err(|e| anyhow::anyhow!(e))?; let audit_logger = AuditLogger::new(true); Ok(AuthInterceptor::new( jwt_service, revocation_service, authz_service, rate_limiter, audit_logger, )) } // ============================================================================ // JWT Token Edge Cases // ============================================================================ #[tokio::test] async fn test_expired_jwt_rejected() -> Result<()> { println!("\n=== Test: Expired JWT Rejected ==="); let auth_interceptor = setup_auth_components().await?; // Generate expired token let token = generate_expired_token("user_expired")?; // Create request with expired token let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); let result = auth_interceptor.clone().authenticate(request).await; assert!(result.is_err(), "Expired JWT should be rejected"); if let Err(status) = result { assert_eq!(status.code(), tonic::Code::Unauthenticated); println!("✓ Expired token rejected: {}", status.message()); } Ok(()) } #[tokio::test] async fn test_invalid_signature_jwt_rejected() -> Result<()> { println!("\n=== Test: Invalid Signature JWT Rejected ==="); let auth_interceptor = setup_auth_components().await?; // Generate token with invalid signature let token = generate_invalid_signature_token("user_invalid_sig")?; // Create request let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); let result = auth_interceptor.clone().authenticate(request).await; assert!( result.is_err(), "JWT with invalid signature should be rejected" ); if let Err(status) = result { assert_eq!(status.code(), tonic::Code::Unauthenticated); println!("✓ Invalid signature rejected: {}", status.message()); } Ok(()) } #[tokio::test] async fn test_malformed_jwt_rejected() -> Result<()> { println!("\n=== Test: Malformed JWT Rejected ==="); let auth_interceptor = setup_auth_components().await?; // Create request with malformed token let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from("Bearer not.a.valid.jwt.token.format")?, ); let result = auth_interceptor.clone().authenticate(request).await; assert!(result.is_err(), "Malformed JWT should be rejected"); if let Err(status) = result { assert_eq!(status.code(), tonic::Code::Unauthenticated); println!("✓ Malformed token rejected: {}", status.message()); } Ok(()) } #[tokio::test] async fn test_missing_jti_claim_rejected() -> Result<()> { println!("\n=== Test: JWT Missing JTI Claim Rejected ==="); let auth_interceptor = setup_auth_components().await?; let config = TestJwtConfig::default(); let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); // Create claims without JTI (empty string) let claims = JwtClaims { jti: "".to_string(), // Invalid: empty JTI sub: "user_no_jti".to_string(), iat: now, exp: now + 3600, nbf: Some(now), iss: config.issuer.clone(), aud: config.audience.clone(), roles: vec!["trader".to_string()], permissions: vec!["api.access".to_string()], token_type: "access".to_string(), session_id: Some(Uuid::new_v4().to_string()), }; let token = encode( &Header::default(), &claims, &EncodingKey::from_secret(config.secret.as_bytes()), )?; // Create request let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); let result = auth_interceptor.clone().authenticate(request).await; assert!(result.is_err(), "JWT without JTI should be rejected"); if let Err(status) = result { println!("✓ Token without JTI rejected: {}", status.message()); } Ok(()) } #[tokio::test] async fn test_wrong_issuer_rejected() -> Result<()> { println!("\n=== Test: JWT with Wrong Issuer Rejected ==="); let auth_interceptor = setup_auth_components().await?; let config = TestJwtConfig::default(); let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); // Create claims with wrong issuer let claims = JwtClaims { jti: Jti::new().0, sub: "user_wrong_issuer".to_string(), iat: now, exp: now + 3600, nbf: Some(now), iss: "wrong-issuer".to_string(), // Wrong issuer aud: config.audience.clone(), roles: vec!["trader".to_string()], permissions: vec!["api.access".to_string()], token_type: "access".to_string(), session_id: Some(Uuid::new_v4().to_string()), }; let token = encode( &Header::default(), &claims, &EncodingKey::from_secret(config.secret.as_bytes()), )?; // Create request let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); let result = auth_interceptor.clone().authenticate(request).await; assert!(result.is_err(), "JWT with wrong issuer should be rejected"); if let Err(status) = result { println!("✓ Wrong issuer rejected: {}", status.message()); } Ok(()) } #[tokio::test] async fn test_wrong_audience_rejected() -> Result<()> { println!("\n=== Test: JWT with Wrong Audience Rejected ==="); let auth_interceptor = setup_auth_components().await?; let config = TestJwtConfig::default(); let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); // Create claims with wrong audience let claims = JwtClaims { jti: Jti::new().0, sub: "user_wrong_aud".to_string(), iat: now, exp: now + 3600, nbf: Some(now), iss: config.issuer.clone(), aud: "wrong-audience".to_string(), // Wrong audience roles: vec!["trader".to_string()], permissions: vec!["api.access".to_string()], token_type: "access".to_string(), session_id: Some(Uuid::new_v4().to_string()), }; let token = encode( &Header::default(), &claims, &EncodingKey::from_secret(config.secret.as_bytes()), )?; // Create request let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); let result = auth_interceptor.clone().authenticate(request).await; assert!( result.is_err(), "JWT with wrong audience should be rejected" ); if let Err(status) = result { println!("✓ Wrong audience rejected: {}", status.message()); } Ok(()) } #[tokio::test] async fn test_token_not_yet_valid_rejected() -> Result<()> { println!("\n=== Test: JWT Not Yet Valid (nbf) Rejected ==="); let auth_interceptor = setup_auth_components().await?; let config = TestJwtConfig::default(); let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); // Create claims with future nbf (not before) let claims = JwtClaims { jti: Jti::new().0, sub: "user_future_nbf".to_string(), iat: now, exp: now + 7200, nbf: Some(now + 3600), // Valid only in 1 hour iss: config.issuer.clone(), aud: config.audience.clone(), roles: vec!["trader".to_string()], permissions: vec!["api.access".to_string()], token_type: "access".to_string(), session_id: Some(Uuid::new_v4().to_string()), }; let token = encode( &Header::default(), &claims, &EncodingKey::from_secret(config.secret.as_bytes()), )?; // Create request let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); let result = auth_interceptor.clone().authenticate(request).await; assert!(result.is_err(), "JWT not yet valid should be rejected"); if let Err(status) = result { println!("✓ Not-yet-valid token rejected: {}", status.message()); } Ok(()) } #[tokio::test] async fn test_missing_authorization_header() -> Result<()> { println!("\n=== Test: Missing Authorization Header ==="); let auth_interceptor = setup_auth_components().await?; // Create request without Authorization header let request = Request::new(()); let result = auth_interceptor.clone().authenticate(request).await; assert!( result.is_err(), "Request without auth header should be rejected" ); if let Err(status) = result { assert_eq!(status.code(), tonic::Code::Unauthenticated); println!("✓ Missing auth header rejected: {}", status.message()); } Ok(()) } #[tokio::test] async fn test_invalid_authorization_format() -> Result<()> { println!("\n=== Test: Invalid Authorization Format ==="); let auth_interceptor = setup_auth_components().await?; // Create request with invalid format (missing "Bearer ") let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from("InvalidFormat token")?, ); let result = auth_interceptor.clone().authenticate(request).await; assert!(result.is_err(), "Invalid auth format should be rejected"); if let Err(status) = result { println!("✓ Invalid format rejected: {}", status.message()); } Ok(()) } #[tokio::test] async fn test_empty_bearer_token() -> Result<()> { println!("\n=== Test: Empty Bearer Token ==="); let auth_interceptor = setup_auth_components().await?; // Create request with empty token let mut request = Request::new(()); request .metadata_mut() .insert("authorization", MetadataValue::try_from("Bearer ")?); let result = auth_interceptor.clone().authenticate(request).await; assert!(result.is_err(), "Empty bearer token should be rejected"); if let Err(status) = result { println!("✓ Empty token rejected: {}", status.message()); } Ok(()) } // ============================================================================ // Session Management Edge Cases // ============================================================================ #[tokio::test] async fn test_concurrent_sessions_same_user() -> Result<()> { println!("\n=== Test: Concurrent Sessions for Same User ==="); let auth_interceptor = setup_auth_components().await?; // Generate two tokens for same user with different session IDs let (token1, _jti1) = generate_test_token( "user_concurrent", vec!["trader".to_string()], vec!["api.access".to_string()], 3600, )?; let (token2, _jti2) = generate_test_token( "user_concurrent", vec!["trader".to_string()], vec!["api.access".to_string()], 3600, )?; // First session request let mut request1 = Request::new(()); request1.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token1))?, ); // Second session request let mut request2 = Request::new(()); request2.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token2))?, ); // Both sessions should be valid let result1 = auth_interceptor.clone().authenticate(request1).await; let result2 = auth_interceptor.clone().authenticate(request2).await; assert!(result1.is_ok(), "First concurrent session should succeed"); assert!(result2.is_ok(), "Second concurrent session should succeed"); println!("✓ Both concurrent sessions validated successfully"); Ok(()) } #[tokio::test] async fn test_session_invalidation_revokes_token() -> Result<()> { println!("\n=== Test: Session Invalidation Revokes Token ==="); let auth_interceptor = setup_auth_components().await?; // Generate token let (token, jti) = generate_test_token( "user_session_invalidate", vec!["trader".to_string()], vec!["api.access".to_string()], 3600, )?; // First request should succeed let mut request1 = Request::new(()); request1.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); let result1 = auth_interceptor.clone().authenticate(request1).await; assert!(result1.is_ok(), "First request should succeed"); println!("✓ Token validated successfully"); // Revoke the token (simulate session invalidation) let revocation_service = RevocationService::new(REDIS_URL).await?; revocation_service .revoke_token(&Jti::from_string(jti), 3600) .await?; println!("✓ Token revoked (session invalidated)"); // Second request should fail let mut request2 = Request::new(()); request2.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); let result2 = auth_interceptor.clone().authenticate(request2).await; assert!(result2.is_err(), "Revoked token should be rejected"); if let Err(status) = result2 { println!("✓ Revoked token rejected: {}", status.message()); } Ok(()) } // ============================================================================ // Rate Limiting Edge Cases // ============================================================================ #[tokio::test] async fn test_rate_limit_per_user_independence() -> Result<()> { println!("\n=== Test: Rate Limit Per-User Independence ==="); let auth_interceptor = setup_auth_components().await?; // Create rate limiter with very low limit (5 req/s) for testing let rate_limiter = RateLimiter::new(5).map_err(|e| anyhow::anyhow!(e))?; // User 1: Make requests until rate limited let mut user1_allowed = 0; for _ in 0..10 { if rate_limiter.check_rate_limit("user_1") { user1_allowed += 1; } } // User 2: Should have independent rate limit let mut user2_allowed = 0; for _ in 0..10 { if rate_limiter.check_rate_limit("user_2") { user2_allowed += 1; } } println!(" User 1 allowed: {} requests", user1_allowed); println!(" User 2 allowed: {} requests", user2_allowed); // Both users should be able to make requests despite the other being limited assert!(user1_allowed > 0, "User 1 should be allowed some requests"); assert!(user2_allowed > 0, "User 2 should be allowed some requests"); assert!(user1_allowed <= 5, "User 1 should be rate limited"); assert!(user2_allowed <= 5, "User 2 should be rate limited"); println!("✓ Per-user rate limits are independent"); Ok(()) } #[tokio::test] async fn test_rate_limit_burst_handling() -> Result<()> { println!("\n=== Test: Rate Limit Burst Handling ==="); let rate_limiter = RateLimiter::new(10).map_err(|e| anyhow::anyhow!(e))?; // 10 req/s // Make burst of 20 requests let mut allowed = 0; let mut denied = 0; for _ in 0..20 { if rate_limiter.check_rate_limit("user_burst") { allowed += 1; } else { denied += 1; } } println!(" Burst: {} allowed, {} denied", allowed, denied); assert!(allowed <= 10, "Burst should be limited to capacity"); assert!(denied > 0, "Some requests should be denied"); println!("✓ Burst correctly limited"); Ok(()) } #[tokio::test] async fn test_rate_limit_reset_after_time() -> Result<()> { println!("\n=== Test: Rate Limit Reset After Time ==="); let rate_limiter = RateLimiter::new(5).map_err(|e| anyhow::anyhow!(e))?; // 5 req/s // Exhaust rate limit let mut first_batch = 0; for _ in 0..10 { if rate_limiter.check_rate_limit("user_reset") { first_batch += 1; } } println!(" First batch: {} allowed", first_batch); assert!(first_batch <= 5, "Should be rate limited"); // Wait for rate limit to reset (1 second) println!(" Waiting 1 second for rate limit reset..."); tokio::time::sleep(Duration::from_millis(1100)).await; // Try again - should allow more requests let mut second_batch = 0; for _ in 0..10 { if rate_limiter.check_rate_limit("user_reset") { second_batch += 1; } } println!(" Second batch: {} allowed", second_batch); assert!(second_batch > 0, "Rate limit should have reset"); println!("✓ Rate limit reset successfully"); Ok(()) } #[tokio::test] async fn test_concurrent_rate_limit_checks() -> Result<()> { println!("\n=== Test: Concurrent Rate Limit Checks ==="); let rate_limiter = RateLimiter::new(50).map_err(|e| anyhow::anyhow!(e))?; // 50 req/s // Spawn 100 concurrent tasks let mut handles = vec![]; for _ in 0..100 { let limiter = rate_limiter.clone(); let handle = tokio::spawn(async move { limiter.check_rate_limit("user_concurrent") }); handles.push(handle); } // Collect results let mut allowed = 0; for handle in handles { if let Ok(true) = handle.await { allowed += 1; } } println!(" Concurrent: {} / 100 allowed", allowed); // Should be approximately limited to 50 assert!(allowed >= 45, "Should allow close to rate limit"); assert!(allowed <= 55, "Should not significantly exceed rate limit"); println!("✓ Concurrent rate limiting working correctly"); Ok(()) } // ============================================================================ // Authorization Edge Cases // ============================================================================ #[tokio::test] async fn test_insufficient_permissions_rejected() -> Result<()> { println!("\n=== Test: Insufficient Permissions Rejected ==="); let auth_interceptor = setup_auth_components().await?; // Generate token with minimal permissions let (token, _jti) = generate_test_token( "user_no_perms", vec!["viewer".to_string()], // Only viewer role vec!["api.access".to_string()], // Only basic access 3600, )?; // Create request let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); // Token should be valid but have limited permissions let result = auth_interceptor.clone().authenticate(request).await; // Authentication succeeds, but user context reflects limited permissions if let Ok(authenticated_request) = result { let extensions = authenticated_request.extensions(); if let Some(user_ctx) = extensions.get::() { assert_eq!(user_ctx.roles, vec!["viewer".to_string()]); assert!(!user_ctx.permissions.contains(&"trading.submit".to_string())); println!( "✓ User authenticated with limited permissions: {:?}", user_ctx.permissions ); } } else { panic!("Authentication should succeed even with limited permissions"); } Ok(()) } #[tokio::test] async fn test_empty_roles_accepted() -> Result<()> { println!("\n=== Test: Empty Roles Still Authenticated ==="); let auth_interceptor = setup_auth_components().await?; // Generate token with no roles let (token, _jti) = generate_test_token( "user_no_roles", vec![], // No roles vec!["api.access".to_string()], 3600, )?; // Create request let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); let result = auth_interceptor.clone().authenticate(request).await; assert!( result.is_ok(), "User with no roles should still authenticate" ); if let Ok(authenticated_request) = result { let extensions = authenticated_request.extensions(); if let Some(user_ctx) = extensions.get::() { assert!(user_ctx.roles.is_empty()); println!("✓ User authenticated with no roles"); } } Ok(()) } // ============================================================================ // Additional JWT Edge Cases (Wave 1 Agent 8) // ============================================================================ #[tokio::test] async fn test_token_with_future_iat_rejected() -> Result<()> { println!("\n=== Test: JWT with Future IAT (Issued At) Rejected ==="); let auth_interceptor = setup_auth_components().await?; let config = TestJwtConfig::default(); let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); // Create claims with future iat (issued 1 hour in the future) let claims = JwtClaims { jti: Jti::new().0, sub: "user_future_iat".to_string(), iat: now + 3600, // Issued 1 hour in the future exp: now + 7200, // Valid for 2 hours nbf: Some(now), iss: config.issuer.clone(), aud: config.audience.clone(), roles: vec!["trader".to_string()], permissions: vec!["api.access".to_string()], token_type: "access".to_string(), session_id: Some(Uuid::new_v4().to_string()), }; let token = encode( &Header::default(), &claims, &EncodingKey::from_secret(config.secret.as_bytes()), )?; // Create request let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); let result = auth_interceptor.clone().authenticate(request).await; assert!(result.is_err(), "JWT with future iat should be rejected"); if let Err(status) = result { println!("✓ Token with future iat rejected: {}", status.message()); } Ok(()) } #[tokio::test] async fn test_token_too_old_rejected() -> Result<()> { println!("\n=== Test: JWT Too Old (>1 hour) Rejected ==="); let auth_interceptor = setup_auth_components().await?; let config = TestJwtConfig::default(); let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); // Create claims with old iat (issued 2 hours ago, max age is 1 hour) let claims = JwtClaims { jti: Jti::new().0, sub: "user_old_token".to_string(), iat: now - 7200, // Issued 2 hours ago exp: now + 3600, // Still valid for 1 hour nbf: Some(now - 7200), iss: config.issuer.clone(), aud: config.audience.clone(), roles: vec!["trader".to_string()], permissions: vec!["api.access".to_string()], token_type: "access".to_string(), session_id: Some(Uuid::new_v4().to_string()), }; let token = encode( &Header::default(), &claims, &EncodingKey::from_secret(config.secret.as_bytes()), )?; // Create request let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); let result = auth_interceptor.clone().authenticate(request).await; assert!(result.is_err(), "JWT older than 1 hour should be rejected"); if let Err(status) = result { println!("✓ Token too old rejected: {}", status.message()); } Ok(()) } #[tokio::test] async fn test_token_too_long_rejected() -> Result<()> { println!("\n=== Test: JWT Token Too Long (>8192 chars) Rejected ==="); let auth_interceptor = setup_auth_components().await?; // Create an excessively long token (potential DoS attack) let long_token = "a".repeat(8200); // Create request let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", long_token))?, ); let result = auth_interceptor.clone().authenticate(request).await; assert!(result.is_err(), "Excessively long JWT should be rejected"); if let Err(status) = result { println!("✓ Token too long rejected: {}", status.message()); } Ok(()) } #[tokio::test] async fn test_token_with_empty_subject_rejected() -> Result<()> { println!("\n=== Test: JWT with Empty Subject Claim Rejected ==="); let auth_interceptor = setup_auth_components().await?; let config = TestJwtConfig::default(); let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); // Create claims with empty subject let claims = JwtClaims { jti: Jti::new().0, sub: "".to_string(), // Empty subject iat: now, exp: now + 3600, nbf: Some(now), iss: config.issuer.clone(), aud: config.audience.clone(), roles: vec!["trader".to_string()], permissions: vec!["api.access".to_string()], token_type: "access".to_string(), session_id: Some(Uuid::new_v4().to_string()), }; let token = encode( &Header::default(), &claims, &EncodingKey::from_secret(config.secret.as_bytes()), )?; // Create request let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); let result = auth_interceptor.clone().authenticate(request).await; assert!(result.is_err(), "JWT with empty subject should be rejected"); if let Err(status) = result { println!("✓ Token with empty subject rejected: {}", status.message()); } Ok(()) } #[tokio::test] async fn test_token_with_corrupted_payload() -> Result<()> { println!("\n=== Test: JWT with Corrupted Payload ==="); let auth_interceptor = setup_auth_components().await?; // Create a token with corrupted base64 payload let corrupted_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.CORRUPTED!!!.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; // Create request let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", corrupted_token))?, ); let result = auth_interceptor.clone().authenticate(request).await; assert!( result.is_err(), "JWT with corrupted payload should be rejected" ); if let Err(status) = result { println!("✓ Corrupted payload rejected: {}", status.message()); } Ok(()) } #[tokio::test] async fn test_token_with_missing_permissions_claim() -> Result<()> { println!("\n=== Test: JWT with Empty Permissions List ==="); let auth_interceptor = setup_auth_components().await?; // Generate token with no permissions let (token, _jti) = generate_test_token( "user_no_perms", vec!["trader".to_string()], vec![], // Empty permissions 3600, )?; // Create request let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); let result = auth_interceptor.clone().authenticate(request).await; // Should fail because api.access permission is required assert!( result.is_err(), "JWT without api.access permission should be rejected" ); if let Err(status) = result { assert_eq!(status.code(), tonic::Code::PermissionDenied); println!( "✓ Token without required permissions rejected: {}", status.message() ); } Ok(()) } #[tokio::test] async fn test_multiple_failed_authentication_attempts() -> Result<()> { println!("\n=== Test: Multiple Failed Authentication Attempts ==="); let auth_interceptor = setup_auth_components().await?; let mut failed_attempts = 0; // Attempt authentication 10 times with invalid tokens for i in 0..10 { let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer invalid_token_{}", i))?, ); let result = auth_interceptor.clone().authenticate(request).await; if result.is_err() { failed_attempts += 1; } } println!(" Failed attempts: {} / 10", failed_attempts); assert_eq!( failed_attempts, 10, "All invalid token attempts should fail" ); println!("✓ Multiple failed authentication attempts handled correctly"); Ok(()) } #[tokio::test] async fn test_token_refresh_scenario() -> Result<()> { println!("\n=== Test: Token Refresh Scenario ==="); let auth_interceptor = setup_auth_components().await?; // Generate initial token let (token1, jti1) = generate_test_token( "user_refresh", vec!["trader".to_string()], vec!["api.access".to_string()], 3600, )?; // First request with token1 let mut request1 = Request::new(()); request1.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token1))?, ); let result1 = auth_interceptor.clone().authenticate(request1).await; assert!(result1.is_ok(), "First token should be valid"); println!("✓ Initial token validated"); // Revoke first token (simulate user requested refresh) let revocation_service = RevocationService::new(REDIS_URL).await?; revocation_service .revoke_token(&Jti::from_string(jti1), 3600) .await?; println!("✓ Old token revoked"); // Generate new token for same user (refresh) let (token2, _jti2) = generate_test_token( "user_refresh", vec!["trader".to_string()], vec!["api.access".to_string()], 3600, )?; // Request with revoked token should fail let mut request2 = Request::new(()); request2.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token1))?, ); let result2 = auth_interceptor.clone().authenticate(request2).await; assert!(result2.is_err(), "Revoked token should fail"); println!("✓ Revoked token rejected"); // Request with new token should succeed let mut request3 = Request::new(()); request3.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token2))?, ); let result3 = auth_interceptor.clone().authenticate(request3).await; assert!(result3.is_ok(), "New token should be valid"); println!("✓ New token validated"); Ok(()) } #[tokio::test] async fn test_bearer_token_case_sensitivity() -> Result<()> { println!("\n=== Test: Bearer Token Case Sensitivity ==="); let auth_interceptor = setup_auth_components().await?; let (token, _jti) = generate_test_token( "user_case_test", vec!["trader".to_string()], vec!["api.access".to_string()], 3600, )?; // Test with lowercase "bearer" (should fail) let mut request1 = Request::new(()); request1.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("bearer {}", token))?, ); let result1 = auth_interceptor.clone().authenticate(request1).await; assert!(result1.is_err(), "Lowercase 'bearer' should be rejected"); println!("✓ Lowercase 'bearer' rejected"); // Test with correct "Bearer" (should succeed) let mut request2 = Request::new(()); request2.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); let result2 = auth_interceptor.clone().authenticate(request2).await; assert!(result2.is_ok(), "Correct 'Bearer' should succeed"); println!("✓ Correct 'Bearer' accepted"); Ok(()) } #[tokio::test] async fn test_token_with_whitespace_padding() -> Result<()> { println!("\n=== Test: Token with Whitespace Padding ==="); let auth_interceptor = setup_auth_components().await?; let (token, _jti) = generate_test_token( "user_whitespace", vec!["trader".to_string()], vec!["api.access".to_string()], 3600, )?; // Test with trailing whitespace let mut request1 = Request::new(()); request1.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {} ", token))?, ); let result1 = auth_interceptor.clone().authenticate(request1).await; // This might fail due to whitespace in token println!( " Trailing whitespace result: {}", if result1.is_ok() { "OK" } else { "FAIL" } ); // Test with leading whitespace after Bearer let mut request2 = Request::new(()); request2.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); let result2 = auth_interceptor.clone().authenticate(request2).await; println!( " Leading whitespace result: {}", if result2.is_ok() { "OK" } else { "FAIL" } ); println!("✓ Whitespace handling tested"); Ok(()) } // ============================================================================ // Performance and Stress Edge Cases // ============================================================================ #[tokio::test] async fn test_authentication_performance_under_load() -> Result<()> { println!("\n=== Test: Authentication Performance Under Load ==="); let auth_interceptor = setup_auth_components().await?; // Generate token let (token, _jti) = generate_test_token( "user_perf", vec!["trader".to_string()], vec!["api.access".to_string()], 3600, )?; // Measure 100 sequential authentications let mut latencies = vec![]; for _ in 0..100 { let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token))?, ); let start = Instant::now(); let _ = auth_interceptor.clone().authenticate(request).await?; latencies.push(start.elapsed()); } // Calculate percentiles latencies.sort(); let p50 = latencies[49]; let p95 = latencies[94]; let p99 = latencies[98]; println!("\n Authentication Latency:"); println!(" ├─ P50: {:?}", p50); println!(" ├─ P95: {:?}", p95); println!(" └─ P99: {:?}", p99); println!(" Target: <10μs"); if p99 > Duration::from_micros(10) { println!(" ⚠ WARNING: P99 latency exceeds 10μs target"); } else { println!(" ✓ Performance target met"); } Ok(()) } #[tokio::test] async fn test_concurrent_authentication_requests() -> Result<()> { println!("\n=== Test: Concurrent Authentication Requests ==="); let auth_interceptor = setup_auth_components().await?; // Generate token let (token, _jti) = generate_test_token( "user_concurrent_auth", vec!["trader".to_string()], vec!["api.access".to_string()], 3600, )?; // Spawn 50 concurrent authentication requests let mut handles = vec![]; println!(" Spawning 50 concurrent auth requests..."); for _ in 0..50 { let interceptor = auth_interceptor.clone(); let token_clone = token.clone(); let handle = tokio::spawn(async move { let mut request = Request::new(()); request.metadata_mut().insert( "authorization", MetadataValue::try_from(format!("Bearer {}", token_clone)).unwrap(), ); interceptor.authenticate(request).await }); handles.push(handle); } // Collect results let mut success_count = 0; for handle in handles { if let Ok(Ok(_)) = handle.await { success_count += 1; } } println!(" ✓ {} / 50 concurrent auths succeeded", success_count); assert_eq!( success_count, 50, "All concurrent authentications should succeed" ); Ok(()) }