alibi_plugins/phone_number/
types.rs1use crate::authentication_helpers::{JsonField, JsonFieldKind, RequestBody};
2use alibi_core::{AuthError, wire::UserView};
3use serde::Deserialize;
4
5#[derive(Clone)]
6pub struct PhoneOtpDelivery {
7 pub phone_number: String,
8 pub code: String,
9}
10
11impl std::fmt::Debug for PhoneOtpDelivery {
12 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13 f.debug_struct("PhoneOtpDelivery").finish_non_exhaustive()
14 }
15}
16
17#[derive(Clone)]
18pub struct PhoneNumberVerification {
19 pub phone_number: String,
20 pub user: UserView,
21}
22
23impl std::fmt::Debug for PhoneNumberVerification {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 f.debug_struct("PhoneNumberVerification")
26 .finish_non_exhaustive()
27 }
28}
29
30#[derive(Deserialize)]
31pub(super) struct SendRequest {
32 #[serde(rename = "phoneNumber")]
33 pub phone_number: String,
34}
35
36#[derive(Deserialize)]
37pub(super) struct SignInRequest {
38 #[serde(rename = "phoneNumber")]
39 pub phone_number: String,
40 pub password: String,
41 #[serde(rename = "rememberMe")]
42 pub remember_me: Option<bool>,
43}
44
45#[derive(Deserialize)]
46pub(super) struct VerifyRequest {
47 #[serde(rename = "phoneNumber")]
48 pub phone_number: String,
49 pub code: String,
50 #[serde(rename = "disableSession")]
51 pub disable_session: Option<bool>,
52 #[serde(rename = "updatePhoneNumber")]
53 pub update_phone_number: Option<bool>,
54 pub username: Option<String>,
55 #[serde(rename = "displayUsername")]
56 pub display_username: Option<String>,
57 pub image: Option<String>,
58 #[serde(rename = "phoneNumberVerified")]
59 pub phone_number_verified: Option<alibi_core::utils::json::JsValue>,
60}
61
62#[derive(Deserialize)]
63pub(super) struct ResetRequest {
64 pub otp: String,
65 #[serde(rename = "phoneNumber")]
66 pub phone_number: String,
67 #[serde(rename = "newPassword")]
68 pub new_password: String,
69}
70
71impl RequestBody for SendRequest {
72 const FIELDS: &'static [JsonField] = &[JsonField::string("phoneNumber", true)];
73}
74
75impl RequestBody for SignInRequest {
76 const FIELDS: &'static [JsonField] = &[
77 JsonField::string("phoneNumber", true),
78 JsonField::string("password", true),
79 JsonField {
80 name: "rememberMe",
81 kind: JsonFieldKind::Boolean,
82 required: false,
83 },
84 ];
85}
86
87impl RequestBody for VerifyRequest {
88 const FIELDS: &'static [JsonField] = &[
89 JsonField::string("phoneNumber", true),
90 JsonField::string("code", true),
91 JsonField {
92 name: "disableSession",
93 kind: JsonFieldKind::Boolean,
94 required: false,
95 },
96 JsonField {
97 name: "updatePhoneNumber",
98 kind: JsonFieldKind::Boolean,
99 required: false,
100 },
101 ];
102}
103
104impl RequestBody for ResetRequest {
105 const FIELDS: &'static [JsonField] = &[
106 JsonField::string("otp", true),
107 JsonField::string("phoneNumber", true),
108 JsonField::string("newPassword", true),
109 ];
110}
111
112pub(super) const fn phone_error(
113 status: u16,
114 code: &'static str,
115 message: &'static str,
116) -> AuthError {
117 AuthError::Upstream {
118 status,
119 code,
120 message,
121 }
122}
123
124pub(crate) fn reject_verified_input(
127 input: Option<&alibi_core::utils::json::JsValue>,
128) -> Result<(), AuthError> {
129 use alibi_core::utils::json::JsValue as Value;
130 let truthy = match input {
131 None | Some(Value::Null) => false,
132 Some(Value::Bool(value)) => *value,
133 Some(Value::Number(value)) => *value != 0.0 && !value.is_nan(),
134 Some(Value::String(value)) => !value.is_empty(),
135 Some(Value::Array(_) | Value::Object(_)) => true,
136 };
137 if truthy {
138 return Err(phone_error(
139 400,
140 "FIELD_NOT_ALLOWED",
141 "phoneNumberVerified is not allowed to be set",
142 ));
143 }
144 Ok(())
145}
146
147#[expect(
148 clippy::as_conversions,
149 clippy::cast_possible_truncation,
150 reason = "JavaScript-compatible numbers deliberately retain IEEE754 rounding and guarded integer coercion at the wire or adapter boundary"
151)]
152pub(crate) async fn parse_signup_phone(
153 ctx: &alibi_core::AuthContext<impl alibi_core::AuthSchema>,
154 input: Option<&alibi_core::utils::json::JsValue>,
155) -> Result<Option<String>, AuthError> {
156 use alibi_core::utils::json::JsValue as Value;
157 match input {
158 None | Some(Value::Null) => Ok(None),
159 Some(Value::String(value)) => Ok(Some(value.clone())),
160 Some(Value::Bool(value)) => Ok(Some(if *value { "1" } else { "0" }.into())),
163 Some(Value::Number(value)) => {
164 use alibi_core::store::NumericTextInput;
165 let number = *value;
169 let input_2 = if (-2_251_799_813_685_248.0..2_251_799_813_685_248.0).contains(&number)
172 && number.fract() == 0.0
173 && !(number == 0.0 && number.is_sign_negative())
174 {
175 NumericTextInput::Integer(number as i64)
176 } else {
177 NumericTextInput::Real(number)
178 };
179 Ok(Some(ctx.database.coerce_user_text_number(input_2).await?))
180 }
181 Some(Value::Array(_) | Value::Object(_)) => Err(phone_error(
182 422,
183 "FAILED_TO_CREATE_USER",
184 "Failed to create user",
185 )),
186 }
187}