haste_repository/pg/
authorization_code.rs

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}