Skip to main content

alibi_plugins/email_otp/
types.rs

1use crate::authentication_helpers::{JsonField, JsonFieldKind, RequestBody};
2use serde::{Deserialize, Serialize};
3
4/// Code scope. Codes issued for one operation cannot authorize another.
5#[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/// Delivery data. Debug intentionally omits the authentication secret.
30#[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}