Skip to main content

alibi_sqlx/store/
oauth_clients.rs

1use super::{SqlxStore, entities::oauth_client::Model};
2use crate::{
3    model::{self, ActiveRow, SqlxModel},
4    schema::AuthSchema,
5};
6use alibi_core::{
7    AuthResult,
8    store::{CreateOAuthClient, OAuthClient, OAuthClientStore},
9};
10use async_trait::async_trait;
11impl<S: AuthSchema> SqlxStore<S> {
12    pub(super) async fn session_oauth_tokens(
13        &self,
14        exec: crate::pool::Exec<'_>,
15        kind: alibi_core::store::OAuthTokenKind,
16        session_id: &str,
17    ) -> AuthResult<Vec<alibi_core::store::OAuthToken>> {
18        macro_rules! select {
19            ($model:ty) => {{
20                let mut sql = model::select_model::<$model>(exec);
21                sql.push(" WHERE ");
22                sql.compare(<$model>::TABLE, "sessionId", " = ", session_id);
23                sql.push(" LIMIT ");
24                sql.bind(self.find_many_limit());
25                exec.fetch_all::<$model>(sql)
26                    .await?
27                    .into_iter()
28                    .map(TryInto::try_into)
29                    .collect()
30            }};
31        }
32        match kind {
33            alibi_core::store::OAuthTokenKind::Access => {
34                select!(super::entities::oauth_access_token::Model)
35            }
36            alibi_core::store::OAuthTokenKind::Refresh => {
37                select!(super::entities::oauth_refresh_token::Model)
38            }
39        }
40    }
41    pub(super) async fn oauth_client_with_exec(
42        &self,
43        exec: crate::pool::Exec<'_>,
44        client_id: &str,
45    ) -> AuthResult<Option<OAuthClient>> {
46        let mut sql = model::select_model::<Model>(exec);
47        sql.push(" WHERE ");
48        sql.compare(Model::TABLE, "clientId", " = ", client_id);
49        model::limit_one(&mut sql);
50        exec.fetch_optional::<Model>(sql)
51            .await?
52            .map(TryInto::try_into)
53            .transpose()
54    }
55}
56#[async_trait]
57impl<S: AuthSchema> OAuthClientStore for SqlxStore<S> {
58    async fn has_oauth_client_resource(
59        &self,
60        client_id: &str,
61        resource_id: &str,
62    ) -> AuthResult<bool> {
63        use super::entities::oauth_client_resource::Model as Link;
64        let mut sql = model::select_model::<Link>(self.exec());
65        sql.push(" WHERE ");
66        sql.compare(Link::TABLE, "clientId", " = ", client_id);
67        sql.push(" AND ");
68        sql.compare(Link::TABLE, "resourceId", " = ", resource_id);
69        model::limit_one(&mut sql);
70        Ok(self.exec().fetch_optional::<Link>(sql).await?.is_some())
71    }
72    async fn persist_oauth_client_registration(
73        &self,
74        data: alibi_core::store::oauth_clients::PersistOAuthClientRegistration,
75    ) -> Result<
76        Option<OAuthClient>,
77        alibi_core::store::oauth_clients::OAuthClientRegistrationWriteError,
78    > {
79        use super::entities::oauth_client_resource::Model as Link;
80        use alibi_core::store::oauth_clients::OAuthClientRegistrationWriteError as WriteError;
81        let tx = self.begin(true).await?;
82        let exec = crate::pool::Exec::tx(&tx);
83        let mut active = ActiveRow::new();
84        let metadata = data.client.metadata;
85        let patch = alibi_core::store::oauth_clients::metadata_to_columns(metadata.clone())?;
86        if data.replacement.is_none() {
87            active.set(
88                "id",
89                self.generated_id(exec, "oauthClient", "oauthClient", "id")
90                    .await?
91                    .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
92            );
93            active.set("clientId", data.client.client_id.clone());
94            active.set("userId", data.client.user_id);
95            active.set("clientSecret", data.client.client_secret);
96        }
97        let fields = Model::columns_from_metadata(metadata)?.into_active();
98        for (column, value) in fields.present() {
99            if ![
100                "id",
101                "clientId",
102                "userId",
103                "clientSecret",
104                "createdAt",
105                "updatedAt",
106            ]
107            .contains(&column)
108                && (data.replacement.is_none()
109                    || patch
110                        .get(column)
111                        .is_some_and(|value| column != "metadata" || !value.is_null()))
112            {
113                active.set(column, value.clone());
114            }
115        }
116        if data.replacement.is_none() {
117            active.set("createdAt", data.client.created_at);
118        } else if let Some(created_at) = data
119            .replacement
120            .as_ref()
121            .and_then(|replacement| replacement.existing_created_at)
122        {
123            active.set("createdAt", created_at);
124        }
125        active.set("updatedAt", data.updated_at);
126        let client = if let Some(replacement) = &data.replacement {
127            let mut sql = crate::sql::Sql::with(exec.engine(), "UPDATE ");
128            sql.ident(Model::TABLE);
129            sql.push(" SET ");
130            for (index, (column, value)) in active.changed().enumerate() {
131                if index > 0 {
132                    sql.push(", ");
133                }
134                sql.ident(column);
135                sql.push(" = ");
136                model::bind_model_value::<Model>(&mut sql, column, value.clone());
137            }
138            sql.push(" WHERE ");
139            sql.compare(Model::TABLE, "clientId", " = ", &data.client.client_id);
140            sql.push(" AND ");
141            sql.compare(
142                Model::TABLE,
143                "clientDiscoveryId",
144                " = ",
145                &replacement.discovery_id,
146            );
147            model::returning::<Model>(&mut sql);
148            let Some(client) = exec.fetch_optional::<Model>(sql).await? else {
149                return Ok(None);
150            };
151            client
152        } else {
153            model::insert::<Model>(exec, &active)
154                .await
155                .map_err(|error| WriteError::classify(error, None))?
156        };
157        let linked_at = chrono::Utc::now();
158        for resource_id in &data.resources {
159            let mut sql = model::select_model::<Link>(exec);
160            sql.push(" WHERE ");
161            sql.compare(Link::TABLE, "clientId", " = ", &data.client.client_id);
162            sql.push(" AND ");
163            sql.compare(Link::TABLE, "resourceId", " = ", resource_id);
164            if exec.fetch_optional::<Link>(sql).await?.is_some() {
165                continue;
166            }
167            let mut link = ActiveRow::new();
168            link.set(
169                "id",
170                self.generated_id(exec, "oauthClientResource", "oauthClientResource", "id")
171                    .await?
172                    .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
173            );
174            link.set("clientId", data.client.client_id.clone());
175            link.set("resourceId", resource_id.clone());
176            link.set("metadata", Option::<serde_json::Value>::None);
177            link.set("createdAt", Some(linked_at));
178            _ = model::insert::<Link>(exec, &link)
179                .await
180                .map_err(|error| WriteError::classify(error, Some(resource_id.clone())))?;
181        }
182        let client = client.try_into()?;
183        tx.commit().await?;
184        Ok(Some(client))
185    }
186    async fn claim_oauth_client_assertion(
187        &self,
188        id: &str,
189        expires_at: chrono::DateTime<chrono::Utc>,
190    ) -> AuthResult<bool> {
191        use super::entities::oauth_client_assertion::Model as Assertion;
192        let mut active = ActiveRow::new();
193        active.set("id", id);
194        active.set("expiresAt", expires_at);
195        match model::insert::<Assertion>(self.exec(), &active).await {
196            Ok(_) => Ok(true),
197            Err(error) => {
198                let mut sql = model::select_model::<Assertion>(self.exec());
199                sql.push(" WHERE ");
200                sql.compare(Assertion::TABLE, "id", " = ", id);
201                match self.exec().fetch_optional::<Assertion>(sql).await {
202                    Ok(Some(_)) => Ok(false),
203                    _ => Err(error),
204                }
205            }
206        }
207    }
208
209    async fn store_oauth_refresh_replay(&self, id: &str, ciphertext: &str) -> AuthResult<()> {
210        let mut sql = crate::sql::Sql::with(self.exec().engine(), "UPDATE ");
211        sql.ident("oauthRefreshToken");
212        sql.push(" SET ");
213        sql.assign("rotationReplayResponse", ciphertext);
214        sql.push(" WHERE ");
215        sql.compare("oauthRefreshToken", "id", " = ", id);
216        _ = self.exec().execute(sql).await?;
217        Ok(())
218    }
219    async fn claim_oauth_refresh_rotation(
220        &self,
221        token: &str,
222        at: chrono::DateTime<chrono::Utc>,
223        replay_expires: Option<chrono::DateTime<chrono::Utc>>,
224    ) -> AuthResult<bool> {
225        let mut sql = crate::sql::Sql::with(self.exec().engine(), "UPDATE ");
226        sql.ident("oauthRefreshToken");
227        sql.push(" SET ");
228        sql.assign("revoked", at);
229        sql.push(", ");
230        sql.assign("rotatedAt", at);
231        if let Some(expires) = replay_expires {
232            sql.push(", ");
233            sql.assign("rotationReplayExpiresAt", expires);
234        }
235        sql.push(" WHERE ");
236        sql.compare("oauthRefreshToken", "token", " = ", token);
237        sql.push(" AND ");
238        sql.ident("revoked");
239        sql.push(" IS NULL");
240        Ok(self.exec().execute(sql).await? > 0)
241    }
242
243    async fn claim_oauth_refresh_rotation_by_id(
244        &self,
245        id: &str,
246        at: chrono::DateTime<chrono::Utc>,
247        replay_expires: Option<chrono::DateTime<chrono::Utc>>,
248    ) -> AuthResult<bool> {
249        let mut sql = crate::sql::Sql::with(self.exec().engine(), "UPDATE ");
250        sql.ident("oauthRefreshToken");
251        sql.push(" SET ");
252        sql.assign("revoked", at);
253        sql.push(", ");
254        sql.assign("rotatedAt", at);
255        if let Some(expires) = replay_expires {
256            sql.push(", ");
257            sql.assign("rotationReplayExpiresAt", expires);
258        }
259        sql.push(" WHERE ");
260        sql.compare("oauthRefreshToken", "id", " = ", id);
261        sql.push(" AND ");
262        sql.ident("revoked");
263        sql.push(" IS NULL");
264        Ok(self.exec().execute(sql).await? > 0)
265    }
266
267    async fn mark_oauth_token_revoked(
268        &self,
269        kind: alibi_core::store::OAuthTokenKind,
270        id: &str,
271        at: chrono::DateTime<chrono::Utc>,
272    ) -> AuthResult<()> {
273        let table = match kind {
274            alibi_core::store::OAuthTokenKind::Access => "oauthAccessToken",
275            alibi_core::store::OAuthTokenKind::Refresh => "oauthRefreshToken",
276        };
277        let mut sql = crate::sql::Sql::with(self.exec().engine(), "UPDATE ");
278        sql.ident(table);
279        sql.push(" SET ");
280        sql.assign("revoked", at);
281        sql.push(" WHERE ");
282        sql.compare(table, "id", " = ", id);
283        _ = self.exec().execute(sql).await?;
284        Ok(())
285    }
286    async fn list_oauth_tokens_for_session(
287        &self,
288        kind: alibi_core::store::OAuthTokenKind,
289        session_id: &str,
290    ) -> AuthResult<Vec<alibi_core::store::OAuthToken>> {
291        self.session_oauth_tokens(self.exec(), kind, session_id)
292            .await
293    }
294
295    async fn delete_oauth_token(
296        &self,
297        kind: alibi_core::store::OAuthTokenKind,
298        token: &str,
299    ) -> AuthResult<()> {
300        match kind {
301            alibi_core::store::OAuthTokenKind::Access => {
302                let mut sql = crate::sql::Sql::with(self.exec().engine(), "DELETE FROM ");
303                sql.ident(super::entities::oauth_access_token::Model::TABLE);
304                sql.push(" WHERE ");
305                sql.compare(
306                    super::entities::oauth_access_token::Model::TABLE,
307                    "token",
308                    " = ",
309                    token,
310                );
311                _ = self.exec().execute(sql).await?;
312            }
313            alibi_core::store::OAuthTokenKind::Refresh => {
314                let mut sql = crate::sql::Sql::with(self.exec().engine(), "DELETE FROM ");
315                sql.ident(super::entities::oauth_refresh_token::Model::TABLE);
316                sql.push(" WHERE ");
317                sql.compare(
318                    super::entities::oauth_refresh_token::Model::TABLE,
319                    "token",
320                    " = ",
321                    token,
322                );
323                _ = self.exec().execute(sql).await?;
324            }
325        }
326        Ok(())
327    }
328    async fn revoke_oauth_refresh_token(
329        &self,
330        token: &str,
331        revoked_at: chrono::DateTime<chrono::Utc>,
332    ) -> AuthResult<bool> {
333        let mut lookup =
334            model::select_model::<super::entities::oauth_refresh_token::Model>(self.exec());
335        lookup.push(" WHERE ");
336        lookup.compare("oauthRefreshToken", "token", " = ", token);
337        let Some(row) = self
338            .exec()
339            .fetch_optional::<super::entities::oauth_refresh_token::Model>(lookup)
340            .await?
341        else {
342            return Ok(false);
343        };
344        if row.revoked.is_some() {
345            return Ok(false);
346        }
347        let mut sql = crate::sql::Sql::with(self.exec().engine(), "UPDATE ");
348        sql.ident("oauthRefreshToken");
349        sql.push(" SET ");
350        sql.assign("revoked", revoked_at);
351        sql.push(" WHERE ");
352        sql.compare("oauthRefreshToken", "token", " = ", token);
353        sql.push(" AND ");
354        sql.ident("revoked");
355        sql.push(" IS NULL");
356        Ok(self.exec().execute(sql).await? > 0)
357    }
358    async fn delete_oauth_access_tokens_for_refresh(&self, refresh_id: &str) -> AuthResult<()> {
359        let mut sql = crate::sql::Sql::with(self.exec().engine(), "DELETE FROM ");
360        sql.ident("oauthAccessToken");
361        sql.push(" WHERE ");
362        sql.compare("oauthAccessToken", "refreshId", " = ", refresh_id);
363        _ = self.exec().execute(sql).await?;
364        Ok(())
365    }
366    async fn delete_oauth_refresh_family(&self, client_id: &str, user_id: &str) -> AuthResult<()> {
367        let mut sql = crate::sql::Sql::with(self.exec().engine(), "DELETE FROM ");
368        sql.ident("oauthAccessToken");
369        sql.push(" WHERE ");
370        sql.ident("refreshId");
371        sql.push(" IN (SELECT id FROM ");
372        sql.ident("oauthRefreshToken");
373        sql.push(" WHERE ");
374        sql.compare("oauthRefreshToken", "clientId", " = ", client_id);
375        sql.push(" AND ");
376        sql.compare("oauthRefreshToken", "userId", " = ", user_id);
377        sql.push(" LIMIT ");
378        sql.bind(
379            i64::try_from(self.config().advanced.database.default_find_many_limit)
380                .unwrap_or(i64::MAX),
381        );
382        sql.push(")");
383        _ = self.exec().execute(sql).await?;
384        let mut sql = crate::sql::Sql::with(self.exec().engine(), "DELETE FROM ");
385        sql.ident("oauthRefreshToken");
386        sql.push(" WHERE ");
387        sql.compare("oauthRefreshToken", "clientId", " = ", client_id);
388        sql.push(" AND ");
389        sql.compare("oauthRefreshToken", "userId", " = ", user_id);
390        _ = self.exec().execute(sql).await?;
391        Ok(())
392    }
393    async fn create_oauth_token(
394        &self,
395        kind: alibi_core::store::OAuthTokenKind,
396        data: alibi_core::store::CreateOAuthToken,
397    ) -> AuthResult<alibi_core::store::OAuthToken> {
398        match kind {
399            alibi_core::store::OAuthTokenKind::Access => {
400                let mut active = ActiveRow::new();
401                active.set(
402                    "id",
403                    self.generated_id(self.exec(), "oauthAccessToken", "oauthAccessToken", "id")
404                        .await?
405                        .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
406                );
407                active.set("token", data.token);
408                active.set("clientId", data.client_id);
409                active.set("userId", data.user_id);
410                active.set("sessionId", data.session_id);
411                active.set("authorizationCodeId", data.authorization_code_id);
412                active.set("createdAt", data.created_at);
413                active.set("expiresAt", data.expires_at);
414                let fields = super::entities::oauth_access_token::Model::columns_from_metadata(
415                    data.metadata,
416                )?
417                .into_active();
418                for (column, value) in fields.present() {
419                    if ![
420                        "id",
421                        "token",
422                        "clientId",
423                        "sessionId",
424                        "userId",
425                        "authorizationCodeId",
426                        "createdAt",
427                        "expiresAt",
428                    ]
429                    .contains(&column)
430                    {
431                        active.set(column, value.clone());
432                    }
433                }
434                model::insert::<super::entities::oauth_access_token::Model>(self.exec(), &active)
435                    .await?
436                    .try_into()
437            }
438            alibi_core::store::OAuthTokenKind::Refresh => {
439                let mut active = ActiveRow::new();
440                active.set(
441                    "id",
442                    self.generated_id(self.exec(), "oauthRefreshToken", "oauthRefreshToken", "id")
443                        .await?
444                        .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
445                );
446                active.set("token", data.token);
447                active.set("clientId", data.client_id);
448                active.set("userId", data.user_id);
449                active.set("sessionId", data.session_id);
450                active.set("authorizationCodeId", data.authorization_code_id);
451                active.set("createdAt", data.created_at);
452                active.set("expiresAt", data.expires_at);
453                let fields = super::entities::oauth_refresh_token::Model::columns_from_metadata(
454                    data.metadata,
455                )?
456                .into_active();
457                for (column, value) in fields.present() {
458                    if ![
459                        "id",
460                        "token",
461                        "clientId",
462                        "sessionId",
463                        "userId",
464                        "authorizationCodeId",
465                        "createdAt",
466                        "expiresAt",
467                    ]
468                    .contains(&column)
469                    {
470                        active.set(column, value.clone());
471                    }
472                }
473                model::insert::<super::entities::oauth_refresh_token::Model>(self.exec(), &active)
474                    .await?
475                    .try_into()
476            }
477        }
478    }
479    async fn get_oauth_token(
480        &self,
481        kind: alibi_core::store::OAuthTokenKind,
482        token: &str,
483    ) -> AuthResult<Option<alibi_core::store::OAuthToken>> {
484        match kind {
485            alibi_core::store::OAuthTokenKind::Access => {
486                let mut sql =
487                    model::select_model::<super::entities::oauth_access_token::Model>(self.exec());
488                sql.push(" WHERE ");
489                sql.compare(
490                    super::entities::oauth_access_token::Model::TABLE,
491                    "token",
492                    " = ",
493                    token,
494                );
495                self.exec()
496                    .fetch_optional::<super::entities::oauth_access_token::Model>(sql)
497                    .await?
498                    .map(TryInto::try_into)
499                    .transpose()
500            }
501            alibi_core::store::OAuthTokenKind::Refresh => {
502                let mut sql =
503                    model::select_model::<super::entities::oauth_refresh_token::Model>(self.exec());
504                sql.push(" WHERE ");
505                sql.compare(
506                    super::entities::oauth_refresh_token::Model::TABLE,
507                    "token",
508                    " = ",
509                    token,
510                );
511                self.exec()
512                    .fetch_optional::<super::entities::oauth_refresh_token::Model>(sql)
513                    .await?
514                    .map(TryInto::try_into)
515                    .transpose()
516            }
517        }
518    }
519    async fn delete_oauth_tokens_for_code(&self, code: &str) -> AuthResult<()> {
520        {
521            let mut sql = crate::sql::Sql::with(self.exec().engine(), "DELETE FROM ");
522            sql.ident(super::entities::oauth_access_token::Model::TABLE);
523            sql.push(" WHERE ");
524            sql.compare(
525                super::entities::oauth_access_token::Model::TABLE,
526                "authorizationCodeId",
527                " = ",
528                code,
529            );
530            _ = self.exec().execute(sql).await?;
531        }
532        {
533            let mut sql = crate::sql::Sql::with(self.exec().engine(), "DELETE FROM ");
534            sql.ident(super::entities::oauth_refresh_token::Model::TABLE);
535            sql.push(" WHERE ");
536            sql.compare(
537                super::entities::oauth_refresh_token::Model::TABLE,
538                "authorizationCodeId",
539                " = ",
540                code,
541            );
542            _ = self.exec().execute(sql).await?;
543        }
544        Ok(())
545    }
546    async fn delete_oauth_client_resource(
547        &self,
548        client_id: &str,
549        resource_id: &str,
550    ) -> AuthResult<()> {
551        let mut sql = crate::sql::Sql::with(self.exec().engine(), "DELETE FROM ");
552        sql.ident(super::entities::oauth_client_resource::Model::TABLE);
553        sql.push(" WHERE ");
554        sql.compare(
555            super::entities::oauth_client_resource::Model::TABLE,
556            "clientId",
557            " = ",
558            client_id,
559        );
560        sql.push(" AND ");
561        sql.compare(
562            super::entities::oauth_client_resource::Model::TABLE,
563            "resourceId",
564            " = ",
565            resource_id,
566        );
567        self.exec().execute(sql).await.map(|_| ())
568    }
569    async fn create_oauth_client_resource(
570        &self,
571        data: alibi_core::store::CreateOAuthClientResource,
572    ) -> AuthResult<alibi_core::store::OAuthClientResource> {
573        let mut active = ActiveRow::new();
574        active.set(
575            "id",
576            self.generated_id(
577                self.exec(),
578                "oauthClientResource",
579                "oauthClientResource",
580                "id",
581            )
582            .await?
583            .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
584        );
585        active.set("clientId", data.client_id);
586        active.set("resourceId", data.resource_id);
587        active.set("metadata", Option::<serde_json::Value>::None);
588        active.set("createdAt", Some(data.created_at));
589        model::insert::<super::entities::oauth_client_resource::Model>(self.exec(), &active)
590            .await?
591            .try_into()
592    }
593    async fn delete_oauth_resource(&self, identifier: &str) -> AuthResult<()> {
594        let mut links = crate::sql::Sql::with(self.exec().engine(), "DELETE FROM ");
595        links.ident(super::entities::oauth_client_resource::Model::TABLE);
596        links.push(" WHERE ");
597        links.compare(
598            super::entities::oauth_client_resource::Model::TABLE,
599            "resourceId",
600            " = ",
601            identifier,
602        );
603        _ = self.exec().execute(links).await?;
604        let mut sql = crate::sql::Sql::with(self.exec().engine(), "DELETE FROM ");
605        sql.ident(super::entities::oauth_resource::Model::TABLE);
606        sql.push(" WHERE ");
607        sql.compare(
608            super::entities::oauth_resource::Model::TABLE,
609            "identifier",
610            " = ",
611            identifier,
612        );
613        self.exec().execute(sql).await.map(|_| ())
614    }
615    async fn update_oauth_resource(
616        &self,
617        identifier: &str,
618        metadata: serde_json::Value,
619        updated_at: chrono::DateTime<chrono::Utc>,
620    ) -> AuthResult<Option<alibi_core::store::OAuthResource>> {
621        let Some(row) = self.get_oauth_resource(identifier).await? else {
622            return Ok(None);
623        };
624        let mut active = ActiveRow::new();
625        active.set("id", row.id);
626        let fields =
627            super::entities::oauth_resource::Model::columns_from_metadata(metadata)?.into_active();
628        for (column, value) in fields.present() {
629            if !["id", "identifier", "createdAt", "updatedAt"].contains(&column) {
630                active.set(column, value.clone());
631            }
632        }
633        active.set("updatedAt", updated_at);
634        model::update::<super::entities::oauth_resource::Model>(self.exec(), &active)
635            .await?
636            .map(TryInto::try_into)
637            .transpose()
638    }
639    async fn list_oauth_resources(&self) -> AuthResult<Vec<alibi_core::store::OAuthResource>> {
640        let mut sql = model::select_model::<super::entities::oauth_resource::Model>(self.exec());
641        sql.push(" LIMIT ");
642        sql.bind(self.find_many_limit());
643        self.exec()
644            .fetch_all::<super::entities::oauth_resource::Model>(sql)
645            .await?
646            .into_iter()
647            .map(TryInto::try_into)
648            .collect()
649    }
650    async fn list_oauth_client_resources(
651        &self,
652        client_id: &str,
653    ) -> AuthResult<Vec<alibi_core::store::OAuthClientResource>> {
654        let mut sql =
655            model::select_model::<super::entities::oauth_client_resource::Model>(self.exec());
656        sql.push(" WHERE ");
657        sql.compare(
658            super::entities::oauth_client_resource::Model::TABLE,
659            "clientId",
660            " = ",
661            client_id,
662        );
663        sql.push(" LIMIT ");
664        sql.bind(self.find_many_limit());
665        self.exec()
666            .fetch_all::<super::entities::oauth_client_resource::Model>(sql)
667            .await?
668            .into_iter()
669            .map(TryInto::try_into)
670            .collect()
671    }
672    async fn create_oauth_resource(
673        &self,
674        data: alibi_core::store::CreateOAuthResource,
675    ) -> AuthResult<alibi_core::store::OAuthResource> {
676        let mut active = ActiveRow::new();
677        active.set(
678            "id",
679            self.generated_id(self.exec(), "oauthResource", "oauthResource", "id")
680                .await?
681                .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
682        );
683        active.set("identifier", data.identifier);
684        let fields = super::entities::oauth_resource::Model::columns_from_metadata(data.metadata)?
685            .into_active();
686        for (column, value) in fields.present() {
687            if !["id", "identifier", "createdAt", "updatedAt"].contains(&column) {
688                active.set(column, value.clone());
689            }
690        }
691        active.set("createdAt", data.created_at);
692        active.set("updatedAt", data.created_at);
693        model::insert::<super::entities::oauth_resource::Model>(self.exec(), &active)
694            .await?
695            .try_into()
696    }
697    async fn get_oauth_resource(
698        &self,
699        identifier: &str,
700    ) -> AuthResult<Option<alibi_core::store::OAuthResource>> {
701        let mut sql = model::select_model::<super::entities::oauth_resource::Model>(self.exec());
702        sql.push(" WHERE ");
703        sql.compare(
704            super::entities::oauth_resource::Model::TABLE,
705            "identifier",
706            " = ",
707            identifier,
708        );
709        model::limit_one(&mut sql);
710        self.exec()
711            .fetch_optional::<super::entities::oauth_resource::Model>(sql)
712            .await?
713            .map(TryInto::try_into)
714            .transpose()
715    }
716    async fn create_oauth_consent(
717        &self,
718        data: alibi_core::store::CreateOAuthConsent,
719    ) -> AuthResult<alibi_core::store::OAuthConsent> {
720        let mut active = ActiveRow::new();
721        active.set(
722            "id",
723            self.generated_id(self.exec(), "oauthConsent", "oauthConsent", "id")
724                .await?
725                .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
726        );
727        active.set("clientId", data.client_id);
728        active.set("userId", data.user_id);
729        let fields = super::entities::oauth_consent::Model::columns_from_metadata(data.metadata)?
730            .into_active();
731        for (column, value) in fields.present() {
732            if !["id", "clientId", "userId", "createdAt", "updatedAt"].contains(&column) {
733                active.set(column, value.clone());
734            }
735        }
736        active.set("createdAt", data.created_at);
737        active.set("updatedAt", data.created_at);
738        model::insert::<super::entities::oauth_consent::Model>(self.exec(), &active)
739            .await?
740            .try_into()
741    }
742    async fn list_oauth_consents_by_user(
743        &self,
744        user_id: &str,
745    ) -> AuthResult<Vec<alibi_core::store::OAuthConsent>> {
746        let mut sql = model::select_model::<super::entities::oauth_consent::Model>(self.exec());
747        sql.push(" WHERE ");
748        sql.compare(
749            super::entities::oauth_consent::Model::TABLE,
750            "userId",
751            " = ",
752            user_id,
753        );
754        sql.push(" LIMIT ");
755        sql.bind(self.find_many_limit());
756        self.exec()
757            .fetch_all::<super::entities::oauth_consent::Model>(sql)
758            .await?
759            .into_iter()
760            .map(TryInto::try_into)
761            .collect()
762    }
763    async fn update_oauth_consent_metadata(
764        &self,
765        id: &str,
766        metadata: serde_json::Value,
767        updated_at: chrono::DateTime<chrono::Utc>,
768    ) -> AuthResult<Option<alibi_core::store::OAuthConsent>> {
769        let mut active = ActiveRow::new();
770        active.set("id", id.to_owned());
771        let fields =
772            super::entities::oauth_consent::Model::columns_from_metadata(metadata)?.into_active();
773        for (column, value) in fields.present() {
774            if !["id", "clientId", "userId", "createdAt", "updatedAt"].contains(&column) {
775                active.set(column, value.clone());
776            }
777        }
778        active.set("updatedAt", updated_at);
779        model::update::<super::entities::oauth_consent::Model>(self.exec(), &active)
780            .await?
781            .map(TryInto::try_into)
782            .transpose()
783    }
784    async fn create_oauth_client(&self, data: CreateOAuthClient) -> AuthResult<OAuthClient> {
785        let mut active = ActiveRow::new();
786        active.set(
787            "id",
788            self.generated_id(self.exec(), "oauthClient", "oauthClient", "id")
789                .await?
790                .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
791        );
792        active.set("clientId", data.client_id);
793        active.set("userId", data.user_id);
794        active.set("clientSecret", data.client_secret);
795        let fields = Model::columns_from_metadata(data.metadata)?.into_active();
796        for (column, value) in fields.present() {
797            if ![
798                "id",
799                "clientId",
800                "userId",
801                "clientSecret",
802                "createdAt",
803                "updatedAt",
804            ]
805            .contains(&column)
806            {
807                active.set(column, value.clone());
808            }
809        }
810        active.set("createdAt", data.created_at);
811        active.set("updatedAt", data.created_at);
812        model::insert::<Model>(self.exec(), &active)
813            .await?
814            .try_into()
815    }
816    async fn get_oauth_consent(
817        &self,
818        id: &str,
819    ) -> AuthResult<Option<alibi_core::store::OAuthConsent>> {
820        let mut sql = model::select_model::<super::entities::oauth_consent::Model>(self.exec());
821        sql.push(" WHERE ");
822        sql.compare(
823            super::entities::oauth_consent::Model::TABLE,
824            "id",
825            " = ",
826            id,
827        );
828        model::limit_one(&mut sql);
829        self.exec()
830            .fetch_optional::<super::entities::oauth_consent::Model>(sql)
831            .await?
832            .map(TryInto::try_into)
833            .transpose()
834    }
835    async fn get_oauth_client(&self, client_id: &str) -> AuthResult<Option<OAuthClient>> {
836        self.oauth_client_with_exec(self.exec(), client_id).await
837    }
838    async fn list_oauth_clients_by_user(&self, user_id: &str) -> AuthResult<Vec<OAuthClient>> {
839        let mut sql = model::select_model::<Model>(self.exec());
840        sql.push(" WHERE ");
841        sql.compare(Model::TABLE, "userId", " = ", user_id);
842        sql.push(" LIMIT ");
843        sql.bind(self.find_many_limit());
844        self.exec()
845            .fetch_all::<Model>(sql)
846            .await?
847            .into_iter()
848            .map(TryInto::try_into)
849            .collect()
850    }
851    async fn delete_oauth_consent(&self, id: &str) -> AuthResult<()> {
852        let mut sql = crate::sql::Sql::with(self.exec().engine(), "DELETE FROM ");
853        sql.ident(super::entities::oauth_consent::Model::TABLE);
854        sql.push(" WHERE ");
855        sql.compare(
856            super::entities::oauth_consent::Model::TABLE,
857            "id",
858            " = ",
859            id,
860        );
861        self.exec().execute(sql).await.map(|_| ())
862    }
863    async fn delete_oauth_client(&self, client_id: &str) -> AuthResult<()> {
864        let mut sql = crate::sql::Sql::with(self.exec().engine(), "DELETE FROM ");
865        sql.ident(Model::TABLE);
866        sql.push(" WHERE ");
867        sql.compare(Model::TABLE, "clientId", " = ", client_id);
868        self.exec().execute(sql).await.map(|_| ())
869    }
870    async fn update_oauth_client_secret(
871        &self,
872        client_id: &str,
873        secret: String,
874        updated_at: chrono::DateTime<chrono::Utc>,
875    ) -> AuthResult<Option<OAuthClient>> {
876        let Some(client) = self.get_oauth_client(client_id).await? else {
877            return Ok(None);
878        };
879        let mut active = ActiveRow::new();
880        active.set("id", client.id);
881        active.set("clientSecret", Some(secret));
882        active.set("updatedAt", updated_at);
883        model::update::<Model>(self.exec(), &active)
884            .await?
885            .map(TryInto::try_into)
886            .transpose()
887    }
888    async fn update_oauth_client_metadata(
889        &self,
890        client_id: &str,
891        metadata: serde_json::Value,
892        updated_at: chrono::DateTime<chrono::Utc>,
893    ) -> AuthResult<Option<OAuthClient>> {
894        let Some(client) = self.get_oauth_client(client_id).await? else {
895            return Ok(None);
896        };
897        let mut active = ActiveRow::new();
898        active.set("id", client.id);
899        let fields = Model::columns_from_metadata(metadata)?.into_active();
900        for (column, value) in fields.present() {
901            if ![
902                "id",
903                "clientId",
904                "userId",
905                "clientSecret",
906                "createdAt",
907                "updatedAt",
908            ]
909            .contains(&column)
910            {
911                active.set(column, value.clone());
912            }
913        }
914        active.set("updatedAt", updated_at);
915        model::update::<Model>(self.exec(), &active)
916            .await?
917            .map(TryInto::try_into)
918            .transpose()
919    }
920}