openauth_plugins/email_otp/
types.rs1use std::fmt;
2use std::sync::Arc;
3
4use http::Request;
5use openauth_core::error::OpenAuthError;
6use openauth_core::options::RateLimitRule;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum EmailOtpType {
10 EmailVerification,
11 SignIn,
12 ForgetPassword,
13 ChangeEmail,
14}
15
16impl EmailOtpType {
17 pub fn as_str(self) -> &'static str {
18 match self {
19 Self::EmailVerification => "email-verification",
20 Self::SignIn => "sign-in",
21 Self::ForgetPassword => "forget-password",
22 Self::ChangeEmail => "change-email",
23 }
24 }
25}
26
27impl TryFrom<&str> for EmailOtpType {
28 type Error = ();
29
30 fn try_from(value: &str) -> Result<Self, Self::Error> {
31 match value {
32 "email-verification" => Ok(Self::EmailVerification),
33 "sign-in" => Ok(Self::SignIn),
34 "forget-password" => Ok(Self::ForgetPassword),
35 "change-email" => Ok(Self::ChangeEmail),
36 _ => Err(()),
37 }
38 }
39}
40
41pub trait EmailOtpHasher: Send + Sync + 'static {
42 fn hash_otp(&self, otp: &str) -> Result<String, OpenAuthError>;
43}
44
45impl<F> EmailOtpHasher for F
46where
47 F: Fn(&str) -> Result<String, OpenAuthError> + Send + Sync + 'static,
48{
49 fn hash_otp(&self, otp: &str) -> Result<String, OpenAuthError> {
50 self(otp)
51 }
52}
53
54pub trait EmailOtpEncryptor: Send + Sync + 'static {
55 fn encrypt_otp(&self, otp: &str) -> Result<String, OpenAuthError>;
56 fn decrypt_otp(&self, stored: &str) -> Result<String, OpenAuthError>;
57}
58
59#[derive(Clone, Default)]
60pub enum OtpStorage {
61 #[default]
62 Plain,
63 Hashed,
64 Encrypted,
65 CustomHash(Arc<dyn EmailOtpHasher>),
66 CustomEncrypt(Arc<dyn EmailOtpEncryptor>),
67}
68
69impl fmt::Debug for OtpStorage {
70 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
71 match self {
72 Self::Plain => formatter.write_str("Plain"),
73 Self::Hashed => formatter.write_str("Hashed"),
74 Self::Encrypted => formatter.write_str("Encrypted"),
75 Self::CustomHash(_) => formatter.write_str("CustomHash(<hasher>)"),
76 Self::CustomEncrypt(_) => formatter.write_str("CustomEncrypt(<encryptor>)"),
77 }
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
82pub enum ResendStrategy {
83 #[default]
84 Rotate,
85 Reuse,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
89pub struct ChangeEmailOptions {
90 pub enabled: bool,
91 pub verify_current_email: bool,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct EmailOtpPayload {
96 pub email: String,
97 pub otp: String,
98 pub otp_type: EmailOtpType,
99}
100
101pub trait SendEmailOtp: Send + Sync + 'static {
102 fn send_email_otp(
103 &self,
104 payload: EmailOtpPayload,
105 request: Option<&Request<Vec<u8>>>,
106 ) -> Result<(), OpenAuthError>;
107}
108
109impl<F> SendEmailOtp for F
110where
111 F: for<'a> Fn(EmailOtpPayload, Option<&'a Request<Vec<u8>>>) -> Result<(), OpenAuthError>
112 + Send
113 + Sync
114 + 'static,
115{
116 fn send_email_otp(
117 &self,
118 payload: EmailOtpPayload,
119 request: Option<&Request<Vec<u8>>>,
120 ) -> Result<(), OpenAuthError> {
121 self(payload, request)
122 }
123}
124
125pub trait EmailOtpGenerator: Send + Sync + 'static {
126 fn generate_otp(&self, email: &str, otp_type: EmailOtpType, length: usize) -> String;
127}
128
129impl<F> EmailOtpGenerator for F
130where
131 F: Fn(&str, EmailOtpType, usize) -> String + Send + Sync + 'static,
132{
133 fn generate_otp(&self, email: &str, otp_type: EmailOtpType, length: usize) -> String {
134 self(email, otp_type, length)
135 }
136}
137
138#[derive(Clone)]
139pub struct EmailOtpOptions {
140 pub sender: Option<Arc<dyn SendEmailOtp>>,
141 pub generator: Option<Arc<dyn EmailOtpGenerator>>,
142 pub otp_length: usize,
143 pub expires_in: u64,
144 pub send_verification_on_sign_up: bool,
145 pub override_default_email_verification: bool,
146 pub disable_sign_up: bool,
147 pub allowed_attempts: u32,
148 pub store_otp: OtpStorage,
149 pub resend_strategy: ResendStrategy,
150 pub change_email: ChangeEmailOptions,
151 pub rate_limit: Option<RateLimitRule>,
152}
153
154impl Default for EmailOtpOptions {
155 fn default() -> Self {
156 Self {
157 sender: None,
158 generator: None,
159 otp_length: 6,
160 expires_in: 5 * 60,
161 send_verification_on_sign_up: false,
162 override_default_email_verification: false,
163 disable_sign_up: false,
164 allowed_attempts: 3,
165 store_otp: OtpStorage::Plain,
166 resend_strategy: ResendStrategy::Rotate,
167 change_email: ChangeEmailOptions::default(),
168 rate_limit: None,
169 }
170 }
171}
172
173impl fmt::Debug for EmailOtpOptions {
174 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
175 formatter
176 .debug_struct("EmailOtpOptions")
177 .field("sender", &self.sender.as_ref().map(|_| "<sender>"))
178 .field("generator", &self.generator.as_ref().map(|_| "<generator>"))
179 .field("otp_length", &self.otp_length)
180 .field("expires_in", &self.expires_in)
181 .field(
182 "send_verification_on_sign_up",
183 &self.send_verification_on_sign_up,
184 )
185 .field(
186 "override_default_email_verification",
187 &self.override_default_email_verification,
188 )
189 .field("disable_sign_up", &self.disable_sign_up)
190 .field("allowed_attempts", &self.allowed_attempts)
191 .field("store_otp", &self.store_otp)
192 .field("resend_strategy", &self.resend_strategy)
193 .field("change_email", &self.change_email)
194 .field("rate_limit", &self.rate_limit)
195 .finish()
196 }
197}