Skip to main content

rustauth_plugins/phone_number/
options.rs

1use std::sync::Arc;
2
3use rustauth_core::error::RustAuthError;
4use time::Duration;
5
6/// Synchronous OTP sender callback.
7///
8/// The Rust plugin intentionally exposes sync callback types today. If the
9/// sender needs async I/O, bridge that work in application code before
10/// returning from the callback.
11pub type PhoneNumberSender =
12    Arc<dyn Fn(&str, &str) -> Result<(), RustAuthError> + Send + Sync + 'static>;
13/// Synchronous OTP verifier callback.
14///
15/// Use this to delegate verification to an external OTP store or provider. The
16/// callback is sync-only in the current Rust API.
17pub type PhoneNumberVerifier =
18    Arc<dyn Fn(&str, &str) -> Result<bool, RustAuthError> + Send + Sync + 'static>;
19/// Synchronous phone-number validation callback.
20pub type PhoneNumberValidator =
21    Arc<dyn Fn(&str) -> Result<bool, RustAuthError> + Send + Sync + 'static>;
22/// Synchronous post-verification callback receiving `(phone_number, user_id)`.
23pub type PhoneNumberCallback =
24    Arc<dyn Fn(&str, &str) -> Result<(), RustAuthError> + Send + Sync + 'static>;
25/// Synchronous temporary value callback used during sign-up-on-verification.
26pub type PhoneNumberTempValue = Arc<dyn Fn(&str) -> String + Send + Sync + 'static>;
27
28#[derive(Clone)]
29pub struct SignUpOnVerification {
30    pub get_temp_email: PhoneNumberTempValue,
31    pub get_temp_name: Option<PhoneNumberTempValue>,
32}
33
34#[derive(Clone)]
35pub struct PhoneNumberOptions {
36    pub otp_length: usize,
37    pub expires_in: Duration,
38    pub allowed_attempts: u32,
39    pub require_verification: bool,
40    /// Sync-only OTP sender callback.
41    pub send_otp: Option<PhoneNumberSender>,
42    /// Sync-only custom OTP verifier callback.
43    pub verify_otp: Option<PhoneNumberVerifier>,
44    /// Sync-only password-reset OTP sender callback.
45    pub send_password_reset_otp: Option<PhoneNumberSender>,
46    /// Sync-only callback invoked after successful phone verification.
47    pub callback_on_verification: Option<PhoneNumberCallback>,
48    /// Sync-only phone-number validator callback.
49    pub phone_number_validator: Option<PhoneNumberValidator>,
50    pub sign_up_on_verification: Option<SignUpOnVerification>,
51    pub schema: super::schema::PhoneNumberSchemaOptions,
52}
53
54impl Default for PhoneNumberOptions {
55    fn default() -> Self {
56        Self {
57            otp_length: 6,
58            expires_in: Duration::minutes(5),
59            allowed_attempts: 3,
60            require_verification: false,
61            send_otp: None,
62            verify_otp: None,
63            send_password_reset_otp: None,
64            callback_on_verification: None,
65            phone_number_validator: None,
66            sign_up_on_verification: None,
67            schema: super::schema::PhoneNumberSchemaOptions::default(),
68        }
69    }
70}
71
72impl PhoneNumberOptions {
73    #[must_use]
74    pub fn new<F>(sender: F) -> Self
75    where
76        F: Fn(&str, &str) -> Result<(), RustAuthError> + Send + Sync + 'static,
77    {
78        Self {
79            send_otp: Some(Arc::new(sender)),
80            ..Self::default()
81        }
82    }
83
84    pub(crate) fn with_defaults(mut self) -> Self {
85        if self.otp_length == 0 {
86            self.otp_length = 6;
87        }
88        if self.expires_in == Duration::ZERO {
89            self.expires_in = Duration::minutes(5);
90        }
91        if self.allowed_attempts == 0 {
92            self.allowed_attempts = 3;
93        }
94        self
95    }
96
97    #[must_use]
98    pub fn expires_in(mut self, expires_in: Duration) -> Self {
99        self.expires_in = expires_in;
100        self
101    }
102
103    #[must_use]
104    pub fn send_otp<F>(mut self, sender: F) -> Self
105    where
106        F: Fn(&str, &str) -> Result<(), RustAuthError> + Send + Sync + 'static,
107    {
108        self.send_otp = Some(Arc::new(sender));
109        self
110    }
111
112    #[must_use]
113    pub fn verify_otp<F>(mut self, verifier: F) -> Self
114    where
115        F: Fn(&str, &str) -> Result<bool, RustAuthError> + Send + Sync + 'static,
116    {
117        self.verify_otp = Some(Arc::new(verifier));
118        self
119    }
120
121    #[must_use]
122    pub fn send_password_reset_otp<F>(mut self, sender: F) -> Self
123    where
124        F: Fn(&str, &str) -> Result<(), RustAuthError> + Send + Sync + 'static,
125    {
126        self.send_password_reset_otp = Some(Arc::new(sender));
127        self
128    }
129
130    #[must_use]
131    pub fn phone_number_validator<F>(mut self, validator: F) -> Self
132    where
133        F: Fn(&str) -> Result<bool, RustAuthError> + Send + Sync + 'static,
134    {
135        self.phone_number_validator = Some(Arc::new(validator));
136        self
137    }
138
139    #[must_use]
140    pub fn callback_on_verification<F>(mut self, callback: F) -> Self
141    where
142        F: Fn(&str, &str) -> Result<(), RustAuthError> + Send + Sync + 'static,
143    {
144        self.callback_on_verification = Some(Arc::new(callback));
145        self
146    }
147
148    #[must_use]
149    pub fn sign_up_on_verification(mut self, options: SignUpOnVerification) -> Self {
150        self.sign_up_on_verification = Some(options);
151        self
152    }
153
154    #[must_use]
155    pub fn require_verification(mut self, require_verification: bool) -> Self {
156        self.require_verification = require_verification;
157        self
158    }
159
160    pub fn validate(&self) -> Result<(), RustAuthError> {
161        if self.otp_length == 0 {
162            return Err(RustAuthError::InvalidConfig(
163                "phone-number otp_length must be greater than zero".to_owned(),
164            ));
165        }
166        if self.expires_in.is_zero() {
167            return Err(RustAuthError::InvalidConfig(
168                "phone-number expires_in must be greater than zero".to_owned(),
169            ));
170        }
171        if self.allowed_attempts == 0 {
172            return Err(RustAuthError::InvalidConfig(
173                "phone-number allowed_attempts must be greater than zero".to_owned(),
174            ));
175        }
176        if self.send_otp.is_none() {
177            return Err(RustAuthError::InvalidConfig(
178                "phone-number send_otp callback is required".to_owned(),
179            ));
180        }
181        Ok(())
182    }
183}
184
185#[derive(Clone, Default)]
186pub struct PhoneNumberOptionsBuilder {
187    otp_length: Option<usize>,
188    expires_in: Option<Duration>,
189    allowed_attempts: Option<u32>,
190    require_verification: Option<bool>,
191    send_otp: Option<PhoneNumberSender>,
192    verify_otp: Option<PhoneNumberVerifier>,
193    send_password_reset_otp: Option<PhoneNumberSender>,
194    callback_on_verification: Option<PhoneNumberCallback>,
195    phone_number_validator: Option<PhoneNumberValidator>,
196    sign_up_on_verification: Option<SignUpOnVerification>,
197    schema: Option<super::schema::PhoneNumberSchemaOptions>,
198}
199
200impl PhoneNumberOptionsBuilder {
201    pub fn build(self) -> Result<PhoneNumberOptions, RustAuthError> {
202        let defaults = PhoneNumberOptions::default();
203        let options = PhoneNumberOptions {
204            otp_length: self.otp_length.unwrap_or(defaults.otp_length),
205            expires_in: self.expires_in.unwrap_or(defaults.expires_in),
206            allowed_attempts: self.allowed_attempts.unwrap_or(defaults.allowed_attempts),
207            require_verification: self
208                .require_verification
209                .unwrap_or(defaults.require_verification),
210            send_otp: self.send_otp.or(defaults.send_otp),
211            verify_otp: self.verify_otp.or(defaults.verify_otp),
212            send_password_reset_otp: self
213                .send_password_reset_otp
214                .or(defaults.send_password_reset_otp),
215            callback_on_verification: self
216                .callback_on_verification
217                .or(defaults.callback_on_verification),
218            phone_number_validator: self
219                .phone_number_validator
220                .or(defaults.phone_number_validator),
221            sign_up_on_verification: self
222                .sign_up_on_verification
223                .or(defaults.sign_up_on_verification),
224            schema: self.schema.unwrap_or(defaults.schema),
225        };
226        options.validate()?;
227        Ok(options)
228    }
229}