hodei_authz_postgres/
lib.rs

1//! PostgreSQL adapter for Hodei authorization framework
2//!
3//! This crate provides a PostgreSQL implementation of the `PolicyStore` trait.
4
5use async_trait::async_trait;
6use cedar_policy::{Policy, PolicySet};
7use hodei_authz::{PolicyStore, PolicyStoreError};
8use sqlx::{PgPool, Row};
9use uuid::Uuid;
10
11/// PostgreSQL implementation of PolicyStore
12pub struct PostgresPolicyStore {
13    pool: PgPool,
14}
15
16impl PostgresPolicyStore {
17    /// Create a new PostgreSQL policy store
18    pub fn new(pool: PgPool) -> Self {
19        Self { pool }
20    }
21    
22    /// Run database migrations
23    pub async fn migrate(&self) -> Result<(), sqlx::migrate::MigrateError> {
24        sqlx::migrate!("./migrations")
25            .run(&self.pool)
26            .await
27    }
28}
29
30#[async_trait]
31impl PolicyStore for PostgresPolicyStore {
32    async fn create_policy(&self, content: String) -> Result<String, PolicyStoreError> {
33        let policy_id = Uuid::new_v4().to_string();
34        
35        sqlx::query("INSERT INTO policies (id, content, created_at) VALUES ($1, $2, NOW())")
36            .bind(&policy_id)
37            .bind(&content)
38            .execute(&self.pool)
39            .await
40            .map_err(|e| PolicyStoreError::Database(e.to_string()))?;
41        
42        Ok(policy_id)
43    }
44    
45    async fn get_policy(&self, id: &str) -> Result<Option<String>, PolicyStoreError> {
46        let record = sqlx::query("SELECT content FROM policies WHERE id = $1")
47            .bind(id)
48            .fetch_optional(&self.pool)
49            .await
50            .map_err(|e| PolicyStoreError::Database(e.to_string()))?;
51        
52        Ok(record.map(|r| r.try_get("content").unwrap()))
53    }
54    
55    async fn list_policies(&self) -> Result<Vec<(String, String)>, PolicyStoreError> {
56        let records = sqlx::query("SELECT id, content FROM policies ORDER BY created_at")
57            .fetch_all(&self.pool)
58            .await
59            .map_err(|e| PolicyStoreError::Database(e.to_string()))?;
60        
61        let policies = records
62            .into_iter()
63            .map(|r| {
64                let id: String = r.try_get("id").unwrap();
65                let content: String = r.try_get("content").unwrap();
66                (id, content)
67            })
68            .collect();
69        
70        Ok(policies)
71    }
72    
73    async fn update_policy(&self, id: &str, content: String) -> Result<(), PolicyStoreError> {
74        let result = sqlx::query("UPDATE policies SET content = $1, updated_at = NOW() WHERE id = $2")
75            .bind(&content)
76            .bind(id)
77            .execute(&self.pool)
78            .await
79            .map_err(|e| PolicyStoreError::Database(e.to_string()))?;
80        
81        if result.rows_affected() == 0 {
82            return Err(PolicyStoreError::NotFound(id.to_string()));
83        }
84        
85        Ok(())
86    }
87    
88    async fn delete_policy(&self, id: &str) -> Result<(), PolicyStoreError> {
89        let result = sqlx::query("DELETE FROM policies WHERE id = $1")
90            .bind(id)
91            .execute(&self.pool)
92            .await
93            .map_err(|e| PolicyStoreError::Database(e.to_string()))?;
94        
95        if result.rows_affected() == 0 {
96            return Err(PolicyStoreError::NotFound(id.to_string()));
97        }
98        
99        Ok(())
100    }
101    
102    async fn load_all_policies(&self) -> Result<PolicySet, PolicyStoreError> {
103        let records = sqlx::query("SELECT id, content FROM policies")
104            .fetch_all(&self.pool)
105            .await
106            .map_err(|e| PolicyStoreError::Database(e.to_string()))?;
107        
108        let mut policies = Vec::new();
109        for record in records {
110            let db_id: String = record.try_get("id")
111                .map_err(|e| PolicyStoreError::Database(e.to_string()))?;
112            let content: String = record.try_get("content")
113                .map_err(|e| PolicyStoreError::Database(e.to_string()))?;
114            
115            let policy_id = cedar_policy::PolicyId::new(db_id.clone());
116            let policy = Policy::parse(Some(policy_id), content)
117                .map_err(|e| PolicyStoreError::Parse(e.to_string()))?;
118            policies.push(policy);
119        }
120        
121        PolicySet::from_policies(policies)
122            .map_err(|e| PolicyStoreError::Internal(e.to_string()))
123    }
124}