better_auth_api/plugins/phone_number/
callbacks.rs1use super::{PhoneNumberPlugin, PhoneOtp, PhoneVerification};
2use crate::plugins::endpoint_context::{EndpointContext, WithCallbacks};
3use better_auth_core::{AuthPlugin, AuthResult, AuthSchema, background::BackgroundFuture};
4use std::{future::Future, pin::Pin, sync::Arc};
5
6pub type PhoneCallbackFuture<'a, T> = Pin<Box<dyn Future<Output = AuthResult<T>> + Send + 'a>>;
8type OtpCallback<S, T> = dyn for<'a> Fn(&'a PhoneOtp, &'a EndpointContext<'_, S>) -> PhoneCallbackFuture<'a, T>
9 + Send
10 + Sync;
11type Sender<S> = dyn Fn(&PhoneOtp, &EndpointContext<'_, S>) -> AuthResult<Option<BackgroundFuture>>
12 + Send
13 + Sync;
14type VerifiedCallback<S> = dyn for<'a> Fn(&'a PhoneVerification, &'a EndpointContext<'_, S>) -> PhoneCallbackFuture<'a, ()>
15 + Send
16 + Sync;
17
18pub struct PhoneNumberCallbacks<S: AuthSchema> {
20 pub(super) send: Option<Arc<Sender<S>>>,
21 pub(super) reset: Option<Arc<Sender<S>>>,
22 pub(super) verify: Option<Arc<OtpCallback<S, bool>>>,
23 pub(super) verified: Option<Arc<VerifiedCallback<S>>>,
24}
25impl<S: AuthSchema> Default for PhoneNumberCallbacks<S> {
26 fn default() -> Self {
27 Self {
28 send: None,
29 reset: None,
30 verify: None,
31 verified: None,
32 }
33 }
34}
35impl<S: AuthSchema> PhoneNumberCallbacks<S> {
36 pub fn send_otp<F>(mut self, callback: F) -> Self
39 where
40 F: Fn(&PhoneOtp, &EndpointContext<'_, S>) -> AuthResult<Option<BackgroundFuture>>
41 + Send
42 + Sync
43 + 'static,
44 {
45 self.send = Some(Arc::new(callback));
46 self
47 }
48 pub fn send_password_reset_otp<F>(mut self, callback: F) -> Self
50 where
51 F: Fn(&PhoneOtp, &EndpointContext<'_, S>) -> AuthResult<Option<BackgroundFuture>>
52 + Send
53 + Sync
54 + 'static,
55 {
56 self.reset = Some(Arc::new(callback));
57 self
58 }
59 pub fn verify_otp<F>(mut self, callback: F) -> Self
61 where
62 F: for<'a> Fn(&'a PhoneOtp, &'a EndpointContext<'_, S>) -> PhoneCallbackFuture<'a, bool>
63 + Send
64 + Sync
65 + 'static,
66 {
67 self.verify = Some(Arc::new(callback));
68 self
69 }
70 pub fn on_verification<F>(mut self, callback: F) -> Self
72 where
73 F: for<'a> Fn(
74 &'a PhoneVerification,
75 &'a EndpointContext<'_, S>,
76 ) -> PhoneCallbackFuture<'a, ()>
77 + Send
78 + Sync
79 + 'static,
80 {
81 self.verified = Some(Arc::new(callback));
82 self
83 }
84}
85#[derive(Clone, Copy)]
86pub(super) enum Delivery {
87 Verification,
88 PasswordReset,
89}
90impl PhoneNumberPlugin {
91 pub fn callbacks<S: AuthSchema>(
93 self,
94 callbacks: PhoneNumberCallbacks<S>,
95 ) -> impl AuthPlugin<S> {
96 WithCallbacks {
97 plugin: self,
98 callbacks: Arc::new(callbacks),
99 }
100 }
101 pub(super) fn has_sender<S: AuthSchema>(
102 &self,
103 ctx: &better_auth_core::AuthContext<S>,
104 kind: Delivery,
105 ) -> bool {
106 let callbacks = ctx.extensions.get::<Arc<PhoneNumberCallbacks<S>>>();
107 match kind {
108 Delivery::Verification => {
109 self.send_otp.is_some()
110 || callbacks.is_some_and(|callbacks| callbacks.send.is_some())
111 }
112 Delivery::PasswordReset => {
113 self.send_password_reset_otp.is_some()
114 || callbacks.is_some_and(|callbacks| callbacks.reset.is_some())
115 }
116 }
117 }
118 pub(super) fn delivery<S: AuthSchema>(
119 &self,
120 otp: PhoneOtp,
121 endpoint: &EndpointContext<'_, S>,
122 kind: Delivery,
123 ) -> AuthResult<Option<BackgroundFuture>> {
124 let callbacks = endpoint
125 .auth
126 .extensions
127 .get::<Arc<PhoneNumberCallbacks<S>>>();
128 let (typed, legacy) = match kind {
129 Delivery::Verification => (
130 callbacks.and_then(|callbacks| callbacks.send.as_ref()),
131 self.send_otp.as_ref(),
132 ),
133 Delivery::PasswordReset => (
134 callbacks.and_then(|callbacks| callbacks.reset.as_ref()),
135 self.send_password_reset_otp.as_ref(),
136 ),
137 };
138 if let Some(callback) = typed {
139 callback(&otp, endpoint)
140 } else if let Some(callback) = legacy {
141 let request = endpoint.request.ok_or_else(|| {
142 better_auth_core::AuthError::config("Legacy phone callbacks require a request")
143 })?;
144 Ok(Some(callback(otp, request.clone())))
145 } else {
146 Err(super::error(
147 501,
148 "SEND_OTP_NOT_IMPLEMENTED",
149 "sendOTP not implemented",
150 ))
151 }
152 }
153 pub(super) async fn notify_verified<S: AuthSchema>(
154 &self,
155 data: PhoneVerification,
156 endpoint: &EndpointContext<'_, S>,
157 ) -> AuthResult<()> {
158 if let Some(callback) = endpoint
159 .auth
160 .extensions
161 .get::<Arc<PhoneNumberCallbacks<S>>>()
162 .and_then(|callbacks| callbacks.verified.as_ref())
163 {
164 callback(&data, endpoint).await?;
165 } else if let Some(callback) = &self.callback_on_verification {
166 let request = endpoint.request.ok_or_else(|| {
167 better_auth_core::AuthError::config("Legacy phone callbacks require a request")
168 })?;
169 callback(data, request.clone()).await?;
170 }
171 Ok(())
172 }
173}