use super::{EmailPasswordConfig, SignUpRequest, SignUpResponse};
use alibi_core::{AuthContext, AuthRequest, AuthResult, AuthSchema, wire::UserView};
use serde_json::{Map, Value, json};
use std::{future::Future, pin::Pin};
pub type ExistingUserSignupCallback = dyn Fn(UserView, Option<AuthRequest>) -> Pin<Box<dyn Future<Output = AuthResult<()>> + Send>>
+ Send
+ Sync;
#[derive(Clone, Debug)]
pub struct SyntheticUserContext {
pub core_fields: Map<String, Value>,
pub additional_fields: Map<String, Value>,
pub id: String,
}
pub type CustomSyntheticUserCallback =
dyn Fn(SyntheticUserContext) -> AuthResult<Map<String, Value>> + Send + Sync;
pub(super) async fn notify_existing(
user: UserView,
request: Option<&AuthRequest>,
config: &EmailPasswordConfig,
context: &AuthContext<impl AuthSchema>,
) -> AuthResult<()> {
let Some(callback) = config.on_existing_user_signup.clone() else {
return Ok(());
};
let request = request.cloned();
super::super::authentication_helpers::run_owned_notification(
context,
async move {
super::super::authentication_helpers::run_notification(callback(user, request)).await;
Ok(())
},
context.config.awaited_notification_errors,
)
.await
}
pub(super) fn synthetic_response(
body: &SignUpRequest,
parsed_fields: &alibi_core::field_policy::FieldValues,
config: &EmailPasswordConfig,
context: &AuthContext<impl AuthSchema>,
) -> AuthResult<(SignUpResponse<Value>, Option<Vec<String>>)> {
let now = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
let core_fields = Map::from_iter([
("name".into(), json!(body.name)),
("email".into(), json!(body.email)),
("emailVerified".into(), json!(false)),
("image".into(), json!(body.image)),
("createdAt".into(), json!(now)),
("updatedAt".into(), json!(now)),
]);
let input = SyntheticUserContext {
core_fields,
additional_fields: parsed_fields
.iter()
.filter(|(name, _)| context.config.user.additional_fields.contains_key(*name))
.map(|(name, value)| Ok((name.clone(), serde_json::to_value(value)?)))
.collect::<Result<_, serde_json::Error>>()?,
id: context
.config
.advanced
.database
.generated_id("user")?
.unwrap_or_else(|| uuid::Uuid::new_v4().simple().to_string()),
};
let mut candidate = if let Some(customize) = &config.custom_synthetic_user {
customize(input)?
} else {
let mut fields = input.core_fields;
_ = fields.insert("id".into(), json!(input.id));
for (name, value) in parsed_fields {
_ = fields.insert(name.clone(), serde_json::to_value(value)?);
}
if config.enable_username {
_ = fields
.entry("username")
.or_insert_with(|| json!(body.username));
_ = fields
.entry("displayUsername")
.or_insert_with(|| json!(body.display_username));
}
fields
};
let mut result = Map::new();
for field in ["id", "name", "email"] {
if let Some(value) = candidate.remove(field) {
_ = result.insert(field.into(), value);
}
}
for (field, default) in [
("emailVerified", json!(false)),
("image", Value::Null),
("createdAt", json!(now)),
("updatedAt", json!(now)),
] {
_ = result.insert(field.into(), candidate.remove(field).unwrap_or(default));
}
let mut defaults = Vec::new();
let enabled = |key: &str| {
context
.get_metadata(key)
.and_then(Value::as_bool)
.unwrap_or(false)
};
if config.enable_username {
defaults.extend([("username", Value::Null), ("displayUsername", Value::Null)]);
}
if enabled("two_factor.enabled") {
defaults.push(("twoFactorEnabled", json!(false)));
}
if enabled("admin.enabled") {
let mut creation = alibi_core::CreateUser::new();
super::super::helpers::apply_default_role(context, &mut creation);
defaults.extend([
("role", json!(creation.role)),
("banned", json!(false)),
("banReason", Value::Null),
("banExpires", Value::Null),
]);
}
if enabled("anonymous.enabled") {
defaults.push(("isAnonymous", json!(false)));
}
if enabled("phone-number.enabled") {
defaults.extend([
("phoneNumber", Value::Null),
("phoneNumberVerified", Value::Null),
]);
}
if enabled("last-login-method.enabled") {
defaults.push(("lastLoginMethod", Value::Null));
}
for (field, default) in defaults {
_ = result.insert(field.into(), candidate.remove(field).unwrap_or(default));
}
let registered = context
.extensions
.get::<alibi_core::field_policy::UserFields>();
let fields = registered
.as_ref()
.map_or(&context.config.user.additional_fields, |fields| &fields.0.0);
for (name, field) in fields {
if !field.returned {
_ = result.remove(name);
} else if let Some(value) = candidate.remove(name) {
_ = result.insert(name.clone(), value);
} else if !result.contains_key(name) {
if let Some(default) = &field.default {
_ = result.insert(name.clone(), serde_json::to_value(default.value())?);
} else if !field.required {
_ = result.insert(name.clone(), Value::Null);
}
}
}
Ok((
SignUpResponse {
token: None,
user: Value::Object(result),
},
None,
))
}