1use crate::authentication_helpers::{JsonField, JsonFieldKind, RequestBody};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6pub enum EmailOtpType {
7 #[serde(rename = "email-verification")]
8 EmailVerification,
9 #[serde(rename = "sign-in")]
10 SignIn,
11 #[serde(rename = "forget-password")]
12 ForgetPassword,
13 #[serde(rename = "change-email")]
14 ChangeEmail,
15}
16
17impl EmailOtpType {
18 #[must_use]
19 pub const fn as_str(self) -> &'static str {
20 match self {
21 Self::EmailVerification => "email-verification",
22 Self::SignIn => "sign-in",
23 Self::ForgetPassword => "forget-password",
24 Self::ChangeEmail => "change-email",
25 }
26 }
27}
28
29#[derive(Clone)]
31pub struct EmailOtpDelivery {
32 pub email: String,
33 pub otp: String,
34 pub otp_type: EmailOtpType,
35}
36
37impl std::fmt::Debug for EmailOtpDelivery {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 f.debug_struct("EmailOtpDelivery")
40 .field("otp_type", &self.otp_type)
41 .finish_non_exhaustive()
42 }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum OtpResendStrategy {
47 Rotate,
48 Reuse,
49}
50
51#[derive(Deserialize)]
52pub(super) struct SendRequest {
53 pub email: String,
54 #[serde(rename = "type")]
55 pub otp_type: EmailOtpType,
56}
57
58#[derive(Deserialize)]
59pub(super) struct CheckRequest {
60 pub email: String,
61 #[serde(rename = "type")]
62 pub otp_type: EmailOtpType,
63 pub otp: String,
64}
65
66#[derive(Deserialize)]
67pub(super) struct VerifyRequest {
68 pub email: String,
69 pub otp: String,
70}
71
72#[derive(Deserialize)]
73pub(super) struct SignInRequest {
74 pub email: String,
75 pub otp: String,
76 pub name: Option<String>,
77 pub image: Option<String>,
78 pub username: Option<String>,
79 #[serde(rename = "displayUsername")]
80 pub display_username: Option<String>,
81}
82
83#[derive(Deserialize)]
84pub(super) struct EmailRequest {
85 pub email: String,
86}
87
88#[derive(Deserialize)]
89pub(super) struct PasswordRequest {
90 pub email: String,
91 pub otp: String,
92 pub password: String,
93}
94
95#[derive(Deserialize)]
96pub(super) struct ChangeEmailRequest {
97 #[serde(rename = "newEmail")]
98 pub new_email: String,
99 pub otp: Option<String>,
100}
101
102#[derive(Deserialize)]
103pub(super) struct ConfirmChangeRequest {
104 #[serde(rename = "newEmail")]
105 pub new_email: String,
106 pub otp: String,
107}
108
109macro_rules! request_fields {
110 ($name:ty, $($field:expr),+ $(,)?) => {
111 impl RequestBody for $name { const FIELDS: &'static [JsonField] = &[$($field),+]; }
112 };
113}
114
115request_fields!(
116 SendRequest,
117 JsonField::string("email", true),
118 JsonField {
119 name: "type",
120 kind: JsonFieldKind::OneOf(&[
121 "email-verification",
122 "sign-in",
123 "forget-password",
124 "change-email"
125 ]),
126 required: true
127 }
128);
129request_fields!(
130 CheckRequest,
131 JsonField::string("email", true),
132 JsonField {
133 name: "type",
134 kind: JsonFieldKind::OneOf(&[
135 "email-verification",
136 "sign-in",
137 "forget-password",
138 "change-email"
139 ]),
140 required: true
141 },
142 JsonField::string("otp", true)
143);
144request_fields!(
145 VerifyRequest,
146 JsonField::string("email", true),
147 JsonField::string("otp", true)
148);
149request_fields!(
150 SignInRequest,
151 JsonField::string("email", true),
152 JsonField::string("otp", true),
153 JsonField::string("name", false),
154 JsonField::string("image", false)
155);
156request_fields!(EmailRequest, JsonField::string("email", true));
157request_fields!(
158 PasswordRequest,
159 JsonField::string("email", true),
160 JsonField::string("otp", true),
161 JsonField::string("password", true)
162);
163request_fields!(
164 ChangeEmailRequest,
165 JsonField::string("newEmail", true),
166 JsonField::string("otp", false)
167);
168request_fields!(
169 ConfirmChangeRequest,
170 JsonField::string("newEmail", true),
171 JsonField::string("otp", true)
172);
173
174pub(super) fn identifier(otp_type: EmailOtpType, email: &str) -> String {
175 format!("{}-otp-{email}", otp_type.as_str())
176}
177
178pub(super) fn split_value(value: &str) -> (&str, usize) {
179 match value.rsplit_once(':') {
180 Some((code, attempts)) => (code, attempts.parse().unwrap_or(0)),
181 None => (value, 0),
182 }
183}