Skip to main content

openauth_passkey/
options.rs

1use std::sync::Arc;
2
3use serde::{Deserialize, Serialize};
4use serde_json::{json, Value};
5
6use crate::webauthn::{PasskeyWebAuthnBackend, RealPasskeyWebAuthnBackend};
7
8/// Advanced passkey plugin settings.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct PasskeyAdvancedOptions {
11    pub webauthn_challenge_cookie: String,
12}
13
14impl Default for PasskeyAdvancedOptions {
15    fn default() -> Self {
16        Self {
17            webauthn_challenge_cookie: "better-auth-passkey".to_owned(),
18        }
19    }
20}
21
22/// Passkey plugin settings.
23#[derive(Clone)]
24pub struct PasskeyOptions {
25    pub rp_id: Option<String>,
26    pub rp_name: Option<String>,
27    pub origin: Vec<String>,
28    pub passkey_table: String,
29    pub authenticator_selection: AuthenticatorSelection,
30    pub registration: PasskeyRegistrationOptions,
31    pub authentication: PasskeyAuthenticationOptions,
32    pub advanced: PasskeyAdvancedOptions,
33    pub backend: Arc<dyn PasskeyWebAuthnBackend>,
34}
35
36impl Default for PasskeyOptions {
37    fn default() -> Self {
38        Self {
39            rp_id: None,
40            rp_name: None,
41            origin: Vec::new(),
42            passkey_table: "passkeys".to_owned(),
43            authenticator_selection: AuthenticatorSelection::default(),
44            registration: PasskeyRegistrationOptions::default(),
45            authentication: PasskeyAuthenticationOptions::default(),
46            advanced: PasskeyAdvancedOptions::default(),
47            backend: Arc::new(RealPasskeyWebAuthnBackend),
48        }
49    }
50}
51
52impl PasskeyOptions {
53    pub fn new() -> Self {
54        Self::default()
55    }
56
57    #[must_use]
58    pub fn rp_id(mut self, rp_id: impl Into<String>) -> Self {
59        self.rp_id = Some(rp_id.into());
60        self
61    }
62
63    #[must_use]
64    pub fn rp_name(mut self, rp_name: impl Into<String>) -> Self {
65        self.rp_name = Some(rp_name.into());
66        self
67    }
68
69    #[must_use]
70    pub fn origin(mut self, origin: impl Into<String>) -> Self {
71        self.origin.push(origin.into());
72        self
73    }
74
75    #[must_use]
76    pub fn passkey_table(mut self, table: impl Into<String>) -> Self {
77        self.passkey_table = table.into();
78        self
79    }
80
81    #[must_use]
82    pub fn authenticator_selection(mut self, selection: AuthenticatorSelection) -> Self {
83        self.authenticator_selection = selection;
84        self
85    }
86
87    #[must_use]
88    pub fn registration(mut self, registration: PasskeyRegistrationOptions) -> Self {
89        self.registration = registration;
90        self
91    }
92
93    #[must_use]
94    pub fn authentication(mut self, authentication: PasskeyAuthenticationOptions) -> Self {
95        self.authentication = authentication;
96        self
97    }
98
99    #[must_use]
100    pub fn advanced(mut self, advanced: PasskeyAdvancedOptions) -> Self {
101        self.advanced = advanced;
102        self
103    }
104
105    #[must_use]
106    pub fn backend(mut self, backend: Arc<dyn PasskeyWebAuthnBackend>) -> Self {
107        self.backend = backend;
108        self
109    }
110}
111
112/// Browser authenticator attachment hint.
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
114pub enum AuthenticatorAttachment {
115    Platform,
116    CrossPlatform,
117}
118
119impl AuthenticatorAttachment {
120    pub(crate) fn from_query(value: &str) -> Option<Self> {
121        match value {
122            "platform" => Some(Self::Platform),
123            "cross-platform" => Some(Self::CrossPlatform),
124            _ => None,
125        }
126    }
127
128    pub(crate) fn as_str(self) -> &'static str {
129        match self {
130            Self::Platform => "platform",
131            Self::CrossPlatform => "cross-platform",
132        }
133    }
134}
135
136/// Resident key preference used in registration options.
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
138pub enum ResidentKeyRequirement {
139    Discouraged,
140    Preferred,
141    Required,
142}
143
144impl ResidentKeyRequirement {
145    pub(crate) fn as_str(self) -> &'static str {
146        match self {
147            Self::Discouraged => "discouraged",
148            Self::Preferred => "preferred",
149            Self::Required => "required",
150        }
151    }
152}
153
154/// User verification preference used in WebAuthn options.
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
156pub enum UserVerificationRequirement {
157    Discouraged,
158    Preferred,
159    Required,
160}
161
162impl UserVerificationRequirement {
163    pub(crate) fn as_str(self) -> &'static str {
164        match self {
165            Self::Discouraged => "discouraged",
166            Self::Preferred => "preferred",
167            Self::Required => "required",
168        }
169    }
170}
171
172/// Authenticator selection hints for generated registration options.
173#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
174pub struct AuthenticatorSelection {
175    pub resident_key: ResidentKeyRequirement,
176    pub user_verification: UserVerificationRequirement,
177    pub authenticator_attachment: Option<AuthenticatorAttachment>,
178}
179
180impl Default for AuthenticatorSelection {
181    fn default() -> Self {
182        Self {
183            resident_key: ResidentKeyRequirement::Preferred,
184            user_verification: UserVerificationRequirement::Preferred,
185            authenticator_attachment: None,
186        }
187    }
188}
189
190impl AuthenticatorSelection {
191    pub fn new() -> Self {
192        Self::default()
193    }
194
195    #[must_use]
196    pub fn resident_key(mut self, resident_key: ResidentKeyRequirement) -> Self {
197        self.resident_key = resident_key;
198        self
199    }
200
201    #[must_use]
202    pub fn user_verification(mut self, user_verification: UserVerificationRequirement) -> Self {
203        self.user_verification = user_verification;
204        self
205    }
206
207    #[must_use]
208    pub fn authenticator_attachment(mut self, attachment: AuthenticatorAttachment) -> Self {
209        self.authenticator_attachment = Some(attachment);
210        self
211    }
212
213    pub(crate) fn with_attachment_override(
214        &self,
215        attachment: Option<AuthenticatorAttachment>,
216    ) -> Self {
217        let mut selection = self.clone();
218        if attachment.is_some() {
219            selection.authenticator_attachment = attachment;
220        }
221        selection
222    }
223
224    pub fn to_json(&self) -> Value {
225        let mut value = json!({
226            "residentKey": self.resident_key.as_str(),
227            "userVerification": self.user_verification.as_str(),
228        });
229        if let Some(attachment) = self.authenticator_attachment {
230            value["authenticatorAttachment"] = json!(attachment.as_str());
231        }
232        value
233    }
234}
235
236/// WebAuthn option customizations resolved for one registration request.
237#[derive(Debug, Clone, PartialEq)]
238pub struct RegistrationWebAuthnOptions {
239    pub authenticator_selection: AuthenticatorSelection,
240    pub extensions: Option<Value>,
241}
242
243impl RegistrationWebAuthnOptions {
244    pub(crate) fn new(
245        authenticator_selection: AuthenticatorSelection,
246        extensions: Option<Value>,
247    ) -> Self {
248        Self {
249            authenticator_selection,
250            extensions,
251        }
252    }
253}
254
255/// User identity used for passkey registration.
256#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
257pub struct PasskeyRegistrationUser {
258    pub id: String,
259    pub name: String,
260    pub display_name: Option<String>,
261}
262
263impl PasskeyRegistrationUser {
264    pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
265        Self {
266            id: id.into(),
267            name: name.into(),
268            display_name: None,
269        }
270    }
271
272    #[must_use]
273    pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
274        self.display_name = Some(display_name.into());
275        self
276    }
277}
278
279pub type ResolveRegistrationUser =
280    Arc<dyn Fn(ResolveRegistrationUserInput) -> Option<PasskeyRegistrationUser> + Send + Sync>;
281
282pub type AfterRegistrationVerification =
283    Arc<dyn Fn(AfterRegistrationVerificationInput) -> Option<String> + Send + Sync>;
284
285pub type AfterAuthenticationVerification =
286    Arc<dyn Fn(AfterAuthenticationVerificationInput) + Send + Sync>;
287
288#[derive(Clone)]
289pub struct PasskeyRegistrationOptions {
290    pub require_session: bool,
291    pub resolve_user: Option<ResolveRegistrationUser>,
292    pub after_verification: Option<AfterRegistrationVerification>,
293    pub extensions: Option<Value>,
294}
295
296impl Default for PasskeyRegistrationOptions {
297    fn default() -> Self {
298        Self {
299            require_session: true,
300            resolve_user: None,
301            after_verification: None,
302            extensions: None,
303        }
304    }
305}
306
307impl PasskeyRegistrationOptions {
308    pub fn new() -> Self {
309        Self::default()
310    }
311
312    #[must_use]
313    pub fn require_session(mut self, require_session: bool) -> Self {
314        self.require_session = require_session;
315        self
316    }
317
318    #[must_use]
319    pub fn resolve_user<F>(mut self, resolver: F) -> Self
320    where
321        F: Fn(ResolveRegistrationUserInput) -> Option<PasskeyRegistrationUser>
322            + Send
323            + Sync
324            + 'static,
325    {
326        self.resolve_user = Some(Arc::new(resolver));
327        self
328    }
329
330    #[must_use]
331    pub fn after_verification<F>(mut self, callback: F) -> Self
332    where
333        F: Fn(AfterRegistrationVerificationInput) -> Option<String> + Send + Sync + 'static,
334    {
335        self.after_verification = Some(Arc::new(callback));
336        self
337    }
338
339    #[must_use]
340    pub fn extensions(mut self, extensions: Value) -> Self {
341        self.extensions = Some(extensions);
342        self
343    }
344}
345
346#[derive(Clone, Default)]
347pub struct PasskeyAuthenticationOptions {
348    pub after_verification: Option<AfterAuthenticationVerification>,
349    pub extensions: Option<Value>,
350}
351
352impl PasskeyAuthenticationOptions {
353    pub fn new() -> Self {
354        Self::default()
355    }
356
357    #[must_use]
358    pub fn after_verification<F>(mut self, callback: F) -> Self
359    where
360        F: Fn(AfterAuthenticationVerificationInput) + Send + Sync + 'static,
361    {
362        self.after_verification = Some(Arc::new(callback));
363        self
364    }
365
366    #[must_use]
367    pub fn extensions(mut self, extensions: Value) -> Self {
368        self.extensions = Some(extensions);
369        self
370    }
371}
372
373#[derive(Debug, Clone, PartialEq, Eq)]
374pub struct ResolveRegistrationUserInput {
375    pub context: Option<String>,
376}
377
378#[derive(Debug, Clone, PartialEq)]
379pub struct AfterRegistrationVerificationInput {
380    pub user: PasskeyRegistrationUser,
381    pub client_data: Value,
382    pub context: Option<String>,
383}
384
385#[derive(Debug, Clone, PartialEq)]
386pub struct AfterAuthenticationVerificationInput {
387    pub credential_id: String,
388    pub client_data: Value,
389}