1use crate::{
2 admin::TenantModelAdmin,
3 pg::{PGConnection, StoreError},
4 types::mfa::{
5 MFAKey, UserMFACredential, UserMFACredentialCreate, UserMFACredentialUpdate,
6 UserMFASearchClaims,
7 },
8};
9use haste_fhir_model::r4::generated::terminology::IssueType;
10use haste_fhir_operation_error::OperationOutcomeError;
11use haste_jwt::TenantId;
12use sqlx::{PgExecutor, QueryBuilder};
13
14async fn create_user_mfa_credential<'a, 'e, E>(
15 executor: E,
16 tenant: &'a TenantId,
17 new_mfa_credentials: UserMFACredentialCreate,
18) -> Result<UserMFACredential, OperationOutcomeError>
19where
20 E: PgExecutor<'e>,
21{
22 let type_: &str = new_mfa_credentials.credential_type.into();
23 let totp_algorithm = new_mfa_credentials
24 .totp_algorithm
25 .unwrap_or_else(|| "SHA1".to_string());
26
27 let user_mfa_credential = sqlx::query_as::<_, UserMFACredential>(
28 r"
29 INSERT INTO user_mfa_credential (tenant, user_id, credential_type, secret_ciphertext, secret_nonce, key_id, totp_algorithm, totp_digits, totp_period, totp_skew)
30 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
31 RETURNING
32 id::TEXT,
33 tenant,
34 user_id,
35 credential_type,
36 secret_ciphertext,
37 secret_nonce,
38 key_id,
39 totp_algorithm,
40 totp_digits,
41 totp_period,
42 totp_skew,
43 created_at,
44 is_active
45 ",
46 )
47 .bind(tenant.as_ref())
48 .bind(new_mfa_credentials.user_id.as_ref())
49 .bind(type_)
50 .bind(new_mfa_credentials.secret_ciphertext)
51 .bind(new_mfa_credentials.secret_nonce)
52 .bind(new_mfa_credentials.key_id)
53 .bind(totp_algorithm)
54 .bind(new_mfa_credentials.totp_digits.unwrap_or(6))
55 .bind(new_mfa_credentials.totp_period.unwrap_or(30))
56 .bind(new_mfa_credentials.totp_skew.unwrap_or(1))
57 .fetch_one(executor)
58 .await
59 .map_err(StoreError::SQLXError)?;
60
61 Ok(user_mfa_credential)
62}
63
64async fn read_user_mfa<'a, 'e, E>(
65 executor: E,
66 tenant: &'a TenantId,
67 key: &'a MFAKey,
68) -> Result<Option<UserMFACredential>, OperationOutcomeError>
69where
70 E: PgExecutor<'e>,
71{
72 let user_mfa = sqlx::query_as::<_, UserMFACredential>(
73 r"
74 SELECT
75 id::TEXT,
76 tenant,
77 user_id,
78 credential_type,
79 secret_ciphertext,
80 secret_nonce,
81 key_id,
82 totp_algorithm,
83 totp_digits,
84 totp_period,
85 totp_skew,
86 created_at,
87 is_active
88 FROM user_mfa_credential
89 WHERE tenant = $1 AND id::text = $2 AND user_id = $3
90 ",
91 )
92 .bind(tenant.as_ref())
93 .bind(&key.mfa_id().0)
94 .bind(key.user_id().as_ref())
95 .fetch_optional(executor)
96 .await
97 .map_err(StoreError::SQLXError)?;
98
99 Ok(user_mfa)
100}
101
102async fn delete_user_mfa<'a, 'e, E>(
103 executor: E,
104 tenant: &'a TenantId,
105 key: &'a MFAKey,
106) -> Result<(), OperationOutcomeError>
107where
108 E: PgExecutor<'e>,
109{
110 let rows_affected = sqlx::query(
111 r"
112 DELETE FROM user_mfa_credential
113 WHERE tenant = $1 AND id::text = $2 AND user_id = $3
114 ",
115 )
116 .bind(tenant.as_ref())
117 .bind(&key.mfa_id().0)
118 .bind(key.user_id().as_ref())
119 .execute(executor)
120 .await
121 .map_err(|_e| {
122 OperationOutcomeError::error(
123 IssueType::not_found(),
124 format!(
125 "User MFA credential '{}' not found or is system created and cannot be deleted.",
126 key.mfa_id().0
127 ),
128 )
129 })?
130 .rows_affected();
131
132 if rows_affected == 0 {
133 return Err(OperationOutcomeError::error(
134 IssueType::not_found(),
135 format!(
136 "User MFA credential '{}' not found or is system created and cannot be deleted.",
137 key.mfa_id().0
138 ),
139 ));
140 }
141
142 Ok(())
143}
144
145async fn search_user_mfa<'a, 'e, E>(
146 executor: E,
147 tenant: &'a TenantId,
148 clauses: &'a UserMFASearchClaims,
149) -> Result<Vec<UserMFACredential>, OperationOutcomeError>
150where
151 E: PgExecutor<'e>,
152{
153 let mut query_builder: QueryBuilder<sqlx::Postgres> = QueryBuilder::new(
154 r"SELECT
155 id::TEXT,
156 tenant,
157 user_id,
158 credential_type,
159 secret_ciphertext,
160 secret_nonce,
161 key_id,
162 totp_algorithm,
163 totp_digits,
164 totp_period,
165 totp_skew,
166 created_at,
167 is_active FROM user_mfa_credential WHERE ",
168 );
169
170 let mut and_clauses = query_builder.separated(" AND ");
171
172 and_clauses
173 .push(" tenant = ")
174 .push_bind_unseparated(tenant.as_ref());
175
176 and_clauses
177 .push(" user_id = ")
178 .push_bind_unseparated(clauses.user_id.as_ref());
179
180 if let Some(is_active) = clauses.is_active {
181 and_clauses
182 .push(" is_active = ")
183 .push_bind_unseparated(is_active);
184 }
185
186 let query = query_builder.build_query_as::<UserMFACredential>();
187
188 let user_mfas: Vec<UserMFACredential> =
189 query.fetch_all(executor).await.map_err(StoreError::from)?;
190
191 Ok(user_mfas)
192}
193
194async fn update_user_mfa<'a, 'e, E>(
195 executor: E,
196 tenant: &'a TenantId,
197 model: UserMFACredentialUpdate,
198) -> Result<UserMFACredential, OperationOutcomeError>
199where
200 E: PgExecutor<'e>,
201{
202 let mut query_builder = QueryBuilder::new(
203 r"
204 UPDATE user_mfa_credential SET
205 ",
206 );
207
208 let mut set_statements = query_builder.separated(", ");
209
210 set_statements
211 .push(" is_active = ")
212 .push_bind_unseparated(model.is_active);
213
214 query_builder.push(" WHERE ");
215
216 let mut where_statements = query_builder.separated(" AND ");
217 where_statements
218 .push(" tenant = ")
219 .push_bind_unseparated(tenant.as_ref())
220 .push(" id::text = ")
221 .push_bind_unseparated(model.id)
222 .push(" user_id = ")
223 .push_bind_unseparated(model.user_id.as_ref());
224
225 query_builder.push(
226 r" RETURNING
227 id::TEXT,
228 tenant,
229 user_id,
230 credential_type,
231 secret_ciphertext,
232 secret_nonce,
233 key_id,
234 totp_algorithm,
235 totp_digits,
236 totp_period,
237 totp_skew,
238 created_at,
239 is_active",
240 );
241
242 let query = query_builder.build_query_as::<UserMFACredential>();
243
244 let user_mfa_credentials = query
245 .fetch_one(executor)
246 .await
247 .map_err(StoreError::SQLXError)?;
248
249 Ok(user_mfa_credentials)
250}
251
252impl
253 TenantModelAdmin<
254 UserMFACredentialCreate,
255 UserMFACredential,
256 UserMFASearchClaims,
257 UserMFACredentialUpdate,
258 MFAKey,
259 > for PGConnection
260{
261 async fn create(
262 &self,
263 tenant: &TenantId,
264 new_user_mfa_credential: UserMFACredentialCreate,
265 ) -> Result<UserMFACredential, OperationOutcomeError> {
266 match self {
267 PGConnection::Pool(pool, _) => {
268 create_user_mfa_credential(pool, tenant, new_user_mfa_credential).await
269 }
270 PGConnection::Transaction(tx, _) => {
271 let mut tx = tx.lock().await;
272
273 create_user_mfa_credential(&mut **tx, tenant, new_user_mfa_credential).await
274 }
275 }
276 }
277
278 async fn read(
279 &self,
280 tenant: &TenantId,
281 id: &MFAKey,
282 ) -> Result<Option<UserMFACredential>, haste_fhir_operation_error::OperationOutcomeError> {
283 match self {
284 PGConnection::Pool(pool, _) => read_user_mfa(pool, tenant, id).await,
285 PGConnection::Transaction(tx, _) => {
286 let mut tx = tx.lock().await;
287 read_user_mfa(&mut **tx, tenant, id).await
288 }
289 }
290 }
291
292 async fn update(
293 &self,
294 tenant: &TenantId,
295 model: UserMFACredentialUpdate,
296 ) -> Result<UserMFACredential, haste_fhir_operation_error::OperationOutcomeError> {
297 match self {
298 PGConnection::Pool(pool, _) => update_user_mfa(pool, tenant, model).await,
299 PGConnection::Transaction(tx, _) => {
300 let mut tx = tx.lock().await;
301 update_user_mfa(&mut **tx, tenant, model).await
302 }
303 }
304 }
305
306 async fn delete(
307 &self,
308 tenant: &TenantId,
309 id: &MFAKey,
310 ) -> Result<(), haste_fhir_operation_error::OperationOutcomeError> {
311 match self {
312 PGConnection::Pool(pool, _) => delete_user_mfa(pool, tenant, id).await,
313 PGConnection::Transaction(tx, _) => {
314 let mut tx = tx.lock().await;
315 delete_user_mfa(&mut **tx, tenant, id).await
316 }
317 }
318 }
319
320 async fn search(
321 &self,
322 tenant: &TenantId,
323 claims: &UserMFASearchClaims,
324 ) -> Result<Vec<UserMFACredential>, OperationOutcomeError> {
325 match self {
326 PGConnection::Pool(pool, _) => search_user_mfa(pool, tenant, claims).await,
327 PGConnection::Transaction(tx, _) => {
328 let mut tx = tx.lock().await;
329 search_user_mfa(&mut **tx, tenant, claims).await
330 }
331 }
332 }
333}