pub mod api_key;
pub mod jwt;
pub mod oauth;
pub mod rebac;
pub mod session;
pub use api_key::*;
pub use jwt::*;
pub use oauth::*;
pub use rebac::*;
pub use session::*;
use crate::core::error::OptionExt;
use crate::error::{Error, Result};
use crate::multitenancy::{Permission, TenantId};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone)]
pub struct AuthResult {
pub user_id: String,
pub tenant_id: TenantId,
pub permissions: Vec<Permission>,
pub expires_at: u64,
pub session_id: Option<String>,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct UserInfo {
pub user_id: String,
pub tenant_id: TenantId,
pub email: String,
pub display_name: Option<String>,
pub roles: Vec<String>,
pub permissions: Vec<Permission>,
pub active: bool,
pub created_at: SystemTime,
pub last_login: Option<SystemTime>,
password_hash: Option<String>,
pub api_keys: Vec<String>,
}
impl UserInfo {
pub fn new(
user_id: impl Into<String>,
email: impl Into<String>,
tenant_id: impl Into<String>,
) -> Self {
UserInfo {
user_id: user_id.into(),
email: email.into(),
tenant_id: tenant_id.into(),
display_name: None,
roles: Vec::new(),
permissions: vec![Permission::Read],
active: true,
created_at: SystemTime::now(),
last_login: None,
password_hash: None,
api_keys: Vec::new(),
}
}
pub fn with_display_name(mut self, name: impl Into<String>) -> Self {
self.display_name = Some(name.into());
self
}
pub fn with_role(mut self, role: impl Into<String>) -> Self {
self.roles.push(role.into());
self
}
pub fn with_permissions(mut self, perms: Vec<Permission>) -> Self {
self.permissions = perms;
self
}
pub fn with_permission(mut self, perm: Permission) -> Self {
if !self.permissions.contains(&perm) {
self.permissions.push(perm);
}
self
}
pub fn with_password(mut self, password: &str) -> Self {
self.password_hash = Some(hash_password(password));
self
}
pub fn verify_password(&self, password: &str) -> bool {
match &self.password_hash {
Some(hash) => verify_password(password, hash),
None => false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AuthMethod {
Jwt,
OAuth,
ApiKey,
Password,
ServiceAccount,
}
#[derive(Debug, Clone)]
pub struct AuthEvent {
pub timestamp: SystemTime,
pub event_type: AuthEventType,
pub user_id: Option<String>,
pub tenant_id: Option<TenantId>,
pub auth_method: AuthMethod,
pub success: bool,
pub ip_address: Option<String>,
pub user_agent: Option<String>,
pub error_message: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AuthEventType {
Login,
Logout,
TokenRefresh,
TokenValidation,
ApiKeyUsage,
PasswordChange,
UserRegistration,
PermissionDenied,
SessionExpired,
}
#[derive(Debug)]
pub struct AuthManager {
jwt_config: JwtConfig,
oauth_config: Option<OAuthConfig>,
users: HashMap<String, UserInfo>,
sessions: HashMap<String, Session>,
api_keys: HashMap<String, ApiKeyInfo>,
refresh_tokens: HashMap<String, RefreshToken>,
auth_events: Vec<AuthEvent>,
max_events: usize,
token_expiry: Duration,
refresh_token_expiry: Duration,
session_timeout: Duration,
}
impl AuthManager {
pub fn new(jwt_config: JwtConfig) -> Self {
AuthManager {
jwt_config,
oauth_config: None,
users: HashMap::new(),
sessions: HashMap::new(),
api_keys: HashMap::new(),
refresh_tokens: HashMap::new(),
auth_events: Vec::new(),
max_events: 10000,
token_expiry: Duration::from_secs(3600), refresh_token_expiry: Duration::from_secs(86400 * 7), session_timeout: Duration::from_secs(3600), }
}
pub fn with_oauth(mut self, config: OAuthConfig) -> Self {
self.oauth_config = Some(config);
self
}
pub fn with_token_expiry(mut self, duration: Duration) -> Self {
self.token_expiry = duration;
self
}
pub fn with_refresh_token_expiry(mut self, duration: Duration) -> Self {
self.refresh_token_expiry = duration;
self
}
pub fn with_session_timeout(mut self, duration: Duration) -> Self {
self.session_timeout = duration;
self
}
pub fn register_user(&mut self, user_info: UserInfo) -> Result<()> {
if self.users.contains_key(&user_info.user_id) {
return Err(Error::InvalidInput(format!(
"User '{}' already exists",
user_info.user_id
)));
}
let user_id = user_info.user_id.clone();
self.users.insert(user_id.clone(), user_info);
self.log_event(AuthEvent {
timestamp: SystemTime::now(),
event_type: AuthEventType::UserRegistration,
user_id: Some(user_id),
tenant_id: None,
auth_method: AuthMethod::Password,
success: true,
ip_address: None,
user_agent: None,
error_message: None,
});
Ok(())
}
pub fn get_user(&self, user_id: &str) -> Option<&UserInfo> {
self.users.get(user_id)
}
pub fn update_user(&mut self, user_info: UserInfo) -> Result<()> {
if !self.users.contains_key(&user_info.user_id) {
return Err(Error::InvalidInput(format!(
"User '{}' not found",
user_info.user_id
)));
}
self.users.insert(user_info.user_id.clone(), user_info);
Ok(())
}
pub fn deactivate_user(&mut self, user_id: &str) -> Result<()> {
let user = self
.users
.get_mut(user_id)
.ok_or_else(|| Error::InvalidInput(format!("User '{}' not found", user_id)))?;
user.active = false;
self.sessions
.retain(|_, session| session.user_id != user_id);
Ok(())
}
pub fn authenticate_password(&mut self, email: &str, password: &str) -> Result<AuthResult> {
let user = self
.users
.values()
.find(|u| u.email == email)
.ok_or_else(|| Error::InvalidInput("Invalid credentials".to_string()))?;
if !user.active {
self.log_event(AuthEvent {
timestamp: SystemTime::now(),
event_type: AuthEventType::Login,
user_id: Some(user.user_id.clone()),
tenant_id: Some(user.tenant_id.clone()),
auth_method: AuthMethod::Password,
success: false,
ip_address: None,
user_agent: None,
error_message: Some("User account is deactivated".to_string()),
});
return Err(Error::InvalidOperation(
"User account is deactivated".to_string(),
));
}
if !user.verify_password(password) {
self.log_event(AuthEvent {
timestamp: SystemTime::now(),
event_type: AuthEventType::Login,
user_id: Some(user.user_id.clone()),
tenant_id: Some(user.tenant_id.clone()),
auth_method: AuthMethod::Password,
success: false,
ip_address: None,
user_agent: None,
error_message: Some("Invalid password".to_string()),
});
return Err(Error::InvalidInput("Invalid credentials".to_string()));
}
let user_id = user.user_id.clone();
let tenant_id = user.tenant_id.clone();
let permissions = user.permissions.clone();
if let Some(user_mut) = self.users.get_mut(&user_id) {
user_mut.last_login = Some(SystemTime::now());
}
let session =
Session::new(user_id.clone(), tenant_id.clone()).with_timeout(self.session_timeout);
let session_id = session.session_id.clone();
self.sessions.insert(session_id.clone(), session);
let expires_at = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
+ self.token_expiry.as_secs();
self.log_event(AuthEvent {
timestamp: SystemTime::now(),
event_type: AuthEventType::Login,
user_id: Some(user_id.clone()),
tenant_id: Some(tenant_id.clone()),
auth_method: AuthMethod::Password,
success: true,
ip_address: None,
user_agent: None,
error_message: None,
});
Ok(AuthResult {
user_id,
tenant_id,
permissions,
expires_at,
session_id: Some(session_id),
metadata: HashMap::new(),
})
}
pub fn generate_token(&self, user_id: &str) -> Result<String> {
let user = self
.users
.get(user_id)
.ok_or_else(|| Error::InvalidInput(format!("User '{}' not found", user_id)))?;
if !user.active {
return Err(Error::InvalidOperation(
"User account is deactivated".to_string(),
));
}
let claims = TokenClaims {
sub: user_id.to_string(),
tenant_id: user.tenant_id.clone(),
roles: user.roles.clone(),
permissions: user
.permissions
.iter()
.map(|p| format!("{:?}", p))
.collect(),
iat: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
exp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
+ self.token_expiry.as_secs(),
iss: self.jwt_config.issuer.clone(),
aud: self.jwt_config.audience.clone(),
jti: generate_token_id(),
};
encode_jwt(&claims, &self.jwt_config)
}
pub fn generate_refresh_token(&mut self, user_id: &str) -> Result<String> {
let user = self
.users
.get(user_id)
.ok_or_else(|| Error::InvalidInput(format!("User '{}' not found", user_id)))?;
if !user.active {
return Err(Error::InvalidOperation(
"User account is deactivated".to_string(),
));
}
let token_id = generate_token_id();
let refresh_token = RefreshToken {
token_id: token_id.clone(),
user_id: user_id.to_string(),
tenant_id: user.tenant_id.clone(),
created_at: SystemTime::now(),
expires_at: SystemTime::now() + self.refresh_token_expiry,
revoked: false,
};
self.refresh_tokens.insert(token_id.clone(), refresh_token);
Ok(token_id)
}
pub fn refresh_access_token(&mut self, refresh_token_id: &str) -> Result<String> {
let refresh_token = self
.refresh_tokens
.get(refresh_token_id)
.ok_or_else(|| Error::InvalidInput("Invalid refresh token".to_string()))?;
if refresh_token.revoked {
return Err(Error::InvalidOperation(
"Refresh token has been revoked".to_string(),
));
}
if refresh_token.expires_at < SystemTime::now() {
return Err(Error::InvalidOperation(
"Refresh token has expired".to_string(),
));
}
let user_id = refresh_token.user_id.clone();
self.log_event(AuthEvent {
timestamp: SystemTime::now(),
event_type: AuthEventType::TokenRefresh,
user_id: Some(user_id.clone()),
tenant_id: Some(refresh_token.tenant_id.clone()),
auth_method: AuthMethod::Jwt,
success: true,
ip_address: None,
user_agent: None,
error_message: None,
});
self.generate_token(&user_id)
}
pub fn validate_token(&mut self, token: &str) -> Result<AuthResult> {
let claims = decode_jwt(token, &self.jwt_config)?;
let (user_permissions, user_active) = {
let user = self
.users
.get(&claims.sub)
.ok_or_else(|| Error::InvalidInput("User not found".to_string()))?;
(user.permissions.clone(), user.active)
};
if !user_active {
return Err(Error::InvalidOperation(
"User account is deactivated".to_string(),
));
}
self.log_event(AuthEvent {
timestamp: SystemTime::now(),
event_type: AuthEventType::TokenValidation,
user_id: Some(claims.sub.clone()),
tenant_id: Some(claims.tenant_id.clone()),
auth_method: AuthMethod::Jwt,
success: true,
ip_address: None,
user_agent: None,
error_message: None,
});
Ok(AuthResult {
user_id: claims.sub,
tenant_id: claims.tenant_id,
permissions: user_permissions,
expires_at: claims.exp,
session_id: None,
metadata: HashMap::new(),
})
}
pub fn authenticate_api_key(&mut self, key: &str) -> Result<AuthResult> {
let (key_active, key_expires_at, key_user_id, key_tenant_id, key_permissions) = {
let api_key_info = self
.api_keys
.get(key)
.ok_or_else(|| Error::InvalidInput("Invalid API key".to_string()))?;
(
api_key_info.active,
api_key_info.expires_at,
api_key_info.user_id.clone(),
api_key_info.tenant_id.clone(),
api_key_info.permissions.clone(),
)
};
if !key_active {
return Err(Error::InvalidOperation(
"API key is deactivated".to_string(),
));
}
if let Some(expires_at) = key_expires_at {
if expires_at < SystemTime::now() {
return Err(Error::InvalidOperation("API key has expired".to_string()));
}
}
let user_active = {
let user = self
.users
.get(&key_user_id)
.ok_or_else(|| Error::InvalidInput("User not found".to_string()))?;
user.active
};
if !user_active {
return Err(Error::InvalidOperation(
"User account is deactivated".to_string(),
));
}
if let Some(key_info) = self.api_keys.get_mut(key) {
key_info.usage_count += 1;
key_info.last_used = Some(SystemTime::now());
}
self.log_event(AuthEvent {
timestamp: SystemTime::now(),
event_type: AuthEventType::ApiKeyUsage,
user_id: Some(key_user_id.clone()),
tenant_id: Some(key_tenant_id.clone()),
auth_method: AuthMethod::ApiKey,
success: true,
ip_address: None,
user_agent: None,
error_message: None,
});
let expires_at = key_expires_at
.map(|t| t.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs())
.unwrap_or(u64::MAX);
Ok(AuthResult {
user_id: key_user_id,
tenant_id: key_tenant_id,
permissions: key_permissions,
expires_at,
session_id: None,
metadata: HashMap::new(),
})
}
pub fn create_api_key(
&mut self,
user_id: &str,
name: &str,
permissions: Option<Vec<Permission>>,
) -> Result<String> {
let (user_tenant_id, user_permissions, user_active) = {
let user = self
.users
.get(user_id)
.ok_or_else(|| Error::InvalidInput(format!("User '{}' not found", user_id)))?;
(
user.tenant_id.clone(),
user.permissions.clone(),
user.active,
)
};
if !user_active {
return Err(Error::InvalidOperation(
"User account is deactivated".to_string(),
));
}
let api_key = generate_api_key();
let api_key_info = ApiKeyInfo {
key_id: generate_token_id(),
name: name.to_string(),
key_hash: hash_api_key(&api_key),
user_id: user_id.to_string(),
tenant_id: user_tenant_id,
permissions: permissions.unwrap_or(user_permissions),
created_at: SystemTime::now(),
expires_at: None,
last_used: None,
usage_count: 0,
active: true,
rate_limit: None,
rate_limit_counter: 0,
rate_limit_window_start: None,
ip_whitelist: Vec::new(),
metadata: HashMap::new(),
};
self.api_keys.insert(api_key.clone(), api_key_info);
if let Some(user_mut) = self.users.get_mut(user_id) {
user_mut.api_keys.push(api_key.clone());
}
Ok(api_key)
}
pub fn revoke_api_key(&mut self, key: &str) -> Result<()> {
let api_key_info = self
.api_keys
.get_mut(key)
.ok_or_else(|| Error::InvalidInput("API key not found".to_string()))?;
api_key_info.active = false;
if let Some(user) = self.users.get_mut(&api_key_info.user_id) {
user.api_keys.retain(|k| k != key);
}
Ok(())
}
pub fn revoke_refresh_token(&mut self, token_id: &str) -> Result<()> {
let refresh_token = self
.refresh_tokens
.get_mut(token_id)
.ok_or_else(|| Error::InvalidInput("Refresh token not found".to_string()))?;
refresh_token.revoked = true;
Ok(())
}
pub fn revoke_all_refresh_tokens(&mut self, user_id: &str) {
for token in self.refresh_tokens.values_mut() {
if token.user_id == user_id {
token.revoked = true;
}
}
}
pub fn get_session(&self, session_id: &str) -> Option<&Session> {
self.sessions.get(session_id)
}
pub fn validate_session(&mut self, session_id: &str) -> Result<&Session> {
let (is_expired, user_id, tenant_id) = {
let session = self
.sessions
.get(session_id)
.ok_or_else(|| Error::InvalidInput("Session not found".to_string()))?;
(
session.is_expired(),
session.user_id.clone(),
session.tenant_id.clone(),
)
};
if is_expired {
self.log_event(AuthEvent {
timestamp: SystemTime::now(),
event_type: AuthEventType::SessionExpired,
user_id: Some(user_id),
tenant_id: Some(tenant_id),
auth_method: AuthMethod::Password,
success: false,
ip_address: None,
user_agent: None,
error_message: Some("Session expired".to_string()),
});
return Err(Error::InvalidOperation("Session has expired".to_string()));
}
if let Some(session) = self.sessions.get_mut(session_id) {
session.refresh();
}
self.sessions
.get(session_id)
.ok_or_else(|| Error::InvalidInput("Session not found".to_string()))
}
pub fn logout(&mut self, session_id: &str) -> Result<()> {
let session = self
.sessions
.remove(session_id)
.ok_or_else(|| Error::InvalidInput("Session not found".to_string()))?;
self.log_event(AuthEvent {
timestamp: SystemTime::now(),
event_type: AuthEventType::Logout,
user_id: Some(session.user_id),
tenant_id: Some(session.tenant_id),
auth_method: AuthMethod::Password,
success: true,
ip_address: None,
user_agent: None,
error_message: None,
});
Ok(())
}
pub fn cleanup_expired(&mut self) {
self.sessions.retain(|_, session| !session.is_expired());
let now = SystemTime::now();
self.refresh_tokens
.retain(|_, token| token.expires_at > now && !token.revoked);
self.api_keys
.retain(|_, key| key.active && key.expires_at.map(|t| t > now).unwrap_or(true));
}
pub fn get_user_events(&self, user_id: &str) -> Vec<&AuthEvent> {
self.auth_events
.iter()
.filter(|e| e.user_id.as_ref().map(|id| id == user_id).unwrap_or(false))
.collect()
}
pub fn get_all_events(&self) -> &[AuthEvent] {
&self.auth_events
}
pub fn change_password(
&mut self,
user_id: &str,
old_password: &str,
new_password: &str,
) -> Result<()> {
let user = self
.users
.get(user_id)
.ok_or_else(|| Error::InvalidInput(format!("User '{}' not found", user_id)))?;
if !user.verify_password(old_password) {
self.log_event(AuthEvent {
timestamp: SystemTime::now(),
event_type: AuthEventType::PasswordChange,
user_id: Some(user_id.to_string()),
tenant_id: Some(user.tenant_id.clone()),
auth_method: AuthMethod::Password,
success: false,
ip_address: None,
user_agent: None,
error_message: Some("Invalid current password".to_string()),
});
return Err(Error::InvalidInput("Invalid current password".to_string()));
}
let tenant_id = user.tenant_id.clone();
if let Some(user_mut) = self.users.get_mut(user_id) {
user_mut.password_hash = Some(hash_password(new_password));
}
self.revoke_all_refresh_tokens(user_id);
self.log_event(AuthEvent {
timestamp: SystemTime::now(),
event_type: AuthEventType::PasswordChange,
user_id: Some(user_id.to_string()),
tenant_id: Some(tenant_id),
auth_method: AuthMethod::Password,
success: true,
ip_address: None,
user_agent: None,
error_message: None,
});
Ok(())
}
fn log_event(&mut self, event: AuthEvent) {
self.auth_events.push(event);
if self.auth_events.len() > self.max_events {
let excess = self.auth_events.len() - self.max_events;
self.auth_events.drain(0..excess);
}
}
}
impl Default for AuthManager {
fn default() -> Self {
Self::new(JwtConfig::default())
}
}
pub type SharedAuthManager = Arc<RwLock<AuthManager>>;
pub fn create_shared_auth_manager(config: JwtConfig) -> SharedAuthManager {
Arc::new(RwLock::new(AuthManager::new(config)))
}
#[derive(Debug, Clone)]
pub struct RefreshToken {
pub token_id: String,
pub user_id: String,
pub tenant_id: TenantId,
pub created_at: SystemTime,
pub expires_at: SystemTime,
pub revoked: bool,
}
fn generate_token_id() -> String {
use rand::Rng;
let mut bytes = [0u8; 32];
rand::rng().fill_bytes(&mut bytes);
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
fn generate_api_key() -> String {
use rand::Rng;
let mut bytes = [0u8; 32];
rand::rng().fill_bytes(&mut bytes);
format!(
"pk_{}",
bytes
.iter()
.map(|b| format!("{:02x}", b))
.collect::<String>()
)
}
fn hash_password(password: &str) -> String {
use pbkdf2::pbkdf2_hmac;
use rand::Rng;
use sha2::Sha256;
let mut salt = [0u8; 16];
rand::rng().fill_bytes(&mut salt);
let mut hash = [0u8; 32];
pbkdf2_hmac::<Sha256>(password.as_bytes(), &salt, 100_000, &mut hash);
format!(
"100000${}${}",
salt.iter()
.map(|b| format!("{:02x}", b))
.collect::<String>(),
hash.iter()
.map(|b| format!("{:02x}", b))
.collect::<String>()
)
}
fn verify_password(password: &str, stored_hash: &str) -> bool {
use pbkdf2::pbkdf2_hmac;
use sha2::Sha256;
let parts: Vec<&str> = stored_hash.split('$').collect();
if parts.len() != 3 {
return false;
}
let iterations: u32 = match parts[0].parse() {
Ok(i) => i,
Err(_) => return false,
};
let salt: Vec<u8> = match hex_decode(parts[1]) {
Some(s) => s,
None => return false,
};
let stored_hash_bytes: Vec<u8> = match hex_decode(parts[2]) {
Some(h) => h,
None => return false,
};
let mut computed_hash = vec![0u8; stored_hash_bytes.len()];
pbkdf2_hmac::<Sha256>(password.as_bytes(), &salt, iterations, &mut computed_hash);
computed_hash == stored_hash_bytes
}
fn hash_api_key(key: &str) -> String {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(key.as_bytes());
let result = hasher.finalize();
result.iter().map(|b| format!("{:02x}", b)).collect()
}
fn hex_decode(s: &str) -> Option<Vec<u8>> {
let mut bytes = Vec::with_capacity(s.len() / 2);
let mut chars = s.chars();
while let (Some(a), Some(b)) = (chars.next(), chars.next()) {
let high = a.to_digit(16)?;
let low = b.to_digit(16)?;
bytes.push((high * 16 + low) as u8);
}
if chars.next().is_some() {
return None;
}
Some(bytes)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_user_registration() {
let mut auth = AuthManager::default();
let user = UserInfo::new("user1", "user@example.com", "tenant_a")
.with_password("secret123")
.with_permission(Permission::Read)
.with_permission(Permission::Write);
auth.register_user(user).expect("operation should succeed");
assert!(auth.get_user("user1").is_some());
}
#[test]
fn test_password_authentication() {
let mut auth = AuthManager::default();
let user =
UserInfo::new("user1", "user@example.com", "tenant_a").with_password("secret123");
auth.register_user(user).expect("operation should succeed");
let result = auth.authenticate_password("user@example.com", "secret123");
assert!(result.is_ok());
let result = auth.authenticate_password("user@example.com", "wrong");
assert!(result.is_err());
let result = auth.authenticate_password("wrong@example.com", "secret123");
assert!(result.is_err());
}
#[test]
fn test_token_generation() {
let auth = AuthManager::default();
let mut auth = auth;
let user =
UserInfo::new("user1", "user@example.com", "tenant_a").with_password("secret123");
auth.register_user(user).expect("operation should succeed");
let token = auth
.generate_token("user1")
.expect("operation should succeed");
assert!(!token.is_empty());
let result = auth.validate_token(&token);
assert!(result.is_ok());
}
#[test]
fn test_api_key_authentication() {
let mut auth = AuthManager::default();
let user = UserInfo::new("user1", "user@example.com", "tenant_a")
.with_password("secret123")
.with_permission(Permission::Read);
auth.register_user(user).expect("operation should succeed");
let api_key = auth
.create_api_key("user1", "test-key", None)
.expect("operation should succeed");
assert!(api_key.starts_with("pk_"));
let result = auth.authenticate_api_key(&api_key);
assert!(result.is_ok());
let auth_result = result.expect("operation should succeed");
assert_eq!(auth_result.user_id, "user1");
assert_eq!(auth_result.tenant_id, "tenant_a");
}
#[test]
fn test_refresh_token() {
let mut auth = AuthManager::default();
let user =
UserInfo::new("user1", "user@example.com", "tenant_a").with_password("secret123");
auth.register_user(user).expect("operation should succeed");
let refresh_token = auth
.generate_refresh_token("user1")
.expect("operation should succeed");
let new_token = auth.refresh_access_token(&refresh_token);
assert!(new_token.is_ok());
auth.revoke_refresh_token(&refresh_token)
.expect("operation should succeed");
let result = auth.refresh_access_token(&refresh_token);
assert!(result.is_err());
}
#[test]
fn test_session_management() {
let mut auth = AuthManager::default();
let user =
UserInfo::new("user1", "user@example.com", "tenant_a").with_password("secret123");
auth.register_user(user).expect("operation should succeed");
let result = auth
.authenticate_password("user@example.com", "secret123")
.expect("operation should succeed");
let session_id = result.session_id.expect("operation should succeed");
assert!(auth.validate_session(&session_id).is_ok());
auth.logout(&session_id).expect("operation should succeed");
assert!(auth.validate_session(&session_id).is_err());
}
#[test]
fn test_password_change() {
let mut auth = AuthManager::default();
let user = UserInfo::new("user1", "user@example.com", "tenant_a").with_password("oldpass");
auth.register_user(user).expect("operation should succeed");
auth.change_password("user1", "oldpass", "newpass")
.expect("operation should succeed");
let result = auth.authenticate_password("user@example.com", "oldpass");
assert!(result.is_err());
let result = auth.authenticate_password("user@example.com", "newpass");
assert!(result.is_ok());
}
#[test]
fn test_user_deactivation() {
let mut auth = AuthManager::default();
let user =
UserInfo::new("user1", "user@example.com", "tenant_a").with_password("secret123");
auth.register_user(user).expect("operation should succeed");
assert!(auth
.authenticate_password("user@example.com", "secret123")
.is_ok());
auth.deactivate_user("user1")
.expect("operation should succeed");
let result = auth.authenticate_password("user@example.com", "secret123");
assert!(result.is_err());
}
#[test]
fn test_password_hashing() {
let password = "test_password_123";
let hash = hash_password(password);
let parts: Vec<&str> = hash.split('$').collect();
assert_eq!(parts.len(), 3);
assert!(verify_password(password, &hash));
assert!(!verify_password("wrong_password", &hash));
}
}