hodei_authz_postgres/
lib.rs1use async_trait::async_trait;
6use cedar_policy::{Policy, PolicySet};
7use hodei_authz::{PolicyStore, PolicyStoreError};
8use sqlx::{PgPool, Row};
9use uuid::Uuid;
10
11pub struct PostgresPolicyStore {
13 pool: PgPool,
14}
15
16impl PostgresPolicyStore {
17 pub fn new(pool: PgPool) -> Self {
19 Self { pool }
20 }
21
22 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}