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(
28            "id",
29            self.generated_id(self.exec(), "passkey", "passkeys", "id")
30                .await?
31                .unwrap_or_else(|| Uuid::new_v4().to_string()),
32        );
33        active.set("name", input.name);
34        active.set("public_key", input.public_key);
35        active.set("user_id", input.user_id);
36        active.set("credential_id", input.credential_id);
37        active.set("counter", counter);
38        active.set("device_type", input.device_type);
39        active.set("backed_up", input.backed_up);
40        active.set("transports", input.transports);
41        active.set("credential", input.credential);
42        active.set("aaguid", input.aaguid);
43        active.set("created_at", Utc::now());
44        active.set("updated_at", Utc::now());
45        model::insert::<Model>(exec, &active)
46            .await
47            .map(|model| Passkey::from(&model))
48    }
49
50    async fn find_passkey(&self, id: &str) -> AuthResult<Option<Model>> {
51        let mut sql = model::by_id::<Model>(self.exec(), id);
52        model::limit_one(&mut sql);
53        self.exec().fetch_optional(sql).await
54    }
55}
56
57#[async_trait]
58impl<S: AuthSchema + Send + Sync> PasskeyStore for SqlxStore<S> {
59    async fn create_passkey(&self, input: CreatePasskey) -> AuthResult<Passkey> {
60        self.create_passkey_with_connection(self.exec(), input)
61            .await
62    }
63
64    async fn get_passkey_by_id(&self, id: &str) -> AuthResult<Option<Passkey>> {
65        Ok(self
66            .find_passkey(id)
67            .await?
68            .map(|model| Passkey::from(&model)))
69    }
70
71    async fn get_passkey_by_credential_id(
72        &self,
73        credential_id: &str,
74    ) -> AuthResult<Option<Passkey>> {
75        let mut sql = model::select_model::<Model>(self.exec());
76        sql.push(" WHERE ");
77        sql.compare(Model::TABLE, "credential_id", " = ", credential_id);
78        model::limit_one(&mut sql);
79        Ok(self
80            .exec()
81            .fetch_optional::<Model>(sql)
82            .await?
83            .map(|model| Passkey::from(&model)))
84    }
85
86    async fn list_passkeys_by_user(&self, user_id: &str) -> AuthResult<Vec<Passkey>> {
87        let mut sql = model::select_model::<Model>(self.exec());
88        sql.push(" WHERE ");
89        sql.compare(Model::TABLE, "user_id", " = ", user_id);
90        Ok(self
91            .exec()
92            .fetch_all::<Model>(sql)
93            .await?
94            .iter()
95            .map(Passkey::from)
96            .collect())
97    }
98
99    async fn update_passkey_authentication(
100        &self,
101        id: &str,
102        update: UpdatePasskeyAuthentication,
103    ) -> AuthResult<Option<Passkey>> {
104        let Some(model) = self.find_passkey(id).await? else {
105            return Ok(None);
106        };
107
108        let mut active = model.into_active();
109        active.set(
110            "counter",
111            i64::try_from(update.counter)
112                .map_err(|_error| AuthError::bad_request("Passkey counter exceeds i64 range"))?,
113        );
114        active.set("backed_up", update.backed_up);
115        active.set("device_type", update.device_type);
116        active.set("credential", update.credential);
117        active.set("updated_at", Utc::now());
118        model::update::<Model>(self.exec(), &active)
119            .await?
120            .map(|model| Some(Passkey::from(&model)))
121            .ok_or_else(record_not_updated)
122    }
123
124    async fn update_passkey_name(&self, id: &str, name: &str) -> AuthResult<Passkey> {
125        let Some(model) = self.find_passkey(id).await? else {
126            return Err(AuthError::not_found("Passkey not found"));
127        };
128
129        let mut active = model.into_active();
130        active.set("name", Some(name.to_owned()));
131        active.set("updated_at", Utc::now());
132        model::update::<Model>(self.exec(), &active)
133            .await?
134            .map(|model| Passkey::from(&model))
135            .ok_or_else(record_not_updated)
136    }
137
138    async fn delete_passkey(&self, id: &str) -> AuthResult<()> {
139        self.exec()
140            .execute(model::delete_by_id::<Model>(self.exec(), id))
141            .await
142            .map(|_| ())
143    }
144}