#![ allow( clippy::missing_inline_in_public_items, clippy::unused_async ) ]
mod private
{
use crate::
{
environment ::{ OpenaiEnvironmentImpl },
secret ::Secret,
error ::OpenAIError,
client ::Client,
};
use core::time::Duration;
use std::
{
collections ::HashMap,
time ::Instant,
sync ::{ Arc, Mutex, RwLock },
};
use serde::{ Deserialize, Serialize };
use error_tools::untyped::Result;
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct OAuthTokenResponse
{
pub access_token : String,
pub token_type : String,
pub expires_in : u64,
pub refresh_token : Option< String >,
pub scope : Option< String >,
}
#[ derive( Debug, Clone ) ]
pub struct MultiTenantConfig
{
pub primary_org_id : String,
pub secondary_org_id : Option< String >,
pub tenant_api_keys : HashMap< String, String >,
pub tenant_rate_limits : HashMap< String, u32 >,
pub isolation_enforced : bool,
}
#[ derive( Debug, Clone ) ]
pub struct AuthSession
{
pub session_id : String,
pub created_at : Instant,
pub last_accessed : Instant,
pub timeout : Duration,
pub is_expired : bool,
pub tenant_id : Option< String >,
}
impl AuthSession
{
#[ must_use ]
pub fn new(session_id : String, timeout : Duration) -> Self
{
let now = Instant::now();
Self
{
session_id,
created_at : now,
last_accessed : now,
timeout,
is_expired : false,
tenant_id : None,
}
}
#[ must_use ]
pub fn is_valid(&self) -> bool
{
!self.is_expired && self.last_accessed.elapsed() < self.timeout
}
pub fn refresh(&mut self) -> bool
{
if self.is_expired
{
return false;
}
self.last_accessed = Instant::now();
self.is_valid()
}
pub fn expire(&mut self)
{
self.is_expired = true;
}
}
#[ derive( Debug, Clone ) ]
pub struct AuthAuditEntry
{
pub timestamp : Instant,
pub event_type : String,
pub success : bool,
pub client_id : String,
pub ip_address : Option< String >,
pub context : HashMap< String, String >,
}
#[ derive( Debug, Clone ) ]
pub struct AdvancedAuthConfig
{
pub primary_api_key : String,
pub secondary_api_key : Option< String >,
pub oauth_access_token : Option< String >,
pub oauth_refresh_token : Option< String >,
pub token_expires_at : Option< u64 >,
pub organization_context : Option< String >,
pub project_context : Option< String >,
pub audit_trail_enabled : bool,
pub max_auth_retries : u32,
pub auth_timeout : Duration,
pub session_management_enabled : bool,
pub default_session_timeout : Duration,
}
impl Default for AdvancedAuthConfig
{
fn default() -> Self
{
Self
{
primary_api_key : String::new(),
secondary_api_key : None,
oauth_access_token : None,
oauth_refresh_token : None,
token_expires_at : None,
organization_context : None,
project_context : None,
audit_trail_enabled : false,
max_auth_retries : 3,
auth_timeout : Duration::from_secs(30),
session_management_enabled : false,
default_session_timeout : Duration::from_secs(3600), }
}
}
pub struct AdvancedAuthManager
{
config : AdvancedAuthConfig,
multi_tenant_config : Option< MultiTenantConfig >,
sessions : Arc< RwLock< HashMap< String, AuthSession > > >,
audit_log : Arc< Mutex< Vec< AuthAuditEntry > > >,
token_refresh_fn : Option< Box< dyn Fn() -> Result< OAuthTokenResponse > + Send + Sync > >,
}
impl AdvancedAuthManager
{
#[ must_use ]
pub fn new(config : AdvancedAuthConfig) -> Self
{
Self
{
config,
multi_tenant_config : None,
sessions : Arc::new(RwLock::new(HashMap::new())),
audit_log : Arc::new(Mutex::new(Vec::new())),
token_refresh_fn : None,
}
}
#[ must_use ]
pub fn with_multi_tenant_config(mut self, config : MultiTenantConfig) -> Self
{
self.multi_tenant_config = Some(config);
self
}
#[ must_use ]
pub fn with_oauth_refresh< F >(mut self, refresh_fn : F) -> Self
where
F: Fn() -> Result< OAuthTokenResponse > + Send + Sync + 'static,
{
self.token_refresh_fn = Some( Box::new( refresh_fn ) );
self
}
pub async fn create_oauth_client(&self) -> Result< Client< OpenaiEnvironmentImpl > >
{
if let Some(expires_at) = self.config.token_expires_at
{
let current_time = u64::try_from(chrono::Utc::now().timestamp()).unwrap_or(0);
if current_time >= expires_at
{
if let Some(ref refresh_fn) = self.token_refresh_fn
{
match refresh_fn()
{
Ok(new_token) =>
{
self.log_auth_event("oauth_token_refresh", true, "system", None, HashMap::new()).await;
return self.create_client_with_token(&new_token.access_token).await;
},
Err(e) =>
{
self.log_auth_event("oauth_token_refresh", false, "system", None,
[("error".to_string(), e.to_string())].iter().cloned().collect()).await;
return Err(e);
}
}
}
return Err(error_tools::Error::from(OpenAIError::InvalidArgument("OAuth token expired and no refresh function provided".to_string())));
}
}
if let Some(ref token) = self.config.oauth_access_token
{
self.create_client_with_token(token).await
}
else
{
Err(error_tools::Error::from(OpenAIError::InvalidArgument("No OAuth token available".to_string())))
}
}
pub async fn create_tenant_client(&self, tenant_id : &str) -> Result< Client< OpenaiEnvironmentImpl > >
{
let multi_tenant = self.multi_tenant_config.as_ref()
.ok_or_else(|| error_tools::Error::from(OpenAIError::InvalidArgument("Multi-tenant configuration not enabled".to_string())))?;
if multi_tenant.isolation_enforced
{
self.verify_tenant_isolation(tenant_id).await?;
}
let tenant_api_key = multi_tenant.tenant_api_keys.get(tenant_id)
.ok_or_else(|| error_tools::Error::from(OpenAIError::InvalidArgument(format!("No API key configured for tenant : {tenant_id}"))))?;
let secret = Secret::new(tenant_api_key.clone())?;
let environment = OpenaiEnvironmentImpl::build(
secret,
Some(tenant_id.to_string()),
None, crate ::environment::OpenAIRecommended::base_url().to_string(),
crate ::environment::OpenAIRecommended::realtime_base_url().to_string(),
)?;
self.log_auth_event("tenant_client_created", true, tenant_id, None, HashMap::new()).await;
Client::build(environment)
}
pub async fn create_failover_client(&self) -> Result< Client< OpenaiEnvironmentImpl > >
{
let primary_result = self.create_client_with_key(&self.config.primary_api_key).await;
match primary_result
{
Ok(client) =>
{
self.log_auth_event("primary_auth_success", true, "system", None, HashMap::new()).await;
Ok(client)
},
Err(primary_error) =>
{
self.log_auth_event("primary_auth_failure", false, "system", None,
[("error".to_string(), primary_error.to_string())].iter().cloned().collect()).await;
if let Some(ref secondary_key) = self.config.secondary_api_key
{
match self.create_client_with_key(secondary_key).await
{
Ok(client) =>
{
self.log_auth_event("failover_auth_success", true, "system", None, HashMap::new()).await;
return Ok(client);
},
Err(secondary_error) =>
{
self.log_auth_event("failover_auth_failure", false, "system", None,
[("error".to_string(), secondary_error.to_string())].iter().cloned().collect()).await;
return Err(secondary_error);
}
}
}
Err(primary_error)
}
}
}
pub async fn create_session_client(&self, session_id : &str) -> Result< Client< OpenaiEnvironmentImpl > >
{
if !self.config.session_management_enabled
{
return Err(error_tools::Error::from(OpenAIError::InvalidArgument("Session management not enabled".to_string())));
}
{
let session_valid = {
let mut sessions = self.sessions.write().unwrap();
if let Some(session) = sessions.get_mut(session_id)
{
if session.is_valid()
{
session.refresh();
true
}
else
{
sessions.remove(session_id);
false
}
}
else
{
let session = AuthSession::new(session_id.to_string(), self.config.default_session_timeout);
sessions.insert(session_id.to_string(), session);
true
}
};
if session_valid
{
if self.sessions.read().unwrap().contains_key(session_id)
{
self.log_auth_event("session_refreshed", true, session_id, None, HashMap::new()).await;
}
else
{
self.log_auth_event("session_created", true, session_id, None, HashMap::new()).await;
}
}
else
{
self.log_auth_event("session_expired", false, session_id, None, HashMap::new()).await;
return Err(error_tools::Error::from(OpenAIError::InvalidArgument("Session expired".to_string())));
}
}
self.create_client_with_key(&self.config.primary_api_key).await
}
pub async fn cleanup_expired_sessions(&self) -> usize
{
if !self.config.session_management_enabled
{
return 0;
}
let mut sessions = self.sessions.write().unwrap();
let initial_count = sessions.len();
sessions.retain(|session_id, session| {
let keep = session.is_valid();
if !keep
{
tokio ::spawn({
let audit_log = Arc::clone(&self.audit_log);
let session_id = session_id.clone();
async move {
let entry = AuthAuditEntry
{
timestamp : Instant::now(),
event_type : "session_cleanup".to_string(),
success : true,
client_id : session_id,
ip_address : None,
context : HashMap::new(),
};
audit_log.lock().unwrap().push(entry);
}
});
}
keep
});
initial_count - sessions.len()
}
pub async fn get_performance_metrics(&self) -> AuthPerformanceMetrics
{
let audit_log = self.audit_log.lock().unwrap();
let total_events = audit_log.len();
let successful_events = audit_log.iter().filter(|e| e.success).count();
let success_rate = if total_events > 0 { successful_events as f64 / total_events as f64 } else { 0.0 };
let recent_events : Vec< _ > = audit_log.iter().rev().take( 100 ).collect();
let recent_successful = recent_events.iter().filter(|e| e.success).count();
let recent_success_rate = if recent_events.is_empty() { 0.0 } else { recent_successful as f64 / recent_events.len() as f64 };
AuthPerformanceMetrics
{
total_auth_attempts : total_events,
successful_auth_attempts : successful_events,
overall_success_rate : success_rate,
recent_success_rate,
active_sessions : self.sessions.read().unwrap().len(),
cached_environments : 0, }
}
async fn create_client_with_key(&self, api_key : &str) -> Result< Client< OpenaiEnvironmentImpl > >
{
let secret = Secret::new(api_key.to_string())?;
let environment = OpenaiEnvironmentImpl::build(
secret,
self.config.organization_context.clone(),
self.config.project_context.clone(),
crate ::environment::OpenAIRecommended::base_url().to_string(),
crate ::environment::OpenAIRecommended::realtime_base_url().to_string(),
)?;
Client::build(environment)
}
async fn create_client_with_token(&self, token : &str) -> Result< Client< OpenaiEnvironmentImpl > >
{
self.create_client_with_key(token).await
}
async fn verify_tenant_isolation(&self, tenant_id : &str) -> Result< () >
{
let multi_tenant = self.multi_tenant_config.as_ref().unwrap();
if !multi_tenant.tenant_api_keys.contains_key(tenant_id)
{
return Err(error_tools::Error::from(OpenAIError::InvalidArgument(format!("Tenant {tenant_id} not authorized"))));
}
Ok(())
}
async fn log_auth_event(
&self,
event_type : &str,
success : bool,
client_id : &str,
ip_address : Option< String >,
context : HashMap< String, String >,
)
{
if !self.config.audit_trail_enabled
{
return;
}
let entry = AuthAuditEntry
{
timestamp : Instant::now(),
event_type : event_type.to_string(),
success,
client_id : client_id.to_string(),
ip_address,
context,
};
self.audit_log.lock().unwrap().push(entry);
}
}
impl core::fmt::Debug for AdvancedAuthManager
{
fn fmt(&self, f : &mut core::fmt::Formatter< '_ >) -> core::fmt::Result
{
f.debug_struct("AdvancedAuthManager")
.field("config", &self.config)
.field("multi_tenant_config", &self.multi_tenant_config)
.field("sessions_count", &self.sessions.read().unwrap().len())
.field("audit_log_entries", &self.audit_log.lock().unwrap().len())
.field("has_token_refresh_fn", &self.token_refresh_fn.is_some())
.finish()
}
}
#[ derive( Debug, Clone ) ]
pub struct AuthPerformanceMetrics
{
pub total_auth_attempts : usize,
pub successful_auth_attempts : usize,
pub overall_success_rate : f64,
pub recent_success_rate : f64,
pub active_sessions : usize,
pub cached_environments : usize,
}
static GLOBAL_AUTH_MANAGER: std::sync::OnceLock< std::sync::Mutex< Option< AdvancedAuthManager > > > = std::sync::OnceLock::new();
pub fn initialize_advanced_auth(config : AdvancedAuthConfig) -> Result< () >
{
let manager = AdvancedAuthManager::new(config);
GLOBAL_AUTH_MANAGER.get_or_init(|| std::sync::Mutex::new(Some(manager)));
Ok(())
}
pub fn get_advanced_auth_manager() -> Result< std::sync::MutexGuard< 'static, Option< AdvancedAuthManager > > >
{
GLOBAL_AUTH_MANAGER.get_or_init(|| std::sync::Mutex::new(None))
.lock()
.map_err(|e| error_tools::Error::from(OpenAIError::Internal(format!("Failed to lock auth manager : {e}"))))
}
pub async fn create_oauth_client() -> Result< Client< OpenaiEnvironmentImpl > >
{
let config = {
let manager_guard = get_advanced_auth_manager()?;
let manager = manager_guard.as_ref()
.ok_or_else(|| error_tools::Error::from(OpenAIError::InvalidArgument("Advanced auth manager not initialized".to_string())))?;
manager.config.clone()
};
let temp_manager = AdvancedAuthManager::new(config);
temp_manager.create_oauth_client().await
}
pub async fn create_tenant_client(tenant_id : &str) -> Result< Client< OpenaiEnvironmentImpl > >
{
let (config, multi_tenant_config) = {
let manager_guard = get_advanced_auth_manager()?;
let manager = manager_guard.as_ref()
.ok_or_else(|| error_tools::Error::from(OpenAIError::InvalidArgument("Advanced auth manager not initialized".to_string())))?;
(manager.config.clone(), manager.multi_tenant_config.clone())
};
let mut temp_manager = AdvancedAuthManager::new(config);
if let Some(mt_config) = multi_tenant_config
{
temp_manager = temp_manager.with_multi_tenant_config(mt_config);
}
temp_manager.create_tenant_client(tenant_id).await
}
pub async fn create_failover_client() -> Result< Client< OpenaiEnvironmentImpl > >
{
let config = {
let manager_guard = get_advanced_auth_manager()?;
let manager = manager_guard.as_ref()
.ok_or_else(|| error_tools::Error::from(OpenAIError::InvalidArgument("Advanced auth manager not initialized".to_string())))?;
manager.config.clone()
};
let temp_manager = AdvancedAuthManager::new(config);
temp_manager.create_failover_client().await
}
#[ cfg( test ) ]
mod tests
{
use super::*;
#[ test ]
fn test_advanced_auth_config_default()
{
let config = AdvancedAuthConfig::default();
assert_eq!(config.max_auth_retries, 3);
assert_eq!(config.auth_timeout, Duration::from_secs(30));
assert!(!config.audit_trail_enabled);
assert!(!config.session_management_enabled);
}
#[ test ]
fn test_auth_session_creation()
{
let session = AuthSession::new("test_session".to_string(), Duration::from_secs(60));
assert_eq!(session.session_id, "test_session");
assert!(session.is_valid());
assert!(!session.is_expired);
}
#[ test ]
fn test_auth_session_expiration()
{
let mut session = AuthSession::new("test_session".to_string(), Duration::from_millis(1));
std ::thread::sleep(Duration::from_millis(2));
assert!(!session.is_valid());
session.expire();
assert!(session.is_expired);
assert!(!session.refresh());
}
#[ tokio::test ]
async fn test_advanced_auth_manager_creation()
{
let config = AdvancedAuthConfig::default();
let manager = AdvancedAuthManager::new(config);
let metrics = manager.get_performance_metrics().await;
assert_eq!(metrics.total_auth_attempts, 0);
assert_eq!(metrics.active_sessions, 0);
assert_eq!(metrics.cached_environments, 0);
}
#[ tokio::test ]
async fn test_multi_tenant_config()
{
let mut tenant_keys = HashMap::new();
tenant_keys.insert("tenant_a".to_string(), "sk-tenant-a-key".to_string());
let multi_tenant_config = MultiTenantConfig
{
primary_org_id : "org_123".to_string(),
secondary_org_id : None,
tenant_api_keys : tenant_keys,
tenant_rate_limits : HashMap::new(),
isolation_enforced : true,
};
let config = AdvancedAuthConfig::default();
let manager = AdvancedAuthManager::new(config).with_multi_tenant_config(multi_tenant_config);
assert!(manager.multi_tenant_config.is_some());
}
}
}
crate ::mod_interface!
{
orphan use OAuthTokenResponse;
orphan use MultiTenantConfig;
orphan use AuthSession;
orphan use AuthAuditEntry;
orphan use AdvancedAuthConfig;
orphan use AdvancedAuthManager;
orphan use AuthPerformanceMetrics;
orphan use initialize_advanced_auth;
orphan use get_advanced_auth_manager;
orphan use create_oauth_client;
orphan use create_tenant_client;
orphan use create_failover_client;
}