alibi_sqlx/store/
passkeys.rs1use 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}