Skip to main content

alibi_sqlx/store/
passkeys.rs

1use super::SqlxStore;
2use super::entities::passkey::Model;
3use crate::error::record_not_updated;
4use crate::model::{self, ActiveRow, SqlxModel};
5use crate::pool::Exec;
6use crate::schema::AuthSchema;
7use alibi_core::error::{AuthError, AuthResult};
8use alibi_core::store::PasskeyStore;
9use alibi_core::types::{CreatePasskey, Passkey, UpdatePasskeyAuthentication};
10use async_trait::async_trait;
11use chrono::Utc;
12use uuid::Uuid;
13
14impl<S> SqlxStore<S>
15where
16    S: AuthSchema + Send + Sync,
17{
18    pub(super) async fn create_passkey_with_connection(
19        &self,
20        exec: Exec<'_>,
21        input: CreatePasskey,
22    ) -> AuthResult<Passkey> {
23        let counter = i64::try_from(input.counter)
24            .map_err(|_error| AuthError::bad_request("Passkey counter exceeds i64 range"))?;
25
26        let mut active = ActiveRow::new();
27        active.set("id", Uuid::new_v4().to_string());
28        active.set("name", input.name);
29        active.set("public_key", input.public_key);
30        active.set("user_id", input.user_id);
31        active.set("credential_id", input.credential_id);
32        active.set("counter", counter);
33        active.set("device_type", input.device_type);
34        active.set("backed_up", input.backed_up);
35        active.set("transports", input.transports);
36        active.set("credential", input.credential);
37        active.set("aaguid", input.aaguid);
38        active.set("created_at", Utc::now());
39        active.set("updated_at", Utc::now());
40        model::insert::<Model>(exec, &active)
41            .await
42            .map(|model| Passkey::from(&model))
43    }
44
45    async fn find_passkey(&self, id: &str) -> AuthResult<Option<Model>> {
46        let mut sql = model::by_id::<Model>(self.exec(), id);
47        model::limit_one(&mut sql);
48        self.exec().fetch_optional(sql).await
49    }
50}
51
52#[async_trait]
53impl<S: AuthSchema + Send + Sync> PasskeyStore for SqlxStore<S> {
54    async fn create_passkey(&self, input: CreatePasskey) -> AuthResult<Passkey> {
55        self.create_passkey_with_connection(self.exec(), input)
56            .await
57    }
58
59    async fn get_passkey_by_id(&self, id: &str) -> AuthResult<Option<Passkey>> {
60        Ok(self
61            .find_passkey(id)
62            .await?
63            .map(|model| Passkey::from(&model)))
64    }
65
66    async fn get_passkey_by_credential_id(
67        &self,
68        credential_id: &str,
69    ) -> AuthResult<Option<Passkey>> {
70        let mut sql = model::select_model::<Model>(self.exec());
71        sql.push(" WHERE ");
72        sql.compare(Model::TABLE, "credential_id", " = ", credential_id);
73        model::limit_one(&mut sql);
74        Ok(self
75            .exec()
76            .fetch_optional::<Model>(sql)
77            .await?
78            .map(|model| Passkey::from(&model)))
79    }
80
81    async fn list_passkeys_by_user(&self, user_id: &str) -> AuthResult<Vec<Passkey>> {
82        let mut sql = model::select_model::<Model>(self.exec());
83        sql.push(" WHERE ");
84        sql.compare(Model::TABLE, "user_id", " = ", user_id);
85        Ok(self
86            .exec()
87            .fetch_all::<Model>(sql)
88            .await?
89            .iter()
90            .map(Passkey::from)
91            .collect())
92    }
93
94    async fn update_passkey_authentication(
95        &self,
96        id: &str,
97        update: UpdatePasskeyAuthentication,
98    ) -> AuthResult<Option<Passkey>> {
99        let Some(model) = self.find_passkey(id).await? else {
100            return Ok(None);
101        };
102
103        let mut active = model.into_active();
104        active.set(
105            "counter",
106            i64::try_from(update.counter)
107                .map_err(|_error| AuthError::bad_request("Passkey counter exceeds i64 range"))?,
108        );
109        active.set("backed_up", update.backed_up);
110        active.set("device_type", update.device_type);
111        active.set("credential", update.credential);
112        active.set("updated_at", Utc::now());
113        model::update::<Model>(self.exec(), &active)
114            .await?
115            .map(|model_2| Some(Passkey::from(&model_2)))
116            .ok_or_else(record_not_updated)
117    }
118
119    async fn update_passkey_name(&self, id: &str, name: &str) -> AuthResult<Passkey> {
120        let Some(model) = self.find_passkey(id).await? else {
121            return Err(AuthError::not_found("Passkey not found"));
122        };
123
124        let mut active = model.into_active();
125        active.set("name", Some(name.to_owned()));
126        active.set("updated_at", Utc::now());
127        model::update::<Model>(self.exec(), &active)
128            .await?
129            .map(|model_2| Passkey::from(&model_2))
130            .ok_or_else(record_not_updated)
131    }
132
133    async fn delete_passkey(&self, id: &str) -> AuthResult<()> {
134        self.exec()
135            .execute(model::delete_by_id::<Model>(self.exec(), id))
136            .await
137            .map(drop)
138    }
139}