alibi_plugins/email_password/
signup.rs1use super::{EmailPasswordConfig, SignUpRequest, SignUpResponse};
3use alibi_core::{AuthContext, AuthRequest, AuthResult, AuthSchema, wire::UserView};
4use serde_json::{Map, Value, json};
5use std::{future::Future, pin::Pin};
6
7pub type ExistingUserSignupCallback = dyn Fn(UserView, Option<AuthRequest>) -> Pin<Box<dyn Future<Output = AuthResult<()>> + Send>>
10 + Send
11 + Sync;
12
13#[derive(Clone, Debug)]
15pub struct SyntheticUserContext {
16 pub core_fields: Map<String, Value>,
17 pub additional_fields: Map<String, Value>,
18 pub id: String,
19}
20
21pub type CustomSyntheticUserCallback =
24 dyn Fn(SyntheticUserContext) -> AuthResult<Map<String, Value>> + Send + Sync;
25
26pub(super) async fn notify_existing(
27 user: UserView,
28 request: Option<&AuthRequest>,
29 config: &EmailPasswordConfig,
30 context: &AuthContext<impl AuthSchema>,
31) -> AuthResult<()> {
32 let Some(callback) = config.on_existing_user_signup.clone() else {
33 return Ok(());
34 };
35 let request = request.cloned();
36 super::super::authentication_helpers::run_owned_notification(
37 context,
38 async move {
39 super::super::authentication_helpers::run_notification(callback(user, request)).await;
40 Ok(())
41 },
42 context.config.awaited_notification_errors,
43 )
44 .await
45}
46
47pub(super) fn synthetic_response(
48 body: &SignUpRequest,
49 parsed_fields: &alibi_core::field_policy::FieldValues,
50 config: &EmailPasswordConfig,
51 context: &AuthContext<impl AuthSchema>,
52) -> AuthResult<(SignUpResponse<Value>, Option<Vec<String>>)> {
53 let now = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
54 let core_fields = Map::from_iter([
55 ("name".into(), json!(body.name)),
56 ("email".into(), json!(body.email)),
57 ("emailVerified".into(), json!(false)),
58 ("image".into(), json!(body.image)),
59 ("createdAt".into(), json!(now)),
60 ("updatedAt".into(), json!(now)),
61 ]);
62 let input = SyntheticUserContext {
63 core_fields,
64 additional_fields: parsed_fields
65 .iter()
66 .filter(|(name, _)| context.config.user.additional_fields.contains_key(*name))
67 .map(|(name, value)| Ok((name.clone(), serde_json::to_value(value)?)))
68 .collect::<Result<_, serde_json::Error>>()?,
69 id: context
70 .config
71 .advanced
72 .database
73 .generated_id("user")?
74 .unwrap_or_else(|| uuid::Uuid::new_v4().simple().to_string()),
75 };
76 let mut candidate = if let Some(customize) = &config.custom_synthetic_user {
77 customize(input)?
78 } else {
79 let mut fields = input.core_fields;
80 _ = fields.insert("id".into(), json!(input.id));
81 for (name, value) in parsed_fields {
82 _ = fields.insert(name.clone(), serde_json::to_value(value)?);
83 }
84 if config.enable_username {
85 _ = fields
86 .entry("username")
87 .or_insert_with(|| json!(body.username));
88 _ = fields
89 .entry("displayUsername")
90 .or_insert_with(|| json!(body.display_username));
91 }
92 fields
93 };
94 let mut result = Map::new();
95 for field in ["id", "name", "email"] {
96 if let Some(value) = candidate.remove(field) {
97 _ = result.insert(field.into(), value);
98 }
99 }
100 for (field, default) in [
101 ("emailVerified", json!(false)),
102 ("image", Value::Null),
103 ("createdAt", json!(now)),
104 ("updatedAt", json!(now)),
105 ] {
106 _ = result.insert(field.into(), candidate.remove(field).unwrap_or(default));
107 }
108 let mut defaults = Vec::new();
109 let enabled = |key: &str| {
110 context
111 .get_metadata(key)
112 .and_then(Value::as_bool)
113 .unwrap_or(false)
114 };
115 if config.enable_username {
116 defaults.extend([("username", Value::Null), ("displayUsername", Value::Null)]);
117 }
118 if enabled("two_factor.enabled") {
119 defaults.push(("twoFactorEnabled", json!(false)));
120 }
121 if enabled("admin.enabled") {
122 let mut creation = alibi_core::CreateUser::new();
123 super::super::helpers::apply_default_role(context, &mut creation);
124 defaults.extend([
125 ("role", json!(creation.role)),
126 ("banned", json!(false)),
127 ("banReason", Value::Null),
128 ("banExpires", Value::Null),
129 ]);
130 }
131 if enabled("anonymous.enabled") {
132 defaults.push(("isAnonymous", json!(false)));
133 }
134 if enabled("phone-number.enabled") {
135 defaults.extend([
136 ("phoneNumber", Value::Null),
137 ("phoneNumberVerified", Value::Null),
138 ]);
139 }
140 if enabled("last-login-method.enabled") {
141 defaults.push(("lastLoginMethod", Value::Null));
142 }
143 for (field, default) in defaults {
144 _ = result.insert(field.into(), candidate.remove(field).unwrap_or(default));
145 }
146 let registered = context
147 .extensions
148 .get::<alibi_core::field_policy::UserFields>();
149 let fields = registered
150 .as_ref()
151 .map_or(&context.config.user.additional_fields, |fields| &fields.0.0);
152 for (name, field) in fields {
153 if !field.returned {
154 _ = result.remove(name);
155 } else if let Some(value) = candidate.remove(name) {
156 _ = result.insert(name.clone(), value);
157 } else if !result.contains_key(name) {
158 if let Some(default) = &field.default {
159 _ = result.insert(name.clone(), serde_json::to_value(default.value())?);
160 } else if !field.required {
161 _ = result.insert(name.clone(), Value::Null);
162 }
163 }
164 }
165 Ok((
166 SignUpResponse {
167 token: None,
168 user: Value::Object(result),
169 },
170 None,
171 ))
172}