1use crate::{
2 admin::{ProjectAuthAdmin, TenantAuthAdmin},
3 pg::{PGConnection, StoreError},
4 types::authorization_code::{
5 AuthorizationCode, AuthorizationCodeKind, AuthorizationCodeSearchClaims, CodeErrors,
6 CreateAuthorizationCode, PKCECodeChallengeMethod,
7 },
8 utilities::generate_id,
9};
10use haste_fhir_model::r4::generated::terminology::IssueType;
11use haste_fhir_operation_error::OperationOutcomeError;
12use haste_jwt::{ProjectId, TenantId};
13use sqlx::{Acquire, Postgres, QueryBuilder, types::Json};
14use sqlx_postgres::types::PgInterval;
15
16fn create_code<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
17 connection: Connection,
18 tenant: &'a TenantId,
19 project: Option<&'a ProjectId>,
20 authorization_code: CreateAuthorizationCode,
21) -> impl Future<Output = Result<AuthorizationCode, OperationOutcomeError>> + Send + 'a {
22 async move {
23 let expires_in: PgInterval = authorization_code
24 .expires_in
25 .try_into()
26 .map_err(|_e| CodeErrors::InvalidDuration)?;
27
28 let code = generate_id(Some(45));
29
30 let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
31
32 let new_authorization_code = sqlx::query_as!(
33 AuthorizationCode,
34 r#"
35 INSERT INTO authorization_code (
36 tenant, project, client_id, kind, code, expires_in,
37 user_id, pkce_code_challenge, pkce_code_challenge_method, redirect_uri, meta, membership
38 )
39 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
40 RETURNING tenant as "tenant: TenantId",
41 kind as "kind: AuthorizationCodeKind",
42 code,
43 user_id,
44 project as "project: ProjectId",
45 client_id,
46 pkce_code_challenge,
47 pkce_code_challenge_method as "pkce_code_challenge_method: PKCECodeChallengeMethod",
48 redirect_uri,
49 meta as "meta: Json<serde_json::Value>",
50 NOW() > (created_at + expires_in) as is_expired,
51 membership,
52 created_at
53 "#,
54 tenant as &TenantId,
55 project as Option<&'a ProjectId>,
56 authorization_code.client_id,
57 authorization_code.kind as AuthorizationCodeKind,
58 code,
59 expires_in as PgInterval,
60 authorization_code.user_id,
61 authorization_code.pkce_code_challenge,
62 authorization_code.pkce_code_challenge_method as Option<PKCECodeChallengeMethod>,
63 authorization_code.redirect_uri,
64 authorization_code.meta as std::option::Option<Json<serde_json::Value>>,
65 authorization_code.membership as std::option::Option<String>,
66 ).fetch_one(&mut *conn).await.map_err(StoreError::SQLXError)?;
67
68 Ok(new_authorization_code)
69 }
70}
71
72fn read_code<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
73 connection: Connection,
74 tenant: &'a TenantId,
75 project: Option<&'a ProjectId>,
76 code: &'a str,
77) -> impl Future<Output = Result<Option<AuthorizationCode>, OperationOutcomeError>> + Send + 'a {
78 async move {
79 let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
80
81 let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(
82 r#"
83 SELECT tenant,
84 kind,
85 code,
86 user_id,
87 project,
88 client_id,
89 pkce_code_challenge,
90 pkce_code_challenge_method,
91 redirect_uri,
92 meta,
93 NOW() > (created_at + expires_in) as is_expired,
94 membership,
95 created_at
96 FROM authorization_code
97 WHERE
98 "#,
99 );
100
101 query_builder.push("tenant = ").push_bind(tenant.as_ref());
102 query_builder.push(" AND code = ").push_bind(code);
103
104 if let Some(project) = project {
105 query_builder
106 .push(" AND project = ")
107 .push_bind(project.as_ref());
108 }
109
110 let query = query_builder.build_query_as();
111
112 let authorization_code: Option<AuthorizationCode> = query
113 .fetch_optional(&mut *conn)
114 .await
115 .map_err(StoreError::SQLXError)?;
116
117 Ok(authorization_code)
118 }
119}
120
121fn delete_code<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
122 connection: Connection,
123 tenant: &'a TenantId,
124 project: Option<&'a ProjectId>,
125 code: &'a str,
126) -> impl Future<Output = Result<(), OperationOutcomeError>> + Send + 'a {
127 async move {
128 let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
129
130 let mut query_builder = QueryBuilder::new(
131 r#"
132 DELETE FROM authorization_code
133 WHERE
134 "#,
135 );
136
137 query_builder.push(" tenant = ").push_bind(tenant.as_ref());
138 query_builder.push(" AND code = ").push_bind(code);
139
140 if let Some(project) = project {
141 query_builder
142 .push(" AND project = ")
143 .push_bind(project.as_ref());
144 }
145
146 query_builder.push(
147 r#"
148 RETURNING tenant,
149 kind,
150 code,
151 user_id,
152 project,
153 client_id,
154 pkce_code_challenge,
155 pkce_code_challenge_method,
156 redirect_uri,
157 meta,
158 NOW() > (created_at + expires_in) as is_expired,
159 membership,
160 created_at
161 "#,
162 );
163
164 let query = query_builder.build_query_as();
165
166 let _authorization_code: Option<AuthorizationCode> = query
167 .fetch_optional(&mut *conn)
168 .await
169 .map_err(StoreError::SQLXError)?;
170
171 Ok(())
172 }
173}
174
175fn search_codes<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
176 connection: Connection,
177 tenant: &'a TenantId,
178 project: Option<&'a ProjectId>,
179 clauses: &'a AuthorizationCodeSearchClaims,
180) -> impl Future<Output = Result<Vec<AuthorizationCode>, OperationOutcomeError>> + Send + 'a {
181 async move {
182 let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
183 let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(
184 r#"
185 SELECT tenant,
186 kind,
187 code,
188 user_id,
189 project,
190 client_id,
191 pkce_code_challenge,
192 pkce_code_challenge_method,
193 redirect_uri,
194 meta,
195 NOW() > (created_at + expires_in) as is_expired,
196 membership,
197 created_at
198 FROM authorization_code
199 WHERE
200 "#,
201 );
202
203 query_builder.push(" tenant = ").push_bind(tenant.as_ref());
204
205 if let Some(project) = project {
206 query_builder
207 .push(" AND project = ")
208 .push_bind(project.as_ref());
209 }
210
211 if let Some(client_id) = &clauses.client_id {
212 query_builder
213 .push(" AND client_id = ")
214 .push_bind(client_id);
215 }
216
217 if let Some(code) = &clauses.code {
218 query_builder.push(" AND code = ").push_bind(code);
219 }
220
221 if let Some(user_id) = &clauses.user_id {
222 query_builder.push(" AND user_id = ").push_bind(user_id);
223 }
224
225 if let Some(kind) = &clauses.kind {
226 query_builder
227 .push(" AND kind = ")
228 .push_bind(kind as &AuthorizationCodeKind);
229 }
230
231 if let Some(user_agent) = &clauses.user_agent {
232 query_builder
233 .push(" AND meta->>'user_agent' = ")
234 .push_bind(user_agent);
235 }
236
237 if let Some(is_expired) = &clauses.is_expired {
238 query_builder
239 .push(" AND (NOW() > (created_at + expires_in)) = ")
240 .push_bind(is_expired);
241 }
242
243 let query = query_builder.build_query_as();
244
245 let authorization_codes: Vec<AuthorizationCode> = query
246 .fetch_all(&mut *conn)
247 .await
248 .map_err(StoreError::SQLXError)?;
249
250 Ok(authorization_codes)
251 }
252}
253
254impl<Key: AsRef<str> + Send + Sync>
255 TenantAuthAdmin<
256 CreateAuthorizationCode,
257 AuthorizationCode,
258 AuthorizationCodeSearchClaims,
259 AuthorizationCode,
260 Key,
261 > for PGConnection
262{
263 async fn create(
264 &self,
265 tenant: &TenantId,
266 authorization_code: CreateAuthorizationCode,
267 ) -> Result<AuthorizationCode, OperationOutcomeError> {
268 match &self {
269 PGConnection::Pool(pool, _) => {
270 let res = create_code(pool, tenant, None, authorization_code).await?;
271 Ok(res)
272 }
273 PGConnection::Transaction(tx, _) => {
274 let mut tx = tx.lock().await;
275
276 let res = create_code(&mut *tx, tenant, None, authorization_code).await?;
277 Ok(res)
278 }
279 }
280 }
281
282 async fn read(
283 &self,
284 tenant: &TenantId,
285 code: &Key,
286 ) -> Result<Option<AuthorizationCode>, OperationOutcomeError> {
287 match &self {
288 PGConnection::Pool(pool, _) => {
289 let res = read_code(pool, tenant, None, code.as_ref()).await?;
290 Ok(res)
291 }
292 PGConnection::Transaction(tx, _) => {
293 let mut tx = tx.lock().await;
294
295 let res = read_code(&mut *tx, tenant, None, code.as_ref()).await?;
296 Ok(res)
297 }
298 }
299 }
300
301 async fn update(
302 &self,
303 _tenant: &TenantId,
304 _model: AuthorizationCode,
305 ) -> Result<AuthorizationCode, OperationOutcomeError> {
306 Err(OperationOutcomeError::fatal(
307 IssueType::Exception(None),
308 "Update operation for AuthorizationCode is not implemented.".to_string(),
309 ))
310 }
311
312 async fn delete(&self, tenant: &TenantId, code: &Key) -> Result<(), OperationOutcomeError> {
313 match &self {
314 PGConnection::Pool(pool, _) => {
315 let res = delete_code(pool, tenant, None, code.as_ref()).await?;
316 Ok(res)
317 }
318 PGConnection::Transaction(tx, _) => {
319 let mut tx = tx.lock().await;
320
321 let res = delete_code(&mut *tx, tenant, None, code.as_ref()).await?;
322 Ok(res)
323 }
324 }
325 }
326
327 async fn search(
328 &self,
329 tenant: &TenantId,
330 clauses: &AuthorizationCodeSearchClaims,
331 ) -> Result<Vec<AuthorizationCode>, OperationOutcomeError> {
332 match &self {
333 PGConnection::Pool(pool, _) => {
334 let res = search_codes(pool, tenant, None, clauses).await?;
335 Ok(res)
336 }
337 PGConnection::Transaction(tx, _) => {
338 let mut tx = tx.lock().await;
339
340 let res = search_codes(&mut *tx, tenant, None, clauses).await?;
341 Ok(res)
342 }
343 }
344 }
345}
346
347impl<Key: AsRef<str> + Send + Sync>
348 ProjectAuthAdmin<
349 CreateAuthorizationCode,
350 AuthorizationCode,
351 AuthorizationCodeSearchClaims,
352 AuthorizationCode,
353 Key,
354 > for PGConnection
355{
356 async fn create(
357 &self,
358 tenant: &TenantId,
359 project: &ProjectId,
360 authorization_code: CreateAuthorizationCode,
361 ) -> Result<AuthorizationCode, OperationOutcomeError> {
362 match &self {
363 PGConnection::Pool(pool, _) => {
364 let res = create_code(pool, tenant, Some(project), authorization_code).await?;
365 Ok(res)
366 }
367 PGConnection::Transaction(tx, _) => {
368 let mut tx = tx.lock().await;
369
370 let res = create_code(&mut *tx, tenant, Some(project), authorization_code).await?;
371 Ok(res)
372 }
373 }
374 }
375
376 async fn read(
377 &self,
378 tenant: &TenantId,
379 project: &ProjectId,
380 code: &Key,
381 ) -> Result<Option<AuthorizationCode>, OperationOutcomeError> {
382 match &self {
383 PGConnection::Pool(pool, _) => {
384 let res = read_code(pool, tenant, Some(project), code.as_ref()).await?;
385 Ok(res)
386 }
387 PGConnection::Transaction(tx, _) => {
388 let mut tx = tx.lock().await;
389
390 let res = read_code(&mut *tx, tenant, Some(project), code.as_ref()).await?;
391 Ok(res)
392 }
393 }
394 }
395
396 async fn update(
397 &self,
398 _tenant: &TenantId,
399 _project: &ProjectId,
400 _model: AuthorizationCode,
401 ) -> Result<AuthorizationCode, OperationOutcomeError> {
402 Err(OperationOutcomeError::fatal(
403 IssueType::Exception(None),
404 "Update operation for AuthorizationCode is not implemented.".to_string(),
405 ))
406 }
407
408 async fn delete(
409 &self,
410 tenant: &TenantId,
411 project: &ProjectId,
412 code: &Key,
413 ) -> Result<(), OperationOutcomeError> {
414 match &self {
415 PGConnection::Pool(pool, _) => {
416 let res = delete_code(pool, tenant, Some(project), code.as_ref()).await?;
417 Ok(res)
418 }
419 PGConnection::Transaction(tx, _) => {
420 let mut tx = tx.lock().await;
421
422 let res = delete_code(&mut *tx, tenant, Some(project), code.as_ref()).await?;
423 Ok(res)
424 }
425 }
426 }
427
428 async fn search(
429 &self,
430 tenant: &TenantId,
431 project: &ProjectId,
432 clauses: &AuthorizationCodeSearchClaims,
433 ) -> Result<Vec<AuthorizationCode>, OperationOutcomeError> {
434 match &self {
435 PGConnection::Pool(pool, _) => {
436 let res = search_codes(pool, tenant, Some(project), clauses).await?;
437 Ok(res)
438 }
439 PGConnection::Transaction(tx, _) => {
440 let mut tx = tx.lock().await;
441
442 let res = search_codes(&mut *tx, tenant, Some(project), clauses).await?;
443 Ok(res)
444 }
445 }
446 }
447}