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(
28 "id",
29 self.generated_id(self.exec(), "passkey", "passkey", "id")
30 .await?
31 .unwrap_or_else(|| Uuid::new_v4().to_string()),
32 );
33 active.set("name", input.name);
34 active.set("publicKey", input.public_key);
35 active.set("userId", input.user_id);
36 active.set("credentialID", input.credential_id);
37 active.set("counter", counter);
38 active.set("deviceType", input.device_type);
39 active.set("backedUp", input.backed_up);
40 active.set("transports", input.transports);
41
42 active.set("aaguid", input.aaguid);
43 active.set("createdAt", Utc::now());
44
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, "credentialID", " = ", 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, "userId", " = ", 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("backedUp", update.backed_up);
115 active.set("deviceType", update.device_type);
116
117 model::update::<Model>(self.exec(), &active)
118 .await?
119 .map(|model| Some(Passkey::from(&model)))
120 .ok_or_else(record_not_updated)
121 }
122
123 async fn update_passkey_name(&self, id: &str, name: &str) -> AuthResult<Passkey> {
124 let Some(model) = self.find_passkey(id).await? else {
125 return Err(AuthError::not_found("Passkey not found"));
126 };
127
128 let mut active = model.into_active();
129 active.set("name", Some(name.to_owned()));
130
131 model::update::<Model>(self.exec(), &active)
132 .await?
133 .map(|model| Passkey::from(&model))
134 .ok_or_else(record_not_updated)
135 }
136
137 async fn delete_passkey(&self, id: &str) -> AuthResult<()> {
138 self.exec()
139 .execute(model::delete_by_id::<Model>(self.exec(), id))
140 .await
141 .map(|_| ())
142 }
143}