doido_auth/
validations.rs1use crate::config::AuthModule;
5use crate::error::AuthError;
6use crate::state::try_global;
7
8pub fn validate_registration(email: &str, password: &str) -> Result<(), AuthError> {
12 let state = match try_global() {
13 Some(state) => state,
14 None => return Ok(()),
15 };
16 if !state.config.has_module(AuthModule::Validatable) {
17 return Ok(());
18 }
19 validate_email(email)?;
20 validate_password_length(password, state.config.password_length)?;
21 Ok(())
22}
23
24pub fn validate_email(email: &str) -> Result<(), AuthError> {
27 if is_valid_email(email) {
28 Ok(())
29 } else {
30 Err(AuthError::Validation("email is invalid".into()))
31 }
32}
33
34pub fn validate_password_length(password: &str, min: usize) -> Result<(), AuthError> {
36 if password.chars().count() >= min {
37 Ok(())
38 } else {
39 Err(AuthError::Validation(format!(
40 "password is too short (minimum is {min} characters)"
41 )))
42 }
43}
44
45fn is_valid_email(email: &str) -> bool {
46 let email = email.trim();
47 if email.is_empty() || email.chars().any(char::is_whitespace) {
48 return false;
49 }
50 match email.split_once('@') {
51 Some((local, domain)) => {
52 !local.is_empty()
53 && domain.contains('.')
54 && !domain.starts_with('.')
55 && !domain.ends_with('.')
56 }
57 None => false,
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn accepts_reasonable_emails() {
67 for e in ["a@b.com", "user.name@example.co", "x@sub.domain.io"] {
68 assert!(validate_email(e).is_ok(), "{e} should be valid");
69 }
70 }
71
72 #[test]
73 fn rejects_bad_emails() {
74 for e in ["", "no-at", "a@b", "@b.com", "a b@c.com", "a@.com", "a@b."] {
75 assert!(validate_email(e).is_err(), "{e} should be invalid");
76 }
77 }
78
79 #[test]
80 fn enforces_minimum_password_length() {
81 assert!(validate_password_length("secret", 6).is_ok());
82 assert!(validate_password_length("short", 6).is_err());
83 }
84}