better_auth_api/plugins/passkey/
callbacks.rs1use async_trait::async_trait;
4use better_auth_core::{
5 AuthConfig, AuthContext, AuthRequest, AuthResult, AuthSchema, CreateUser, RuntimeExtensions,
6 plugin::MetadataMap, store::AuthTransaction, wire::UserView,
7};
8use serde::{Deserialize, Serialize};
9use serde_json::{Map, Value};
10
11#[async_trait]
13pub trait PasskeyUsers: Send + Sync {
14 async fn get_user_by_id(&self, id: &str) -> AuthResult<Option<UserView>>;
15 async fn create_user(&self, user: CreateUser) -> AuthResult<UserView>;
16 async fn get_user_by_email(&self, email: &str) -> AuthResult<Option<UserView>>;
17 async fn update_user(
18 &self,
19 id: &str,
20 update: better_auth_core::UpdateUser,
21 ) -> AuthResult<UserView>;
22 async fn delete_user(&self, id: &str) -> AuthResult<()>;
23}
24#[derive(Clone, Copy)]
26pub struct PasskeyEndpoint<'a> {
27 pub request: &'a AuthRequest,
28 pub body: &'a Value,
29 pub auth_config: &'a AuthConfig,
30 pub extensions: &'a RuntimeExtensions,
31 pub metadata: &'a MetadataMap,
32 pub users: &'a dyn PasskeyUsers,
33}
34pub(super) struct Users<'a, S: AuthSchema> {
35 pub ctx: &'a AuthContext<S>,
36 pub transaction: Option<&'a dyn AuthTransaction<S>>,
37}
38#[async_trait]
39impl<S: AuthSchema> PasskeyUsers for Users<'_, S> {
40 async fn get_user_by_id(&self, id: &str) -> AuthResult<Option<UserView>> {
41 let user = match self.transaction {
42 Some(tx) => tx.get_user_by_id(id).await?,
43 None => self.ctx.database.get_user_by_id(id).await?,
44 };
45 match user {
46 Some(user) => self.ctx.internal_user_view(&user).await.map(Some),
47 None => Ok(None),
48 }
49 }
50 async fn get_user_by_email(&self, email: &str) -> AuthResult<Option<UserView>> {
51 let user = match self.transaction {
52 Some(tx) => tx.get_user_by_email(email).await?,
53 None => self.ctx.database.get_user_by_email(email).await?,
54 };
55 match user {
56 Some(user) => self.ctx.internal_user_view(&user).await.map(Some),
57 None => Ok(None),
58 }
59 }
60 async fn update_user(
61 &self,
62 id: &str,
63 update: better_auth_core::UpdateUser,
64 ) -> AuthResult<UserView> {
65 let user = match self.transaction {
66 Some(tx) => tx.update_user(id, update).await?,
67 None => self.ctx.database.update_user(id, update).await?,
68 };
69 self.ctx.internal_user_view(&user).await
70 }
71 async fn delete_user(&self, id: &str) -> AuthResult<()> {
72 match self.transaction {
73 Some(tx) => tx.delete_user(id).await,
74 None => self.ctx.database.delete_user(id).await,
75 }
76 }
77 async fn create_user(&self, mut user: CreateUser) -> AuthResult<UserView> {
78 crate::plugins::helpers::apply_default_role(self.ctx, &mut user);
79 let user = match self.transaction {
80 Some(tx) => tx.create_user(user).await?,
81 None => self.ctx.database.create_user(user).await?,
82 };
83 self.ctx.internal_user_view(&user).await
84 }
85}
86impl<'a> PasskeyEndpoint<'a> {
87 pub(super) fn new<S: AuthSchema>(
88 ctx: &'a AuthContext<S>,
89 request: &'a AuthRequest,
90 body: &'a Value,
91 users: &'a dyn PasskeyUsers,
92 ) -> Self {
93 Self {
94 request,
95 body,
96 auth_config: &ctx.config,
97 extensions: &ctx.extensions,
98 metadata: &ctx.metadata,
99 users,
100 }
101 }
102}
103#[derive(Debug, Clone, Serialize, Deserialize)]
105#[serde(rename_all = "camelCase")]
106pub struct PasskeyRegistrationUser {
107 pub id: String,
108 pub name: String,
109 #[serde(skip_serializing_if = "Option::is_none")]
110 pub display_name: Option<String>,
111}
112#[derive(Debug, Clone, Serialize)]
114#[serde(rename_all = "camelCase")]
115pub struct PasskeyCredential {
116 pub id: String,
117 pub public_key: Vec<u8>,
118 pub counter: u64,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub transports: Option<Vec<String>>,
121}
122#[derive(Debug, Clone, Serialize)]
124#[serde(rename_all = "camelCase")]
125pub struct PasskeyRegistrationInfo {
126 pub fmt: String,
127 pub aaguid: String,
128 pub credential: PasskeyCredential,
129 pub credential_type: &'static str,
130 pub attestation_object: Vec<u8>,
131 pub user_verified: bool,
132 pub credential_device_type: &'static str,
133 pub credential_backed_up: bool,
134 pub origin: String,
135 #[serde(rename = "rpID")]
136 pub rp_id: String,
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub authenticator_extension_results: Option<Value>,
139}
140#[derive(Debug, Clone, Serialize)]
141#[serde(rename_all = "camelCase")]
142pub struct PasskeyRegistrationVerification {
143 pub verified: bool,
144 pub registration_info: PasskeyRegistrationInfo,
145}
146#[derive(Debug, Clone, Serialize)]
148#[serde(rename_all = "camelCase")]
149pub struct PasskeyAuthenticationInfo {
150 #[serde(rename = "credentialID")]
151 pub credential_id: String,
152 pub new_counter: u64,
153 pub user_verified: bool,
154 pub credential_device_type: &'static str,
155 pub credential_backed_up: bool,
156 pub origin: String,
157 #[serde(rename = "rpID")]
158 pub rp_id: String,
159 #[serde(skip_serializing_if = "Option::is_none")]
160 pub authenticator_extension_results: Option<Value>,
161}
162#[derive(Debug, Clone, Serialize)]
163#[serde(rename_all = "camelCase")]
164pub struct PasskeyAuthenticationVerification {
165 pub verified: bool,
166 pub authentication_info: PasskeyAuthenticationInfo,
167}
168#[derive(Debug, Clone, Default)]
170pub struct PasskeyRegistrationResult {
171 pub user_id: Option<String>,
172 pub name: Option<String>,
173}
174#[async_trait]
175pub trait PasskeyUserResolver: Send + Sync {
176 async fn resolve(
177 &self,
178 ctx: PasskeyEndpoint<'_>,
179 context: Option<&str>,
180 ) -> AuthResult<PasskeyRegistrationUser>;
181}
182#[async_trait]
183pub trait PasskeyExtensionsResolver: Send + Sync {
184 async fn extensions(&self, ctx: PasskeyEndpoint<'_>) -> AuthResult<Option<Map<String, Value>>>;
185}
186#[async_trait]
187pub trait PasskeyRegistrationHook: Send + Sync {
188 async fn after_verification(
189 &self,
190 ctx: PasskeyEndpoint<'_>,
191 verification: &PasskeyRegistrationVerification,
192 user: &PasskeyRegistrationUser,
193 client_data: &Value,
194 context: Option<&str>,
195 ) -> AuthResult<PasskeyRegistrationResult>;
196}
197#[async_trait]
198pub trait PasskeyAuthenticationHook: Send + Sync {
199 async fn after_verification(
200 &self,
201 ctx: PasskeyEndpoint<'_>,
202 verification: &PasskeyAuthenticationVerification,
203 client_data: &Value,
204 ) -> AuthResult<()>;
205}