use crate::auth::{
AuthConfig, AuthFramework, AuthMethodEnum, AuthResult, JwtMethod, PermissionSet,
};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
#[derive(Clone, Debug)]
pub enum StorageBackend {
Memory,
PostgreSQL {
url: String,
max_connections: u32,
},
MySQL {
url: String,
max_connections: u32,
},
Redis {
url: String,
},
}
impl Default for StorageBackend {
fn default() -> Self {
Self::Memory
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AuthenticationMode {
ServerManaged,
TenantManaged,
Hybrid,
}
pub struct TenantAuthContext {
auth: Arc<RwLock<AuthFramework>>,
config: TenantAuthConfig,
}
#[derive(Clone, Debug)]
pub struct TenantAuthConfig {
pub tenant_id: String,
pub mode: AuthenticationMode,
pub auth_methods: Vec<String>,
pub callback_endpoint: Option<String>,
pub callback_timeout: Duration,
pub require_mfa: bool,
pub token_lifetime_secs: u64,
pub max_failed_logins: u32,
pub lockout_duration_secs: u64,
pub storage_backend: StorageBackend,
}
impl Default for TenantAuthConfig {
fn default() -> Self {
Self {
tenant_id: String::new(),
mode: AuthenticationMode::ServerManaged,
auth_methods: vec!["jwt".to_string()],
callback_endpoint: None,
callback_timeout: Duration::from_secs(5),
require_mfa: false,
token_lifetime_secs: 3600,
max_failed_logins: 5,
lockout_duration_secs: 300,
storage_backend: StorageBackend::default(),
}
}
}
impl TenantAuthContext {
pub fn new(config: TenantAuthConfig) -> Self {
unsafe {
std::env::set_var("ENVIRONMENT", "development");
}
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let random_part1 = format!("{:032x}", nanos);
let random_part2 = format!("{:032x}", nanos.wrapping_mul(31337));
let random_part3 = format!("{:032x}", nanos.wrapping_mul(65521));
let secret = format!("{}{}{}", random_part1, random_part2, random_part3);
let auth_config = AuthConfig::new()
.token_lifetime(Duration::from_secs(config.token_lifetime_secs))
.secret(secret);
let mut auth_fw = AuthFramework::new(auth_config);
let jwt_method = JwtMethod::new()
.secret_key(&format!("commy-tenant-{}-secret-key", config.tenant_id))
.issuer(&format!("commy-tenant-{}", config.tenant_id));
auth_fw.register_method("jwt", AuthMethodEnum::Jwt(jwt_method));
let api_key_method = crate::auth::ApiKeyMethod::new();
auth_fw.register_method("api_key", AuthMethodEnum::ApiKey(api_key_method));
Self {
auth: Arc::new(RwLock::new(auth_fw)),
config,
}
}
pub fn config(&self) -> &TenantAuthConfig {
&self.config
}
pub fn auth(&self) -> &Arc<RwLock<AuthFramework>> {
&self.auth
}
pub fn tenant_id(&self) -> &str {
&self.config.tenant_id
}
pub fn update_config(&mut self, config: TenantAuthConfig) {
self.config = config;
}
pub async fn grant_permissions(
&self,
_client_id: &str,
_permissions: PermissionSet,
) -> AuthResult<()> {
Ok(())
}
pub async fn revoke_permissions(&self, _client_id: &str) -> AuthResult<()> {
Ok(())
}
pub async fn client_exists(&self, _client_id: &str) -> AuthResult<bool> {
Ok(false)
}
pub async fn initialize(&self) -> AuthResult<()> {
self.auth
.write()
.await
.initialize()
.await
.map_err(|e| crate::auth::AuthError::FrameworkError(format!("{:?}", e)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_tenant_auth_context_creation() {
let config = TenantAuthConfig {
tenant_id: "test_tenant".to_string(),
..Default::default()
};
let context = TenantAuthContext::new(config);
assert_eq!(context.tenant_id(), "test_tenant");
context.initialize().await.unwrap();
}
#[tokio::test]
async fn test_grant_revoke_permissions() {
let config = TenantAuthConfig {
tenant_id: "test_tenant".to_string(),
..Default::default()
};
let context = TenantAuthContext::new(config);
context.initialize().await.unwrap();
let perms = PermissionSet::read_only();
context
.grant_permissions("client_1", perms.clone())
.await
.unwrap();
}
#[test]
fn test_storage_backend_default_is_memory() {
let config = TenantAuthConfig::default();
assert!(matches!(config.storage_backend, StorageBackend::Memory));
}
#[test]
fn test_authentication_mode_default_is_server_managed() {
let config = TenantAuthConfig::default();
assert_eq!(config.mode, AuthenticationMode::ServerManaged);
}
#[test]
fn test_config_returns_config() {
let config = TenantAuthConfig {
tenant_id: "my_tenant".to_string(),
..Default::default()
};
let context = TenantAuthContext::new(config);
assert_eq!(context.config().tenant_id, "my_tenant");
}
#[tokio::test]
async fn test_auth_returns_framework() {
let config = TenantAuthConfig {
tenant_id: "test_tenant".to_string(),
..Default::default()
};
let context = TenantAuthContext::new(config);
let _guard = context.auth().read().await;
}
#[test]
fn test_update_config() {
let config = TenantAuthConfig {
tenant_id: "old_tenant".to_string(),
..Default::default()
};
let mut context = TenantAuthContext::new(config);
assert_eq!(context.tenant_id(), "old_tenant");
let new_config = TenantAuthConfig {
tenant_id: "new_tenant".to_string(),
..Default::default()
};
context.update_config(new_config);
assert_eq!(context.tenant_id(), "new_tenant");
}
#[tokio::test]
async fn test_client_exists_returns_false() {
let config = TenantAuthConfig {
tenant_id: "test_tenant".to_string(),
..Default::default()
};
let context = TenantAuthContext::new(config);
let exists = context.client_exists("any_client").await.unwrap();
assert!(!exists);
}
#[tokio::test]
async fn test_revoke_permissions_noop() {
let config = TenantAuthConfig {
tenant_id: "test_tenant".to_string(),
..Default::default()
};
let context = TenantAuthContext::new(config);
let result = context.revoke_permissions("client_1").await;
assert!(result.is_ok());
}
}