use super::funejson::Value; use super::auth_normalise_email::normalise_email; use super::auth_password_policy_check_password::{check_password, password_policy_from_value}; use super::auth_password_policy_types::PasswordPolicy; const MAX_NAME: usize = 100; fn is_ascii_space(ch: char) -> bool { matches!(ch, ' ' | '\t' | '\n' | '\r' | '\u{0C}' | '\u{0B}') } /// A sign-up form's email, password and name checked together: the values /// to store when valid, and a message for each field that needs fixing. /// /// # Panics /// Panics on a policy whose numbers make no sense. pub fn validate_registration(email: &str, password: &str, name: &str, policy: &PasswordPolicy) -> RegistrationCheck { let mut fields: Vec<(String, String)> = Vec::new(); let normalised = normalise_email(email); if normalised.is_none() { let message = if email.trim_matches(is_ascii_space).is_empty() { "Enter your email address." } else { "Enter a valid email address, like name@example.com." }; fields.push(("email".to_string(), message.to_string())); } let trimmed_name = name.trim_matches(is_ascii_space); let count = trimmed_name.chars().count(); let name_error: Option = if count == 0 { Some("Enter your name.".to_string()) } else if count > MAX_NAME { Some(format!("Use no more than {} characters for your name.", MAX_NAME)) } else if trimmed_name.chars().any(|ch| (ch as u32) < 0x20 || ch as u32 == 0x7F) { Some("Your name cannot contain control characters.".to_string()) } else { None }; let check = check_password( password, normalised.as_deref(), if name_error.is_none() { Some(trimmed_name) } else { None }, policy, ); if password.is_empty() { fields.push(("password".to_string(), "Enter a password.".to_string())); } else if !check.valid { let joined: Vec<&str> = check.failures.iter().map(|f| f.message.as_str()).collect(); fields.push(("password".to_string(), joined.join(" "))); } let valid_name = name_error.is_none(); if let Some(message) = name_error { fields.push(("name".to_string(), message)); } RegistrationCheck { valid: fields.is_empty(), email: normalised, name: if valid_name { Some(trimmed_name.to_string()) } else { None }, fields, } } pub fn registration_check_to_value(check: &RegistrationCheck) -> Value { let text = |v: &Option| v.as_deref().map(Value::str).unwrap_or(Value::Null); Value::obj(vec![ ("valid", Value::Bool(check.valid)), ("email", text(&check.email)), ("name", text(&check.name)), ( "fields", Value::Obj(check.fields.iter().map(|(k, v)| (k.clone(), Value::str(v))).collect()), ), ]) } pub fn fune_vector(args: &[Value]) -> Value { // A non-string is an empty field, as in TypeScript and Python. let policy = password_policy_from_value(&args[3]); registration_check_to_value(&validate_registration( args[0].as_str(), args[1].as_str(), args[2].as_str(), &policy, )) }