Skip to main content

haste_repository/pg/
user.rs

1use crate::{
2    admin::{Login, TenantAuthAdmin},
3    pg::{PGConnection, StoreError},
4    types::user::{
5        AuthMethod, CreateUser, LoginMethod, LoginResult, UpdateUser, User, UserRole,
6        UserSearchClauses,
7    },
8};
9use haste_fhir_operation_error::OperationOutcomeError;
10use haste_jwt::TenantId;
11use sqlx::{Acquire, Postgres, QueryBuilder};
12
13fn login<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
14    connection: Connection,
15    tenant: &'a TenantId,
16    method: &'a LoginMethod,
17) -> impl Future<Output = Result<LoginResult, OperationOutcomeError>> + Send + 'a {
18    async move {
19        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
20        match method {
21            LoginMethod::EmailPassword { email, password } => {
22                let user = sqlx::query_as!(
23                    User,
24                    r#"
25                  SELECT id, tenant as "tenant: TenantId", email, role as "role: UserRole", method as "method: AuthMethod", provider_id FROM users WHERE tenant = $1 AND method = $2 AND email = $3 AND password = crypt($4, password)
26                "#,
27                    tenant.as_ref(),
28                    AuthMethod::EmailPassword as AuthMethod,
29                    email,
30                    password
31                ).fetch_optional(&mut *conn).await.map_err(StoreError::from)?;
32
33                if let Some(user) = user {
34                    Ok(LoginResult::Success { user })
35                } else {
36                    Ok(LoginResult::Failure)
37                }
38            }
39            LoginMethod::OIDC {
40                email: _,
41                provider_id: _,
42            } => Ok(LoginResult::Failure),
43        }
44    }
45}
46
47impl Login for PGConnection {
48    async fn login(
49        &self,
50        tenant: &TenantId,
51        method: &LoginMethod,
52    ) -> Result<LoginResult, haste_fhir_operation_error::OperationOutcomeError> {
53        match &self {
54            PGConnection::Pool(pool, _) => {
55                let res = login(pool, tenant, method).await?;
56                Ok(res)
57            }
58            PGConnection::Transaction(tx, _) => {
59                let mut tx = tx.lock().await;
60
61                let res = login(&mut *tx, tenant, method).await?;
62                Ok(res)
63            }
64        }
65    }
66}
67
68fn create_user<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
69    connection: Connection,
70    tenant: &'a TenantId,
71    new_user: CreateUser,
72) -> impl Future<Output = Result<User, OperationOutcomeError>> + Send + 'a {
73    async move {
74        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
75
76        let mut query_builder = QueryBuilder::new(
77            r#"
78                INSERT INTO users(tenant, id, email, role, method, provider_id, password)
79            "#,
80        );
81
82        query_builder.push(" VALUES (");
83
84        let mut seperator = query_builder.separated(", ");
85
86        seperator
87            .push_bind(tenant.as_ref())
88            .push_bind(new_user.id)
89            .push_bind(new_user.email)
90            .push_bind(new_user.role as UserRole)
91            .push_bind(new_user.method as AuthMethod);
92
93        if let Some(provider_id) = new_user.provider_id {
94            seperator.push_bind(provider_id);
95        } else {
96            seperator.push_bind(None::<String>);
97        }
98
99        if let Some(password) = new_user.password {
100            seperator
101                .push("crypt(")
102                .push_bind_unseparated(password)
103                .push_unseparated(", gen_salt('bf'))");
104        } else {
105            seperator.push_bind(None::<String>);
106        }
107
108        query_builder.push(r#") RETURNING id, tenant, provider_id, email, role , method"#);
109
110        let query = query_builder.build_query_as();
111
112        let user = query
113            .fetch_one(&mut *conn)
114            .await
115            .map_err(StoreError::SQLXError)?;
116
117        Ok(user)
118    }
119}
120
121fn read_user<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
122    connection: Connection,
123    tenant: &'a TenantId,
124    id: &'a str,
125) -> impl Future<Output = Result<Option<User>, OperationOutcomeError>> + Send + 'a {
126    async move {
127        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
128        let user = sqlx::query_as!(
129            User,
130            r#"
131                SELECT id, tenant as "tenant: TenantId", provider_id, email, role as "role: UserRole", method as "method: AuthMethod"
132                FROM users
133                WHERE tenant = $1 AND id = $2
134            "#,
135            tenant.as_ref(),
136            id
137        ).fetch_optional(&mut *conn).await.map_err(StoreError::SQLXError)?;
138
139        Ok(user)
140    }
141}
142
143fn update_user<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
144    connection: Connection,
145    tenant: &'a TenantId,
146    model: UpdateUser,
147) -> impl Future<Output = Result<User, OperationOutcomeError>> + Send + 'a {
148    async move {
149        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
150        let mut query_builder = QueryBuilder::new(
151            r#"
152                UPDATE users SET 
153            "#,
154        );
155
156        let mut update_clauses = query_builder.separated(", ");
157
158        if let Some(provider_id) = model.provider_id {
159            update_clauses
160                .push(" provider_id = ")
161                .push_bind_unseparated(provider_id);
162        }
163
164        if let Some(email) = model.email.as_ref() {
165            update_clauses
166                .push(" email = ")
167                .push_bind_unseparated(email);
168        }
169
170        if let Some(role) = model.role.as_ref() {
171            update_clauses.push(" role = ").push_bind_unseparated(role);
172        }
173
174        if let Some(method) = model.method.as_ref() {
175            update_clauses
176                .push(" method = ")
177                .push_bind_unseparated(method);
178        }
179
180        if let Some(password) = model.password {
181            update_clauses
182                .push(" password = crypt(")
183                .push_bind_unseparated(password)
184                .push_unseparated(", gen_salt('bf'))");
185        }
186
187        update_clauses
188            .push(" tenant = ")
189            .push_bind_unseparated(tenant.as_ref());
190
191        query_builder.push(" WHERE id = ");
192        query_builder.push_bind(model.id);
193
194        query_builder.push(r#" RETURNING id, tenant, provider_id, email, role, method"#);
195
196        let query = query_builder.build_query_as();
197
198        let user = query
199            .fetch_one(&mut *conn)
200            .await
201            .map_err(StoreError::SQLXError)?;
202
203        Ok(user)
204    }
205}
206
207fn delete_user<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
208    connection: Connection,
209    tenant: &'a TenantId,
210    id: &'a str,
211) -> impl Future<Output = Result<(), OperationOutcomeError>> + Send + 'a {
212    async move {
213        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
214        let _user = sqlx::query_as!(
215            User,
216            r#"
217                DELETE FROM users
218                WHERE tenant = $1 AND id = $2
219                RETURNING id, tenant as "tenant: TenantId", provider_id, email, role as "role: UserRole", method as "method: AuthMethod"
220            "#,
221            tenant.as_ref(),
222            id
223        ).fetch_optional(&mut *conn).await.map_err(StoreError::SQLXError)?;
224
225        Ok(())
226    }
227}
228
229fn search_user<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
230    connection: Connection,
231    tenant: &'a TenantId,
232    clauses: &'a UserSearchClauses,
233) -> impl Future<Output = Result<Vec<User>, OperationOutcomeError>> + Send + 'a {
234    async move {
235        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
236        let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(
237            r#"SELECT id, tenant, email, role, method, provider_id FROM users WHERE  "#,
238        );
239
240        let mut seperator = query_builder.separated(" AND ");
241        seperator
242            .push(" tenant = ")
243            .push_bind_unseparated(tenant.as_ref());
244
245        if let Some(email) = clauses.email.as_ref() {
246            seperator.push(" email = ").push_bind_unseparated(email);
247        }
248
249        if let Some(role) = clauses.role.as_ref() {
250            seperator.push(" role = ").push_bind_unseparated(role);
251        }
252
253        if let Some(method) = clauses.method.as_ref() {
254            seperator.push(" method = ").push_bind_unseparated(method);
255        }
256
257        let query = query_builder.build_query_as();
258
259        let users: Vec<User> = query
260            .fetch_all(&mut *conn)
261            .await
262            .map_err(StoreError::from)?;
263
264        Ok(users)
265    }
266}
267
268impl<Key: AsRef<str> + Send + Sync>
269    TenantAuthAdmin<CreateUser, User, UserSearchClauses, UpdateUser, Key> for PGConnection
270{
271    async fn create(
272        &self,
273        tenant: &TenantId,
274        new_user: CreateUser,
275    ) -> Result<User, OperationOutcomeError> {
276        match self {
277            PGConnection::Pool(pool, _) => {
278                let res = create_user(pool, tenant, new_user).await?;
279                Ok(res)
280            }
281            PGConnection::Transaction(tx, _) => {
282                let mut tx = tx.lock().await;
283                let res = create_user(&mut *tx, tenant, new_user).await?;
284                Ok(res)
285            }
286        }
287    }
288
289    async fn read(
290        &self,
291        tenant: &TenantId,
292        id: &Key,
293    ) -> Result<Option<User>, OperationOutcomeError> {
294        match self {
295            PGConnection::Pool(pool, _) => {
296                let res = read_user(pool, tenant, id.as_ref()).await?;
297                Ok(res)
298            }
299            PGConnection::Transaction(tx, _) => {
300                let mut tx = tx.lock().await;
301                let res = read_user(&mut *tx, tenant, id.as_ref()).await?;
302                Ok(res)
303            }
304        }
305    }
306
307    async fn update(
308        &self,
309        tenant: &TenantId,
310        user: UpdateUser,
311    ) -> Result<User, OperationOutcomeError> {
312        match self {
313            PGConnection::Pool(pool, _) => {
314                let res = update_user(pool, &tenant, user).await?;
315                Ok(res)
316            }
317            PGConnection::Transaction(tx, _) => {
318                let mut tx = tx.lock().await;
319                let res = update_user(&mut *tx, &tenant, user).await?;
320                Ok(res)
321            }
322        }
323    }
324
325    async fn delete(&self, tenant: &TenantId, id: &Key) -> Result<(), OperationOutcomeError> {
326        match self {
327            PGConnection::Pool(pool, _) => {
328                let res = delete_user(pool, tenant, id.as_ref()).await?;
329                Ok(res)
330            }
331            PGConnection::Transaction(tx, _) => {
332                let mut tx = tx.lock().await;
333                let res = delete_user(&mut *tx, tenant, id.as_ref()).await?;
334                Ok(res)
335            }
336        }
337    }
338
339    async fn search(
340        &self,
341        tenant: &TenantId,
342        clauses: &UserSearchClauses,
343    ) -> Result<Vec<User>, OperationOutcomeError> {
344        match self {
345            PGConnection::Pool(pool, _) => {
346                let res = search_user(pool, tenant, clauses).await?;
347                Ok(res)
348            }
349            PGConnection::Transaction(tx, _) => {
350                let mut tx = tx.lock().await;
351                let res = search_user(&mut *tx, tenant, clauses).await?;
352                Ok(res)
353            }
354        }
355    }
356}