Skip to main content

haste_repository/pg/
authorization_code.rs

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