#[cfg(feature = "server")]
use async_trait::async_trait;
#[cfg(feature = "server")]
use sqlx::PgPool;
#[cfg(feature = "server")]
use std::sync::Arc;
#[cfg(feature = "server")]
use uuid::Uuid;
#[cfg(feature = "server")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PermissionResult {
Allowed,
Denied,
}
#[cfg(feature = "server")]
#[async_trait]
pub trait AuthorizationService: Send + Sync {
async fn has_permission(&self, user_id: &str, permission: &str) -> Result<bool, String>;
async fn has_role(&self, user_id: &str, role_name: &str) -> Result<bool, String>;
async fn can_access_resource(
&self,
user_id: &str,
resource_type: &str,
resource_id: Uuid,
action: &str,
) -> Result<bool, String>;
async fn get_user_permissions(&self, user_id: &str) -> Result<Vec<String>, String>;
}
#[cfg(feature = "server")]
pub struct PostgresAuthorizationService {
pool: Arc<PgPool>,
}
#[cfg(feature = "server")]
impl PostgresAuthorizationService {
pub fn new(pool: Arc<PgPool>) -> Self {
Self { pool }
}
}
#[cfg(feature = "server")]
#[async_trait]
impl AuthorizationService for PostgresAuthorizationService {
async fn has_permission(&self, user_id: &str, permission: &str) -> Result<bool, String> {
let user_uuid = Uuid::parse_str(user_id).map_err(|e| format!("Invalid user ID: {}", e))?;
let result = sqlx::query_scalar::<_, bool>(
r#"
SELECT EXISTS(
SELECT 1
FROM user_roles ur
JOIN role_permissions rp ON ur.role_id = rp.role_id
JOIN permissions p ON rp.permission_id = p.id
WHERE ur.user_id = $1
AND p.name = $2
AND (ur.expires_at IS NULL OR ur.expires_at > NOW())
)
"#,
)
.bind(user_uuid)
.bind(permission)
.fetch_one(self.pool.as_ref())
.await
.map_err(|e| format!("Database error: {}", e))?;
Ok(result)
}
async fn has_role(&self, user_id: &str, role_name: &str) -> Result<bool, String> {
let user_uuid = Uuid::parse_str(user_id).map_err(|e| format!("Invalid user ID: {}", e))?;
let result = sqlx::query_scalar::<_, bool>(
r#"
SELECT EXISTS(
SELECT 1
FROM user_roles ur
JOIN roles r ON ur.role_id = r.id
WHERE ur.user_id = $1
AND r.name = $2
AND (ur.expires_at IS NULL OR ur.expires_at > NOW())
)
"#,
)
.bind(user_uuid)
.bind(role_name)
.fetch_one(self.pool.as_ref())
.await
.map_err(|e| format!("Database error: {}", e))?;
Ok(result)
}
async fn can_access_resource(
&self,
user_id: &str,
resource_type: &str,
resource_id: Uuid,
action: &str,
) -> Result<bool, String> {
let user_uuid = Uuid::parse_str(user_id).map_err(|e| format!("Invalid user ID: {}", e))?;
match resource_type {
"workflow" => {
let result = sqlx::query_scalar::<_, bool>(
r#"
SELECT EXISTS(
SELECT 1
FROM user_workflow_permissions
WHERE workflow_id = $1
AND user_id = $2
AND permission_level >= CASE
WHEN $3 = 'read' THEN 1
WHEN $3 = 'write' THEN 2
WHEN $3 = 'admin' THEN 3
WHEN $3 = 'owner' THEN 4
ELSE 0
END
)
"#,
)
.bind(resource_id)
.bind(user_uuid)
.bind(action)
.fetch_one(self.pool.as_ref())
.await
.map_err(|e| format!("Database error: {}", e))?;
Ok(result)
}
"execution" => {
let result = sqlx::query_scalar::<_, bool>(
r#"
SELECT EXISTS(
SELECT 1
FROM execution_permissions
WHERE execution_id = $1
AND subject_type = 'user'
AND subject_id = $2
)
"#,
)
.bind(resource_id)
.bind(user_uuid)
.fetch_one(self.pool.as_ref())
.await
.map_err(|e| format!("Database error: {}", e))?;
Ok(result)
}
_ => {
let permission_name = format!("{}.{}", resource_type, action);
self.has_permission(user_id, &permission_name).await
}
}
}
async fn get_user_permissions(&self, user_id: &str) -> Result<Vec<String>, String> {
let user_uuid = Uuid::parse_str(user_id).map_err(|e| format!("Invalid user ID: {}", e))?;
let permissions = sqlx::query_scalar::<_, String>(
r#"
SELECT DISTINCT p.name
FROM user_roles ur
JOIN role_permissions rp ON ur.role_id = rp.role_id
JOIN permissions p ON rp.permission_id = p.id
WHERE ur.user_id = $1
AND (ur.expires_at IS NULL OR ur.expires_at > NOW())
ORDER BY p.name
"#,
)
.bind(user_uuid)
.fetch_all(self.pool.as_ref())
.await
.map_err(|e| format!("Database error: {}", e))?;
Ok(permissions)
}
}
#[cfg(feature = "server")]
pub async fn check_permission(
auth_service: &Arc<dyn AuthorizationService>,
user_id: &str,
permission: &str,
) -> Result<(), String> {
match auth_service.has_permission(user_id, permission).await {
Ok(true) => Ok(()),
Ok(false) => Err(format!("Permission denied: {}", permission)),
Err(e) => Err(format!("Authorization error: {}", e)),
}
}
#[cfg(feature = "server")]
pub async fn check_resource_access(
auth_service: &Arc<dyn AuthorizationService>,
user_id: &str,
resource_type: &str,
resource_id: Uuid,
action: &str,
) -> Result<(), String> {
match auth_service
.can_access_resource(user_id, resource_type, resource_id, action)
.await
{
Ok(true) => Ok(()),
Ok(false) => Err(format!(
"Access denied to {} {}",
resource_type, resource_id
)),
Err(e) => Err(format!("Authorization error: {}", e)),
}
}