1use std::future::{ready, Future};
2use std::pin::Pin;
3use std::sync::Arc;
4
5use indexmap::IndexMap;
6use rustauth_core::options::RateLimitRule;
7use serde::{Deserialize, Serialize};
8use serde_json::{json, Value};
9use time::Duration;
10
11use crate::webauthn::{PasskeyWebAuthnBackend, RealPasskeyWebAuthnBackend};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct PasskeyRateLimit {
18 pub window: Duration,
19 pub max: u64,
20}
21
22impl Default for PasskeyRateLimit {
23 fn default() -> Self {
24 Self {
25 window: Duration::seconds(10),
26 max: 3,
27 }
28 }
29}
30
31impl PasskeyRateLimit {
32 pub fn new() -> Self {
33 Self::default()
34 }
35
36 #[must_use]
37 pub fn window(mut self, window: Duration) -> Self {
38 self.window = window;
39 self
40 }
41
42 #[must_use]
43 pub fn max(mut self, max: u64) -> Self {
44 self.max = max;
45 self
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct PasskeyChallengeRateLimit {
56 pub window: Duration,
57 pub max: u64,
58}
59
60impl Default for PasskeyChallengeRateLimit {
61 fn default() -> Self {
62 Self {
63 window: Duration::minutes(5),
64 max: 5,
65 }
66 }
67}
68
69impl PasskeyChallengeRateLimit {
70 pub fn new() -> Self {
71 Self::default()
72 }
73
74 #[must_use]
75 pub fn window(mut self, window: Duration) -> Self {
76 self.window = window;
77 self
78 }
79
80 #[must_use]
81 pub fn max(mut self, max: u64) -> Self {
82 self.max = max;
83 self
84 }
85
86 #[must_use]
88 pub fn disabled(mut self) -> Self {
89 self.max = 0;
90 self
91 }
92
93 pub(crate) fn rule(&self) -> Option<RateLimitRule> {
94 if self.max == 0 || self.window.is_zero() {
95 return None;
96 }
97 Some(RateLimitRule {
98 window: self.window,
99 max: self.max,
100 })
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct PasskeyAdvancedOptions {
107 pub webauthn_challenge_cookie: String,
108}
109
110impl Default for PasskeyAdvancedOptions {
111 fn default() -> Self {
112 Self {
113 webauthn_challenge_cookie: "better-auth-passkey".to_owned(),
114 }
115 }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub struct PasskeyManagementOptions {
121 pub require_fresh_session: bool,
123}
124
125impl Default for PasskeyManagementOptions {
126 fn default() -> Self {
127 Self {
128 require_fresh_session: true,
129 }
130 }
131}
132
133impl PasskeyManagementOptions {
134 pub fn new() -> Self {
135 Self::default()
136 }
137
138 #[must_use]
139 pub fn require_fresh_session(mut self, require_fresh_session: bool) -> Self {
140 self.require_fresh_session = require_fresh_session;
141 self
142 }
143}
144
145#[derive(Debug, Clone, Default, PartialEq, Eq)]
150pub struct PasskeySchemaOptions {
151 pub table_name: Option<String>,
152 pub field_names: IndexMap<String, String>,
153}
154
155impl PasskeySchemaOptions {
156 pub fn new() -> Self {
157 Self::default()
158 }
159
160 #[must_use]
161 pub fn table_name(mut self, table_name: impl Into<String>) -> Self {
162 self.table_name = Some(table_name.into());
163 self
164 }
165
166 #[must_use]
167 pub fn field_name(
168 mut self,
169 logical_name: impl Into<String>,
170 database_name: impl Into<String>,
171 ) -> Self {
172 self.field_names
173 .insert(logical_name.into(), database_name.into());
174 self
175 }
176
177 pub(crate) fn table_name_or<'a>(&'a self, default_name: &'a str) -> &'a str {
178 self.table_name.as_deref().unwrap_or(default_name)
179 }
180
181 pub(crate) fn field_name_or<'a>(
182 &'a self,
183 logical_name: &str,
184 default_name: &'a str,
185 ) -> &'a str {
186 self.field_names
187 .get(logical_name)
188 .map(String::as_str)
189 .unwrap_or(default_name)
190 }
191}
192
193#[derive(Clone)]
195pub struct PasskeyOptions {
196 pub rp_id: Option<String>,
197 pub rp_name: Option<String>,
198 pub origin: Vec<String>,
199 pub passkey_table: String,
200 pub schema: PasskeySchemaOptions,
201 pub authenticator_selection: AuthenticatorSelection,
202 pub registration: PasskeyRegistrationOptions,
203 pub authentication: PasskeyAuthenticationOptions,
204 pub management: PasskeyManagementOptions,
205 pub advanced: PasskeyAdvancedOptions,
206 pub rate_limit: PasskeyRateLimit,
207 pub challenge_rate_limit: PasskeyChallengeRateLimit,
208 pub(crate) backend: Arc<dyn PasskeyWebAuthnBackend>,
209}
210
211impl Default for PasskeyOptions {
212 fn default() -> Self {
213 Self {
214 rp_id: None,
215 rp_name: None,
216 origin: Vec::new(),
217 passkey_table: "passkeys".to_owned(),
218 schema: PasskeySchemaOptions::default(),
219 authenticator_selection: AuthenticatorSelection::default(),
220 registration: PasskeyRegistrationOptions::default(),
221 authentication: PasskeyAuthenticationOptions::default(),
222 management: PasskeyManagementOptions::default(),
223 advanced: PasskeyAdvancedOptions::default(),
224 rate_limit: PasskeyRateLimit::default(),
225 challenge_rate_limit: PasskeyChallengeRateLimit::default(),
226 backend: Arc::new(RealPasskeyWebAuthnBackend),
227 }
228 }
229}
230
231impl PasskeyOptions {
232 pub fn new() -> Self {
233 Self::default()
234 }
235
236 #[must_use]
237 pub fn rp_id(mut self, rp_id: impl Into<String>) -> Self {
238 self.rp_id = Some(rp_id.into());
239 self
240 }
241
242 #[must_use]
243 pub fn rp_name(mut self, rp_name: impl Into<String>) -> Self {
244 self.rp_name = Some(rp_name.into());
245 self
246 }
247
248 #[must_use]
249 pub fn origin(mut self, origin: impl Into<String>) -> Self {
250 self.origin.push(origin.into());
251 self
252 }
253
254 #[must_use]
255 pub fn passkey_table(mut self, table: impl Into<String>) -> Self {
256 self.passkey_table = table.into();
257 self
258 }
259
260 #[must_use]
261 pub fn schema(mut self, schema: PasskeySchemaOptions) -> Self {
262 self.schema = schema;
263 self
264 }
265
266 #[must_use]
267 pub fn authenticator_selection(mut self, selection: AuthenticatorSelection) -> Self {
268 self.authenticator_selection = selection;
269 self
270 }
271
272 #[must_use]
273 pub fn registration(mut self, registration: PasskeyRegistrationOptions) -> Self {
274 self.registration = registration;
275 self
276 }
277
278 #[must_use]
279 pub fn authentication(mut self, authentication: PasskeyAuthenticationOptions) -> Self {
280 self.authentication = authentication;
281 self
282 }
283
284 #[must_use]
285 pub fn management(mut self, management: PasskeyManagementOptions) -> Self {
286 self.management = management;
287 self
288 }
289
290 #[must_use]
291 pub fn advanced(mut self, advanced: PasskeyAdvancedOptions) -> Self {
292 self.advanced = advanced;
293 self
294 }
295
296 #[must_use]
297 pub fn rate_limit(mut self, rate_limit: PasskeyRateLimit) -> Self {
298 self.rate_limit = rate_limit;
299 self
300 }
301
302 #[must_use]
303 pub fn challenge_rate_limit(mut self, challenge_rate_limit: PasskeyChallengeRateLimit) -> Self {
304 self.challenge_rate_limit = challenge_rate_limit;
305 self
306 }
307
308 #[cfg(feature = "test-util")]
309 #[must_use]
311 pub fn backend(mut self, backend: Arc<dyn PasskeyWebAuthnBackend>) -> Self {
312 self.backend = backend;
313 self
314 }
315
316 pub(crate) fn rate_limit_rule(&self) -> RateLimitRule {
317 RateLimitRule {
318 window: self.rate_limit.window,
319 max: self.rate_limit.max,
320 }
321 }
322}
323
324#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
326pub enum AuthenticatorAttachment {
327 Platform,
328 CrossPlatform,
329}
330
331impl AuthenticatorAttachment {
332 pub(crate) fn from_query(value: &str) -> Option<Self> {
333 match value {
334 "platform" => Some(Self::Platform),
335 "cross-platform" => Some(Self::CrossPlatform),
336 _ => None,
337 }
338 }
339
340 pub(crate) fn as_str(self) -> &'static str {
341 match self {
342 Self::Platform => "platform",
343 Self::CrossPlatform => "cross-platform",
344 }
345 }
346}
347
348#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
350pub enum ResidentKeyRequirement {
351 Discouraged,
352 Preferred,
353 Required,
354}
355
356impl ResidentKeyRequirement {
357 pub(crate) fn as_str(self) -> &'static str {
358 match self {
359 Self::Discouraged => "discouraged",
360 Self::Preferred => "preferred",
361 Self::Required => "required",
362 }
363 }
364}
365
366#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
368pub enum UserVerificationRequirement {
369 Discouraged,
370 Preferred,
371 Required,
372}
373
374impl UserVerificationRequirement {
375 pub(crate) fn as_str(self) -> &'static str {
376 match self {
377 Self::Discouraged => "discouraged",
378 Self::Preferred => "preferred",
379 Self::Required => "required",
380 }
381 }
382}
383
384#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
386pub struct AuthenticatorSelection {
387 pub resident_key: ResidentKeyRequirement,
388 pub user_verification: UserVerificationRequirement,
389 pub authenticator_attachment: Option<AuthenticatorAttachment>,
390}
391
392impl Default for AuthenticatorSelection {
393 fn default() -> Self {
394 Self {
395 resident_key: ResidentKeyRequirement::Preferred,
396 user_verification: UserVerificationRequirement::Preferred,
397 authenticator_attachment: None,
398 }
399 }
400}
401
402impl AuthenticatorSelection {
403 pub fn new() -> Self {
404 Self::default()
405 }
406
407 #[must_use]
408 pub fn resident_key(mut self, resident_key: ResidentKeyRequirement) -> Self {
409 self.resident_key = resident_key;
410 self
411 }
412
413 #[must_use]
414 pub fn user_verification(mut self, user_verification: UserVerificationRequirement) -> Self {
415 self.user_verification = user_verification;
416 self
417 }
418
419 #[must_use]
420 pub fn authenticator_attachment(mut self, attachment: AuthenticatorAttachment) -> Self {
421 self.authenticator_attachment = Some(attachment);
422 self
423 }
424
425 pub(crate) fn with_attachment_override(
426 &self,
427 attachment: Option<AuthenticatorAttachment>,
428 ) -> Self {
429 let mut selection = self.clone();
430 if attachment.is_some() {
431 selection.authenticator_attachment = attachment;
432 }
433 selection
434 }
435
436 pub fn to_json(&self) -> Value {
437 let mut value = json!({
438 "residentKey": self.resident_key.as_str(),
439 "userVerification": self.user_verification.as_str(),
440 });
441 if let Some(attachment) = self.authenticator_attachment {
442 value["authenticatorAttachment"] = json!(attachment.as_str());
443 }
444 value
445 }
446}
447
448#[derive(Debug, Clone, PartialEq)]
450pub struct RegistrationWebAuthnOptions {
451 pub authenticator_selection: AuthenticatorSelection,
452 pub extensions: Option<Value>,
453}
454
455impl RegistrationWebAuthnOptions {
456 pub(crate) fn new(
457 authenticator_selection: AuthenticatorSelection,
458 extensions: Option<Value>,
459 ) -> Self {
460 Self {
461 authenticator_selection,
462 extensions,
463 }
464 }
465}
466
467#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
469pub struct PasskeyRegistrationUser {
470 pub id: String,
471 pub name: String,
472 pub display_name: Option<String>,
473}
474
475impl PasskeyRegistrationUser {
476 pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
477 Self {
478 id: id.into(),
479 name: name.into(),
480 display_name: None,
481 }
482 }
483
484 #[must_use]
485 pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
486 self.display_name = Some(display_name.into());
487 self
488 }
489}
490
491pub type PasskeyBoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send + 'static>>;
492
493pub type ResolveRegistrationUser = Arc<
494 dyn Fn(ResolveRegistrationUserInput) -> PasskeyBoxFuture<Option<PasskeyRegistrationUser>>
495 + Send
496 + Sync,
497>;
498
499pub type AfterRegistrationVerification = Arc<
500 dyn Fn(AfterRegistrationVerificationInput) -> PasskeyBoxFuture<Option<String>> + Send + Sync,
501>;
502
503#[derive(Debug, Clone, Copy, PartialEq, Eq)]
507pub struct PasskeyAuthenticationRejected;
508
509pub type AfterAuthenticationVerification = Arc<
510 dyn Fn(
511 AfterAuthenticationVerificationInput,
512 ) -> PasskeyBoxFuture<Result<(), PasskeyAuthenticationRejected>>
513 + Send
514 + Sync,
515>;
516
517pub type PasskeyExtensionsResolver =
518 Arc<dyn Fn(PasskeyExtensionsInput) -> PasskeyBoxFuture<Option<Value>> + Send + Sync>;
519
520#[derive(Clone)]
521pub struct PasskeyRegistrationOptions {
522 pub require_session: bool,
523 pub resolve_user: Option<ResolveRegistrationUser>,
524 pub after_verification: Option<AfterRegistrationVerification>,
525 pub extensions: Option<PasskeyExtensionsResolver>,
526}
527
528impl Default for PasskeyRegistrationOptions {
529 fn default() -> Self {
530 Self {
531 require_session: true,
532 resolve_user: None,
533 after_verification: None,
534 extensions: None,
535 }
536 }
537}
538
539impl PasskeyRegistrationOptions {
540 pub fn new() -> Self {
541 Self::default()
542 }
543
544 #[must_use]
545 pub fn require_session(mut self, require_session: bool) -> Self {
546 self.require_session = require_session;
547 self
548 }
549
550 #[must_use]
551 pub fn resolve_user<F>(mut self, resolver: F) -> Self
552 where
553 F: Fn(ResolveRegistrationUserInput) -> Option<PasskeyRegistrationUser>
554 + Send
555 + Sync
556 + 'static,
557 {
558 self.resolve_user = Some(Arc::new(move |input| Box::pin(ready(resolver(input)))));
559 self
560 }
561
562 #[must_use]
563 pub fn resolve_user_async<F, Fut>(mut self, resolver: F) -> Self
564 where
565 F: Fn(ResolveRegistrationUserInput) -> Fut + Send + Sync + 'static,
566 Fut: Future<Output = Option<PasskeyRegistrationUser>> + Send + 'static,
567 {
568 self.resolve_user = Some(Arc::new(move |input| Box::pin(resolver(input))));
569 self
570 }
571
572 #[must_use]
573 pub fn after_verification<F>(mut self, callback: F) -> Self
574 where
575 F: Fn(AfterRegistrationVerificationInput) -> Option<String> + Send + Sync + 'static,
576 {
577 self.after_verification = Some(Arc::new(move |input| Box::pin(ready(callback(input)))));
578 self
579 }
580
581 #[must_use]
582 pub fn after_verification_async<F, Fut>(mut self, callback: F) -> Self
583 where
584 F: Fn(AfterRegistrationVerificationInput) -> Fut + Send + Sync + 'static,
585 Fut: Future<Output = Option<String>> + Send + 'static,
586 {
587 self.after_verification = Some(Arc::new(move |input| Box::pin(callback(input))));
588 self
589 }
590
591 #[must_use]
592 pub fn extensions(mut self, extensions: Value) -> Self {
593 self.extensions = Some(Arc::new(move |_| Box::pin(ready(Some(extensions.clone())))));
594 self
595 }
596
597 #[must_use]
598 pub fn extensions_resolver<F, Fut>(mut self, resolver: F) -> Self
599 where
600 F: Fn(PasskeyExtensionsInput) -> Fut + Send + Sync + 'static,
601 Fut: Future<Output = Option<Value>> + Send + 'static,
602 {
603 self.extensions = Some(Arc::new(move |input| Box::pin(resolver(input))));
604 self
605 }
606}
607
608#[derive(Clone, Default)]
609pub struct PasskeyAuthenticationOptions {
610 pub after_verification: Option<AfterAuthenticationVerification>,
611 pub extensions: Option<PasskeyExtensionsResolver>,
612}
613
614impl PasskeyAuthenticationOptions {
615 pub fn new() -> Self {
616 Self::default()
617 }
618
619 #[must_use]
620 pub fn after_verification<F>(mut self, callback: F) -> Self
621 where
622 F: Fn(AfterAuthenticationVerificationInput) + Send + Sync + 'static,
623 {
624 self.after_verification = Some(Arc::new(move |input| {
625 callback(input);
626 Box::pin(ready(Ok(())))
627 }));
628 self
629 }
630
631 #[must_use]
632 pub fn after_verification_async<F, Fut>(mut self, callback: F) -> Self
633 where
634 F: Fn(AfterAuthenticationVerificationInput) -> Fut + Send + Sync + 'static,
635 Fut: Future<Output = Result<(), PasskeyAuthenticationRejected>> + Send + 'static,
636 {
637 self.after_verification = Some(Arc::new(move |input| Box::pin(callback(input))));
638 self
639 }
640
641 #[must_use]
642 pub fn extensions(mut self, extensions: Value) -> Self {
643 self.extensions = Some(Arc::new(move |_| Box::pin(ready(Some(extensions.clone())))));
644 self
645 }
646
647 #[must_use]
648 pub fn extensions_resolver<F, Fut>(mut self, resolver: F) -> Self
649 where
650 F: Fn(PasskeyExtensionsInput) -> Fut + Send + Sync + 'static,
651 Fut: Future<Output = Option<Value>> + Send + 'static,
652 {
653 self.extensions = Some(Arc::new(move |input| Box::pin(resolver(input))));
654 self
655 }
656}
657
658#[derive(Debug, Clone, PartialEq, Eq)]
659pub struct ResolveRegistrationUserInput {
660 pub context: Option<String>,
661}
662
663#[derive(Debug, Clone, PartialEq, Eq)]
664pub struct PasskeyExtensionsInput {
665 pub context: Option<String>,
666 pub user_id: Option<String>,
668}
669
670#[derive(Debug, Clone, PartialEq)]
671pub struct AfterRegistrationVerificationInput {
672 pub user: PasskeyRegistrationUser,
673 pub client_data: Value,
674 pub context: Option<String>,
675}
676
677#[derive(Debug, Clone, PartialEq)]
678pub struct AfterAuthenticationVerificationInput {
679 pub credential_id: String,
680 pub client_data: Value,
681}