use auth_framework::auth::AuthFramework;
use auth_framework::auth::AuthResult;
use auth_framework::authentication::credentials::Credential;
use auth_framework::config::AuthConfig;
use auth_framework::testing::test_infrastructure::TestEnvironmentGuard;
use auth_framework::tokens::AuthToken;
use auth_framework::{SecureJwtClaims, SecureJwtConfig, SecureJwtValidator};
use jsonwebtoken::{DecodingKey, EncodingKey, Header, encode};
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[tokio::test]
async fn test_timing_attack_resistance() {
let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
let config = AuthConfig::default();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let existing_user = "existing_user";
let nonexistent_user = "nonexistent_user_123456789";
let mut existing_times = Vec::new();
let mut nonexistent_times = Vec::new();
for _ in 0..10 {
let start = std::time::Instant::now();
let _ = framework
.authenticate(
"password",
Credential::password(existing_user, "wrong_pass"),
)
.await;
existing_times.push(start.elapsed());
let start = std::time::Instant::now();
let _ = framework
.authenticate(
"password",
Credential::password(nonexistent_user, "wrong_pass"),
)
.await;
nonexistent_times.push(start.elapsed());
}
let avg_existing: f64 = existing_times
.iter()
.map(|d| d.as_nanos() as f64)
.sum::<f64>()
/ existing_times.len() as f64;
let avg_nonexistent: f64 = nonexistent_times
.iter()
.map(|d| d.as_nanos() as f64)
.sum::<f64>()
/ nonexistent_times.len() as f64;
let ratio = if avg_existing > avg_nonexistent {
avg_existing / avg_nonexistent
} else {
avg_nonexistent / avg_existing
};
assert!(
ratio < 2.0,
"Timing attack vulnerability detected: ratio {:.2}",
ratio
);
}
#[tokio::test]
async fn test_dos_protection_mechanisms() {
let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
let config = AuthConfig::default();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let framework = Arc::new(framework);
let mut handles = Vec::new();
for i in 0..100 {
let framework = framework.clone();
let handle = tokio::spawn(async move {
framework
.create_session(
&format!("user_{}", i),
Duration::from_secs(3600),
None,
None,
)
.await
});
handles.push(handle);
}
let mut success_count = 0;
let mut error_count = 0;
for handle in handles {
match handle.await {
Ok(Ok(_)) => success_count += 1,
Ok(Err(_)) => error_count += 1,
Err(_) => error_count += 1,
}
}
assert!(
success_count + error_count == 100,
"Some requests were lost"
);
if error_count > 0 {
println!(
"DoS protection active: {}/{} requests rejected",
error_count, 100
);
} else {
println!("All requests processed successfully");
}
}
#[tokio::test]
async fn test_jwt_manipulation_attacks() {
let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
let config = AuthConfig::default();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let malicious_tokens = vec![
AuthToken::new(
"user",
"eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJ1c2VyIiwiaWF0IjoxNTE2MjM5MDIyfQ.",
Duration::from_secs(3600),
"jwt",
),
AuthToken::new(
"user",
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyIiwiaWF0IjoxNTE2MjM5MDIyfQ.INVALID_SIGNATURE",
Duration::from_secs(3600),
"jwt",
),
AuthToken::new(
"user",
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.MODIFIED_PAYLOAD.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
Duration::from_secs(3600),
"jwt",
),
AuthToken::new(
"user",
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyIiwiZXhwIjoxNTE2MjM5MDIyfQ.OLD_SIGNATURE",
Duration::from_secs(0),
"jwt",
),
];
for token in malicious_tokens {
match framework.validate_token(&token).await {
Ok(true) => panic!(
"Malicious token should not validate: {}",
token.access_token()
),
Ok(false) => (), Err(_) => (), }
}
}
#[tokio::test]
async fn test_session_hijacking_prevention() {
let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
let config = AuthConfig::default();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let mut session_ids = Vec::new();
for i in 0..10 {
let session_id = framework
.create_session(
&format!("user_{}", i),
Duration::from_secs(3600),
None,
None,
)
.await
.unwrap();
session_ids.push(session_id);
}
for (i, target_session) in session_ids.iter().enumerate() {
assert!(
framework
.get_session(target_session)
.await
.unwrap()
.is_some()
);
let manipulation_attempts = vec![
target_session.clone() + "x", target_session[..target_session.len() - 1].to_string(), target_session.replace('s', "x"), target_session.replace('-', "x"), target_session.chars().rev().collect(), format!("x{}", target_session), target_session.replace("sess_", "hack_"), format!("{}x", &target_session[..target_session.len() - 1]), ];
for manipulated_id in manipulation_attempts {
if manipulated_id == *target_session {
continue;
}
if framework
.get_session(&manipulated_id)
.await
.unwrap()
.is_some()
{
panic!(
"🚨 SECURITY VULNERABILITY: Manipulated session ID should not work!\nOriginal: {}\nManipulated: {}\nThis indicates session validation is insufficient!",
target_session, manipulated_id
);
}
}
for (j, other_session) in session_ids.iter().enumerate() {
if i != j {
let cross_manipulations = vec![
other_session.clone() + "x",
other_session[..other_session.len() - 1].to_string(),
other_session.replace('s', "x"),
];
for manipulated in cross_manipulations {
if manipulated != *target_session
&& manipulated != *other_session
&& framework.get_session(&manipulated).await.unwrap().is_some()
{
panic!(
"🚨 CROSS-SESSION VULNERABILITY: Manipulated session {} (from {}) should not work!",
manipulated, other_session
);
}
}
}
}
}
for session_id in &session_ids {
assert!(framework.get_session(session_id).await.unwrap().is_some());
}
}
#[tokio::test]
async fn test_resource_exhaustion_protection() {
let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
let config = AuthConfig::default();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let mut session_ids = Vec::new();
for i in 0..10000 {
match framework
.create_session(
&format!("user_{}", i),
Duration::from_secs(3600),
None,
None,
)
.await
{
Ok(session_id) => {
session_ids.push(session_id);
if session_ids.len() % 1000 == 0 {
println!("Created {} sessions", session_ids.len());
}
}
Err(_) => {
println!("Reached limit at {} sessions", session_ids.len());
break;
}
}
}
assert!(
!session_ids.is_empty(),
"Should be able to create at least one session"
);
for session_id in session_ids.iter().take(100) {
let _ = framework.delete_session(session_id).await;
}
}
#[tokio::test]
async fn test_input_injection_attacks() {
let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
let config = AuthConfig::default();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let injection_patterns = vec![
"'; DROP TABLE users; --",
"' OR '1'='1",
"admin'/**/OR/**/1=1--",
"user'; INSERT INTO sessions VALUES ('hack'); --",
"1' UNION SELECT * FROM sessions--",
];
for pattern in injection_patterns {
let credential = Credential::password(pattern, "password");
if let Ok(AuthResult::Success(_)) = framework.authenticate("password", credential).await {
panic!(
"Injection pattern should not produce a successful auth: {}",
pattern
);
}
let _ = framework
.create_session(pattern, Duration::from_secs(3600), None, None)
.await;
}
}
#[tokio::test]
async fn test_unicode_normalization_attacks() {
let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
let config = AuthConfig::default();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let unicode_attacks = vec![
("admin", "admin"), ("user", "u\u{0073}er"), ("test", "te\u{0301}st"), ("root", "r\u{043E}\u{043E}t"), ];
for (original, attack) in unicode_attacks {
let credential1 = Credential::password(original, "password");
let credential2 = Credential::password(attack, "password");
let result1 = framework.authenticate("password", credential1).await;
let result2 = framework.authenticate("password", credential2).await;
match (result1, result2) {
(Ok(_), Ok(_)) => (), (Err(_), Err(_)) => (), _ => {
println!(
"Warning: Inconsistent handling of '{}' vs '{}'",
original, attack
);
}
}
}
}
#[tokio::test]
async fn test_concurrent_session_limits() {
let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
let config = AuthConfig::default();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let framework = Arc::new(framework);
let user_id = "test_user";
let mut handles = Vec::new();
for i in 0..50 {
let framework = framework.clone();
let user_id = user_id.to_string();
let handle = tokio::spawn(async move {
framework
.create_session(
&user_id,
Duration::from_secs(3600),
Some(format!("session_{}", i)),
None,
)
.await
});
handles.push(handle);
}
let mut session_ids = Vec::new();
for handle in handles {
if let Ok(Ok(session_id)) = handle.await {
session_ids.push(session_id);
}
}
println!("Created {} concurrent sessions for user", session_ids.len());
assert!(
!session_ids.is_empty(),
"Should be able to create at least one session"
);
for session_id in &session_ids {
assert!(framework.get_session(session_id).await.unwrap().is_some());
}
for session_id in session_ids {
let _ = framework.delete_session(&session_id).await;
}
}
#[tokio::test]
async fn test_error_information_disclosure() {
let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
let config = AuthConfig::default();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let test_cases = vec![
("nonexistent_user", "wrong_password"),
("", ""),
("admin", "admin"),
("root", "root"),
];
for (username, password) in test_cases {
match framework
.authenticate("password", Credential::password(username, password))
.await
{
Ok(_) => (),
Err(e) => {
let error_msg = e.to_string().to_lowercase();
assert!(
!error_msg.contains("database"),
"Error leaks database info: {}",
error_msg
);
assert!(
!error_msg.contains("table"),
"Error leaks table info: {}",
error_msg
);
assert!(
!error_msg.contains("column"),
"Error leaks column info: {}",
error_msg
);
assert!(
!error_msg.contains("stack"),
"Error leaks stack trace: {}",
error_msg
);
assert!(
!error_msg.contains("panic"),
"Error leaks panic info: {}",
error_msg
);
assert!(
!error_msg.contains("internal"),
"Error leaks internal info: {}",
error_msg
);
}
}
}
}
#[tokio::test]
async fn test_rate_limiting_boundary_conditions() {
let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
let config = AuthConfig::default();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let framework = Arc::new(framework);
let mut handles = Vec::new();
for i in 0..20 {
let framework = framework.clone();
let handle = tokio::spawn(async move {
framework
.authenticate(
"password",
Credential::password(format!("user_{}", i), "wrong_pass"),
)
.await
});
handles.push(handle);
}
let mut success_count = 0;
let mut error_count = 0;
for handle in handles {
match handle.await {
Ok(Ok(_)) => success_count += 1,
Ok(Err(_)) => error_count += 1,
Err(_) => error_count += 1,
}
}
println!(
"Rate limiting test: {} successful, {} errors",
success_count, error_count
);
assert_eq!(success_count + error_count, 20, "Some requests were lost");
}
#[tokio::test]
async fn test_session_validation_strictness() {
let _env = TestEnvironmentGuard::new().with_jwt_secret("test-secret");
let config = AuthConfig::default();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let session_id1 = framework
.create_session("user1", Duration::from_secs(3600), None, None)
.await
.unwrap();
let session_id2 = framework
.create_session("user2", Duration::from_secs(3600), None, None)
.await
.unwrap();
println!("Session 1: {}", session_id1);
println!("Session 2: {}", session_id2);
let mut invalid_sessions = vec![
"sess_00000000-0000-0000-0000-000000000000".to_string(), "sess_11111111-1111-1111-1111-111111111111".to_string(),
"sess_aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa".to_string(),
"sess_ffffffff-ffff-ffff-ffff-ffffffffffff".to_string(),
"".to_string(), "invalid-session-id".to_string(),
"sess_".to_string(), format!("{}extra", session_id1), session_id1[..session_id1.len() - 1].to_string(), session_id1.replace("sess_", "hack_"), session_id1.to_uppercase(),
];
if session_id1.contains('-') {
invalid_sessions.push(session_id1.replace('-', "_"));
}
for invalid_id in invalid_sessions {
match framework.get_session(&invalid_id).await.unwrap() {
Some(_) => {
panic!(
"🚨 SECURITY FAILURE: Invalid session ID '{}' should not return a session!",
invalid_id
);
}
None => {
println!("✅ Correctly rejected invalid session: {}", invalid_id);
}
}
}
assert!(framework.get_session(&session_id1).await.unwrap().is_some());
assert!(framework.get_session(&session_id2).await.unwrap().is_some());
}
#[tokio::test]
async fn test_jwt_signature_validation_security() {
println!("🔒 Testing JWT signature validation security...");
let config = SecureJwtConfig::default();
let validator = SecureJwtValidator::new(config).expect("test JWT config");
let forged_jwt =
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdHRhY2tlciJ9.FORGED_SIGNATURE";
let decoding_key = DecodingKey::from_secret("test-secret".as_ref());
let result = validator.validate_token(forged_jwt, &decoding_key);
assert!(
result.is_err(),
"🚨 CRITICAL SECURITY FAILURE: Forged JWT was accepted!"
);
let claims = SecureJwtClaims {
sub: "user123".to_string(),
iss: "auth-framework".to_string(),
aud: "api".to_string(),
exp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64
+ 3600,
nbf: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64,
iat: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64,
jti: "secure-token".to_string(),
scope: "read".to_string(),
typ: "access".to_string(),
sid: None,
client_id: None,
auth_ctx_hash: None,
};
let secret = "test-secret";
let encoding_key = EncodingKey::from_secret(secret.as_ref());
let valid_jwt = encode(&Header::default(), &claims, &encoding_key).unwrap();
let result = validator.validate_token(&valid_jwt, &decoding_key);
if let Err(ref e) = result {
println!("JWT validation error: {}", e);
}
assert!(
result.is_ok(),
"Valid JWT with proper signature should be accepted: {:?}",
result.err()
);
let none_jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzdWIiOiJhdHRhY2tlciJ9.";
let result = validator.validate_token(none_jwt, &decoding_key);
assert!(
result.is_err(),
"🚨 CRITICAL: 'none' algorithm should be rejected!"
);
let wrong_key = DecodingKey::from_secret("wrong-secret".as_ref());
let result = validator.validate_token(&valid_jwt, &wrong_key);
assert!(
result.is_err(),
"🚨 CRITICAL: JWT should fail with wrong key!"
);
println!("✅ JWT signature validation security tests passed!");
}
#[test]
fn test_security_audit_documentation_exists() {
let guide_path = std::path::Path::new("docs/guides/SECURITY_GUIDE.md");
assert!(
guide_path.exists(),
"Security guide must exist at docs/guides/SECURITY_GUIDE.md"
);
let content = std::fs::read_to_string(guide_path).unwrap();
assert!(!content.is_empty(), "Security guide must not be empty");
println!("✅ Security audit documentation verified");
}