Skip to main content

better_auth_api/plugins/passkey/
callbacks.rs

1//! Typed callback inputs. User writes use the active registration transaction.
2
3use 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/// User operations exposed to application registration callbacks.
12#[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/// Endpoint data and the active user adapter.
25#[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/// Account identity retained with the registration challenge.
104#[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/// A verified public-key credential.
113#[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/// Information from a cryptographically verified registration.
123#[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/// Information from a cryptographically verified assertion.
147#[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/// Overrides applied after successful registration verification.
169#[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}