b453d5d
use time::{Date, OffsetDateTime};
use crate::errors::AuthError;
pub fn validate_email(raw: &str) -> Result<String, AuthError> {
let email = raw.trim().to_lowercase();
if email.len() < 3 || email.len() > 254 {
return Err(AuthError::BadRequest("invalid email address".to_string()));
}
if email.chars().any(|c| c.is_whitespace()) {
return Err(AuthError::BadRequest("invalid email address".to_string()));
}
let Some(at) = email.find('@') else {
return Err(AuthError::BadRequest("invalid email address".to_string()));
};
if at == 0 || at == email.len() - 1 || !email[at + 1..].contains('.') {
return Err(AuthError::BadRequest("invalid email address".to_string()));
}
Ok(email)
}
pub fn validate_username(name: &str) -> Result<String, AuthError> {
if name.is_empty() || name.len() > 64 {
return Err(AuthError::BadRequest(
"username must be between 1 and 64 characters".to_string(),
));
}
if !name
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '.' | '-'))
{
return Err(AuthError::BadRequest(
"username may only contain letters, digits, underscores, dots and dashes".to_string(),
));
}
Ok(name.to_string())
}
pub fn derive_username(email: &str, explicit: Option<&str>) -> Result<String, AuthError> {
match explicit {
Some(name) => validate_username(name),
None => {
let local_part = email.split('@').next().unwrap_or_default();
let base = local_part.split('+').next().unwrap_or_default();
validate_username(base)
}
}
}
pub fn validate_age(date_of_birth: Option<Date>, min_age: u8) -> Result<(), AuthError> {
let Some(dob) = date_of_birth else {
return Err(AuthError::BadRequest(
"date of birth is required".to_string(),
));
};
let today = OffsetDateTime::now_utc().date();
let mut age = today.year() - dob.year();
if (today.month(), today.day()) < (dob.month(), dob.day()) {
age -= 1;
}
if age < min_age as i32 {
return Err(AuthError::BadRequest(format!(
"you must be at least {min_age} years old"
)));
}
Ok(())
}