use auth_framework::{
auth::{AuthFramework, AuthResult},
authentication::credentials::{Credential, CredentialMetadata},
config::{AuditConfig, AuthConfig, RateLimitConfig, SecurityConfig, StorageConfig},
methods::{
ApiKeyMethod, AuthMethodEnum, JwtMethod, MfaChallenge, MfaType, OAuth2Method,
PasswordMethod,
},
tokens::{AuthToken, TokenManager},
};
use std::collections::HashMap;
use std::time::Duration;
fn create_working_config() -> AuthConfig {
AuthConfig {
token_lifetime: Duration::from_secs(3600),
refresh_token_lifetime: Duration::from_secs(86400),
enable_multi_factor: false,
issuer: "test-issuer".to_string(),
audience: "test-audience".to_string(),
secret: Some("test-secret-key-with-sufficient-length-for-security".to_string()),
storage: StorageConfig::Memory,
rate_limiting: RateLimitConfig {
enabled: false,
max_requests: 100,
window: Duration::from_secs(60),
burst: 10,
},
security: SecurityConfig {
min_password_length: 8,
require_password_complexity: false,
password_hash_algorithm: auth_framework::config::PasswordHashAlgorithm::Argon2,
jwt_algorithm: auth_framework::config::JwtAlgorithm::HS256,
secret_key: Some("test-secret-key-with-sufficient-length-for-security".to_string()),
secure_cookies: true,
cookie_same_site: auth_framework::config::CookieSameSite::Lax,
csrf_protection: false,
session_timeout: Duration::from_secs(1800),
},
audit: AuditConfig {
enabled: true,
log_success: true,
log_failures: true,
log_permissions: true,
log_tokens: false,
storage: auth_framework::config::AuditStorage::Tracing,
},
method_configs: HashMap::new(),
}
}
#[tokio::test]
async fn test_framework_creation_and_initialization() {
let config = create_working_config();
let mut framework = AuthFramework::new(config);
let init_result = framework.initialize().await;
assert!(
init_result.is_ok(),
"Framework initialization should succeed"
);
let second_init = framework.initialize().await;
assert!(second_init.is_ok(), "Second initialization should not fail");
}
#[tokio::test]
async fn test_method_registration_all_types() {
let config = create_working_config();
let mut framework = AuthFramework::new(config);
framework
.initialize()
.await
.expect("Framework initialization should succeed");
let _token_manager =
TokenManager::new_hmac(b"password-secret", "password-issuer", "password-audience");
let password_method = PasswordMethod::new();
framework.register_method("password", AuthMethodEnum::Password(password_method));
let jwt_method = JwtMethod::new()
.secret_key("jwt-secret-key")
.issuer("jwt-issuer")
.audience("jwt-audience");
framework.register_method("jwt", AuthMethodEnum::Jwt(jwt_method));
let api_key_method = ApiKeyMethod::new();
framework.register_method("api_key", AuthMethodEnum::ApiKey(api_key_method));
let oauth2_method = OAuth2Method::new();
framework.register_method("oauth2", AuthMethodEnum::OAuth2(oauth2_method));
let stats = framework
.get_stats()
.await
.expect("Getting framework stats should succeed");
assert!(
stats.registered_methods.len() >= 4,
"All methods should be registered, found: {}",
stats.registered_methods.len()
);
}
#[tokio::test]
async fn test_authentication_flows() {
let config = create_working_config();
let mut framework = AuthFramework::new(config);
framework
.initialize()
.await
.expect("Framework initialization should succeed");
let password_method = PasswordMethod::new();
framework.register_method("password", AuthMethodEnum::Password(password_method));
let credential = Credential::password("testuser", "testpass");
let result = framework.authenticate("password", credential).await;
match result {
Ok(AuthResult::Success(_)) => {
println!("Authentication succeeded");
}
Ok(AuthResult::Failure(reason)) => {
println!("Authentication failed as expected: {}", reason);
}
Ok(AuthResult::MfaRequired(_)) => {
println!("MFA required");
}
Err(e) => {
println!("Authentication error: {}", e);
}
}
let credential = Credential::password("testuser", "testpass");
let result = framework.authenticate("nonexistent", credential).await;
assert!(
result.is_err(),
"Authentication with nonexistent method should fail"
);
}
#[tokio::test]
async fn test_token_operations() {
let config = create_working_config();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let password_method = PasswordMethod::new();
framework.register_method("test_method", AuthMethodEnum::Password(password_method));
let token_result = framework
.create_auth_token(
"user123",
vec!["read".to_string(), "write".to_string()],
"test_method",
None,
)
.await;
assert!(token_result.is_ok(), "Token creation should succeed");
let token = token_result.unwrap();
let validation_result = framework.validate_token(&token).await;
assert!(
validation_result.is_ok(),
"Token validation should not error"
);
let is_valid = validation_result.unwrap();
assert!(is_valid, "Created token should be valid");
let user_info_result = framework.get_user_info(&token).await;
assert!(user_info_result.is_ok(), "Getting user info should succeed");
let user_info = user_info_result.unwrap();
assert_eq!(user_info.id, "user123", "User ID should match");
let permission_result = framework
.check_permission(&token, "read", "documents")
.await;
assert!(
permission_result.is_ok(),
"Permission check should not error"
);
let revoke_result = framework.revoke_token(&token).await;
assert!(revoke_result.is_ok(), "Token revocation should succeed");
}
#[tokio::test]
async fn test_session_management() {
let config = create_working_config();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let session_result = framework
.create_session(
"user123",
Duration::from_secs(3600),
Some("192.168.1.1".to_string()),
Some("Test User Agent".to_string()),
)
.await;
assert!(session_result.is_ok(), "Session creation should succeed");
let session_id = session_result.unwrap();
assert!(!session_id.is_empty(), "Session ID should not be empty");
let get_session_result = framework.get_session(&session_id).await;
assert!(get_session_result.is_ok(), "Getting session should succeed");
let session = get_session_result.unwrap();
assert!(session.is_some(), "Session should exist");
let session_data = session.unwrap();
assert_eq!(session_data.user_id, "user123", "User ID should match");
let delete_result = framework.delete_session(&session_id).await;
assert!(delete_result.is_ok(), "Session deletion should succeed");
let get_deleted_session = framework.get_session(&session_id).await;
assert!(
get_deleted_session.is_ok(),
"Getting deleted session should not error"
);
let deleted_session = get_deleted_session.unwrap();
assert!(
deleted_session.is_none(),
"Deleted session should not exist"
);
}
#[tokio::test]
async fn test_api_key_management() {
let config = create_working_config();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let api_key_result = framework
.create_api_key("user123", Some(Duration::from_secs(3600)))
.await;
assert!(api_key_result.is_ok(), "API key creation should succeed");
let api_key = api_key_result.unwrap();
assert!(
api_key.starts_with("ak_"),
"API key should have correct prefix"
);
let validation_result = framework.validate_api_key(&api_key).await;
match validation_result {
Ok(user_info) => {
assert_eq!(user_info.id, "user123", "User ID should match");
println!("API key validation succeeded");
}
Err(e) => {
println!(
"API key validation failed (expected with memory storage): {}",
e
);
}
}
let revoke_result = framework.revoke_api_key(&api_key).await;
match revoke_result {
Ok(_) => println!("API key revocation succeeded"),
Err(e) => println!(
"API key revocation failed (expected with memory storage): {}",
e
),
}
let validation_after_revoke = framework.validate_api_key(&api_key).await;
assert!(
validation_after_revoke.is_err(),
"Revoked API key validation should fail"
);
let permanent_key_result = framework.create_api_key("user456", None).await;
assert!(
permanent_key_result.is_ok(),
"Permanent API key creation should succeed"
);
}
#[tokio::test]
async fn test_mfa_operations() {
let config = create_working_config();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let secret_result = framework.generate_totp_secret("user123").await;
assert!(
secret_result.is_ok(),
"TOTP secret generation should succeed"
);
let secret = secret_result.unwrap();
assert!(!secret.is_empty(), "TOTP secret should not be empty");
let qr_result = framework
.generate_totp_qr_code("user123", "TestApp", &secret)
.await;
assert!(qr_result.is_ok(), "TOTP QR code generation should succeed");
let qr_url = qr_result.unwrap();
assert!(
qr_url.starts_with("otpauth://totp/"),
"QR URL should have correct format"
);
let code_result = framework.generate_totp_code(&secret).await;
assert!(code_result.is_ok(), "TOTP code generation should succeed");
let code = code_result.unwrap();
assert_eq!(code.len(), 6, "TOTP code should be 6 digits");
assert!(
code.chars().all(|c| c.is_ascii_digit()),
"TOTP code should contain only digits"
);
let phone_result = framework
.register_phone_number("user123", "+1234567890")
.await;
assert!(
phone_result.is_ok(),
"Phone number registration should succeed"
);
let email_result = framework
.register_email("user123", "user@example.com")
.await;
assert!(email_result.is_ok(), "Email registration should succeed");
let backup_codes_result = framework.generate_backup_codes("user123", 10).await;
assert!(
backup_codes_result.is_ok(),
"Backup codes generation should succeed"
);
let backup_codes = backup_codes_result.unwrap();
assert_eq!(
backup_codes.len(),
10,
"Should generate requested number of backup codes"
);
}
#[tokio::test]
async fn test_credential_types() {
let password_cred = Credential::password("user123", "secret");
assert_eq!(password_cred.credential_type(), "password");
assert!(password_cred.is_sensitive());
let oauth_cred = Credential::oauth_code("auth_code_123");
assert_eq!(oauth_cred.credential_type(), "oauth");
assert!(!oauth_cred.is_sensitive());
assert!(oauth_cred.supports_refresh());
let api_key_cred = Credential::api_key("ak_12345");
assert_eq!(api_key_cred.credential_type(), "api_key");
assert!(api_key_cred.is_sensitive());
let jwt_cred = Credential::jwt("jwt_token_123");
assert_eq!(jwt_cred.credential_type(), "jwt");
assert!(jwt_cred.is_sensitive());
let metadata = CredentialMetadata::new()
.client_id("test_client_123")
.scope("read")
.scope("write")
.user_agent("TestAgent/1.0")
.client_ip("192.168.1.100")
.custom("device_id", "device_123");
assert_eq!(metadata.client_id, Some("test_client_123".to_string()));
assert_eq!(
metadata.scopes,
vec!["read".to_string(), "write".to_string()]
);
assert_eq!(metadata.user_agent, Some("TestAgent/1.0".to_string()));
assert_eq!(metadata.client_ip, Some("192.168.1.100".to_string()));
assert_eq!(
metadata.custom.get("device_id"),
Some(&"device_123".to_string())
);
}
#[tokio::test]
async fn test_error_handling() {
let config = create_working_config();
let framework = AuthFramework::new(config);
let credential = Credential::password("user", "pass");
let auth_result = framework.authenticate("test", credential).await;
assert!(
auth_result.is_err(),
"Authentication should fail on uninitialized framework"
);
let dummy_token = AuthToken::new(
"user123".to_string(),
"dummy-token".to_string(),
Duration::from_secs(3600),
"test",
);
let validation_result = framework.validate_token(&dummy_token).await;
assert!(
validation_result.is_err(),
"Token validation should fail on uninitialized framework"
);
let session_result = framework
.create_session("user123", Duration::from_secs(3600), None, None)
.await;
assert!(
session_result.is_err(),
"Session creation should fail on uninitialized framework"
);
}
#[tokio::test]
async fn test_validation_functions() {
let config = create_working_config();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let valid_usernames = vec!["user123", "test_user", "user-name", "a_b_c"];
for username in valid_usernames {
let result = framework.validate_username(username).await;
assert!(
result.is_ok(),
"Username validation should not error for: {}",
username
);
let is_valid = result.unwrap();
assert!(is_valid, "Username should be valid: {}", username);
}
let long_username = "a".repeat(33);
let invalid_usernames = vec!["ab", "a", "", &long_username, "user@name", "user name"];
for username in invalid_usernames {
let result = framework.validate_username(username).await;
assert!(
result.is_ok(),
"Username validation should not error for: {}",
username
);
let is_valid = result.unwrap();
assert!(!is_valid, "Username should be invalid: {}", username);
}
let valid_names = vec!["John Doe", "Alice Smith", "User Name", "Test User 123"];
for name in valid_names {
let result = framework.validate_display_name(name).await;
assert!(
result.is_ok(),
"Display name validation should not error for: {}",
name
);
let is_valid = result.unwrap();
assert!(is_valid, "Display name should be valid: {}", name);
}
let ip_addresses = vec!["192.168.1.1", "10.0.0.1", "127.0.0.1"];
for ip in ip_addresses {
let result = framework.check_ip_rate_limit(ip).await;
assert!(
result.is_ok(),
"IP rate limit check should not error for: {}",
ip
);
}
}
#[tokio::test]
async fn test_cleanup_operations() {
let config = create_working_config();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let cleanup_result = framework.cleanup_expired_data().await;
assert!(cleanup_result.is_ok(), "Cleanup should succeed");
let tokens_result = framework.list_user_tokens("nonexistent_user").await;
assert!(
tokens_result.is_ok(),
"Listing tokens for non-existent user should not error"
);
let tokens = tokens_result.unwrap();
assert!(tokens.is_empty(), "Non-existent user should have no tokens");
let metrics_result = framework.get_security_metrics().await;
assert!(
metrics_result.is_ok(),
"Getting security metrics should succeed"
);
let metrics = metrics_result.unwrap();
assert!(!metrics.is_empty(), "Security metrics should not be empty");
}
#[tokio::test]
async fn test_mfa_challenge_handling() {
let mfa_challenge = MfaChallenge::new(
MfaType::Totp,
"user123",
Duration::from_secs(300), );
assert!(
!mfa_challenge.is_expired(),
"Fresh MFA challenge should not be expired"
);
let expired_challenge = MfaChallenge::new(MfaType::Totp, "user123", Duration::from_secs(0));
tokio::time::sleep(Duration::from_millis(10)).await;
assert!(
expired_challenge.is_expired(),
"Zero-duration MFA challenge should be expired"
);
}
#[tokio::test]
async fn test_end_to_end_integration() {
let config = create_working_config();
let mut framework = AuthFramework::new(config);
framework.initialize().await.unwrap();
let password_method = PasswordMethod::new();
framework.register_method("password", AuthMethodEnum::Password(password_method));
let token = framework
.create_auth_token("user123", vec!["read".to_string()], "password", None)
.await
.unwrap();
assert!(framework.validate_token(&token).await.unwrap());
let session_id = framework
.create_session("user123", Duration::from_secs(3600), None, None)
.await
.unwrap();
let session = framework.get_session(&session_id).await.unwrap();
assert!(session.is_some());
let api_key = framework
.create_api_key("user123", Some(Duration::from_secs(3600)))
.await
.unwrap();
match framework.validate_api_key(&api_key).await {
Ok(api_user_info) => {
assert_eq!(api_user_info.id, "user123");
}
Err(_) => {
println!("API key validation failed in integration test (expected)");
}
}
let secret = framework.generate_totp_secret("user123").await.unwrap();
let _code = framework.generate_totp_code(&secret).await.unwrap();
framework.delete_session(&session_id).await.unwrap();
framework.revoke_token(&token).await.unwrap();
let _ = framework.revoke_api_key(&api_key).await;
framework.cleanup_expired_data().await.unwrap();
}