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