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