use super::auth::{AuthManager, Credentials as ApiCredentials};
use crate::core::error::{Error, Result};
use crate::core::models::User;
use crate::transport::auth_traits::{
AuthProvider, AuthResponse, Credentials as TransportCredentials,
};
use async_trait::async_trait;
use secrecy::ExposeSecret;
use std::sync::Arc;
use tokio::sync::RwLock;
pub struct AuthManagerAdapter {
inner: Arc<RwLock<AuthManager>>,
http_provider: Arc<dyn AuthProvider>,
}
impl AuthManagerAdapter {
pub fn new(auth_manager: AuthManager, http_provider: Arc<dyn AuthProvider>) -> Self {
Self {
inner: Arc::new(RwLock::new(auth_manager)),
http_provider,
}
}
pub async fn inner(&self) -> tokio::sync::RwLockReadGuard<'_, AuthManager> {
self.inner.read().await
}
pub async fn inner_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, AuthManager> {
self.inner.write().await
}
#[allow(dead_code)]
fn convert_credentials(creds: &ApiCredentials) -> TransportCredentials {
match creds {
ApiCredentials::EmailPassword { email, password } => {
TransportCredentials::EmailPassword {
email: email.clone(),
password: password.expose_secret().to_string(),
}
}
ApiCredentials::ApiKey { key } => {
TransportCredentials::ApiKey(key.expose_secret().to_string())
}
}
}
}
#[async_trait]
impl AuthProvider for AuthManagerAdapter {
async fn authenticate(&self, credentials: &TransportCredentials) -> Result<AuthResponse> {
let response = self.http_provider.authenticate(credentials).await?;
let mut manager = self.inner.write().await;
manager.set_session_with_ttl(
response.session_token.clone(),
response.user.clone(),
response.expires_in,
);
Ok(response)
}
async fn refresh_session(&self, session_token: &str) -> Result<AuthResponse> {
let response = self.http_provider.refresh_session(session_token).await?;
let mut manager = self.inner.write().await;
manager.set_session_with_ttl(
response.session_token.clone(),
response.user.clone(),
response.expires_in,
);
Ok(response)
}
async fn validate_token(&self, session_token: &str) -> Result<bool> {
let manager = self.inner.read().await;
if let Some(current_token) = manager.session_token() {
if current_token != session_token {
return Ok(false);
}
if !manager.is_authenticated() {
return Ok(false);
}
} else {
return Ok(false);
}
drop(manager);
self.http_provider.validate_token(session_token).await
}
async fn logout(&self, session_token: &str) -> Result<()> {
self.http_provider.logout(session_token).await?;
let mut manager = self.inner.write().await;
manager.clear_session();
Ok(())
}
async fn get_user(&self, session_token: &str) -> Result<User> {
let manager = self.inner.read().await;
if let Some(user) = manager.current_user() {
if let Some(current_token) = manager.session_token() {
if current_token == session_token {
return Ok(user.clone());
}
}
}
drop(manager);
let user = self.http_provider.get_user(session_token).await?;
let mut manager = self.inner.write().await;
if let Some(current_token) = manager.session_token() {
if current_token == session_token {
manager.set_session(session_token.to_string(), user.clone());
}
}
Ok(user)
}
}
pub struct AuthManagerAdapterBuilder {
auth_manager: Option<AuthManager>,
http_provider: Option<Arc<dyn AuthProvider>>,
}
impl Default for AuthManagerAdapterBuilder {
fn default() -> Self {
Self::new()
}
}
impl AuthManagerAdapterBuilder {
pub fn new() -> Self {
Self {
auth_manager: None,
http_provider: None,
}
}
pub fn auth_manager(mut self, manager: AuthManager) -> Self {
self.auth_manager = Some(manager);
self
}
pub fn http_provider(mut self, provider: Arc<dyn AuthProvider>) -> Self {
self.http_provider = Some(provider);
self
}
pub fn build(self) -> Result<AuthManagerAdapter> {
let auth_manager = self.auth_manager.unwrap_or_default();
let http_provider = self
.http_provider
.ok_or_else(|| Error::Config("HTTP provider is required".to_string()))?;
Ok(AuthManagerAdapter::new(auth_manager, http_provider))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transport::auth_traits::MockAuthProvider;
#[tokio::test]
async fn test_adapter_authentication() {
let mock_provider = Arc::new(MockAuthProvider::default());
let auth_manager = AuthManager::new();
let adapter = AuthManagerAdapter::new(auth_manager, mock_provider);
let credentials = TransportCredentials::EmailPassword {
email: "test@example.com".to_string(),
password: "password123".to_string(),
};
let response = adapter.authenticate(&credentials).await.unwrap();
assert_eq!(response.session_token, "mock_session_token_123");
let manager = adapter.inner().await;
assert!(manager.is_authenticated());
assert_eq!(manager.session_token(), Some("mock_session_token_123"));
}
#[tokio::test]
async fn test_adapter_logout() {
let mock_provider = Arc::new(MockAuthProvider::default());
let mut auth_manager = AuthManager::new();
auth_manager.set_session(
"test_token".to_string(),
User {
id: crate::core::models::common::UserId(1),
email: "test@example.com".to_string(),
first_name: "Test".to_string(),
last_name: "User".to_string(),
is_active: true,
is_superuser: false,
is_qbnewb: false,
date_joined: chrono::Utc::now(),
last_login: None,
locale: None,
google_auth: false,
ldap_auth: false,
common_name: Some("Test User".to_string()),
group_ids: Vec::new(),
login_attributes: None,
user_group_memberships: Vec::new(),
},
);
let adapter = AuthManagerAdapter::new(auth_manager, mock_provider);
{
let manager = adapter.inner().await;
assert!(manager.is_authenticated());
}
adapter.logout("test_token").await.unwrap();
{
let manager = adapter.inner().await;
assert!(!manager.is_authenticated());
assert!(manager.session_token().is_none());
}
}
}