1use email_address::EmailAddress;
2
3use crate::error::{AuthError, Result};
4
5pub fn validate_email(email: &str) -> Result<()> {
7 if EmailAddress::is_valid(email) {
8 Ok(())
9 } else {
10 Err(AuthError::Internal(format!(
11 "invalid email address: {email}"
12 )))
13 }
14}
15
16pub fn validate_slug(slug: &str) -> Result<()> {
19 let ok = !slug.is_empty()
20 && slug.len() >= 2
21 && slug.len() <= 63
22 && slug
23 .chars()
24 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
25 && !slug.starts_with('-')
26 && !slug.ends_with('-');
27 if ok {
28 Ok(())
29 } else {
30 Err(AuthError::Internal(format!(
31 "invalid org slug '{slug}': must be 2–63 lowercase alphanumeric/hyphen characters, not starting or ending with a hyphen"
32 )))
33 }
34}
35
36pub fn validate_password(password: &str, min_len: usize) -> Result<()> {
42 if password.len() < min_len {
43 return Err(AuthError::WeakPassword);
44 }
45 if !password.chars().any(|c| c.is_ascii_uppercase()) {
46 return Err(AuthError::WeakPassword);
47 }
48 if !password.chars().any(|c| c.is_ascii_digit()) {
49 return Err(AuthError::WeakPassword);
50 }
51 if !password
52 .chars()
53 .any(|c| !c.is_alphanumeric() && c.is_ascii())
54 {
55 return Err(AuthError::WeakPassword);
56 }
57 Ok(())
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn valid_slug_passes() {
66 assert!(validate_slug("my-org").is_ok());
67 assert!(validate_slug("acme").is_ok());
68 assert!(validate_slug("org-123").is_ok());
69 }
70
71 #[test]
72 fn invalid_slug_rejected() {
73 assert!(validate_slug("").is_err());
74 assert!(validate_slug("a").is_err()); assert!(validate_slug("-leading").is_err());
76 assert!(validate_slug("trailing-").is_err());
77 assert!(validate_slug("has space").is_err());
78 assert!(validate_slug("UPPER").is_err());
79 assert!(validate_slug(&"a".repeat(64)).is_err()); }
81
82 #[test]
83 fn valid_email_passes() {
84 assert!(validate_email("user@example.com").is_ok());
85 assert!(validate_email("user+tag@sub.domain.io").is_ok());
86 }
87
88 #[test]
89 fn invalid_email_rejected() {
90 assert!(validate_email("notanemail").is_err());
91 assert!(validate_email("@nodomain").is_err());
92 assert!(validate_email("missing@").is_err());
93 assert!(validate_email("").is_err());
94 }
95
96 #[test]
97 fn strong_password_passes() {
98 assert!(validate_password("Secure@123", 8).is_ok());
99 assert!(validate_password("Tr0ub4dor&3", 8).is_ok());
100 }
101
102 #[test]
103 fn weak_passwords_rejected() {
104 assert!(validate_password("short", 8).is_err());
105 assert!(validate_password("alllowercase1!", 8).is_err()); assert!(validate_password("NoDigitsHere!", 8).is_err()); assert!(validate_password("NoSpecial123", 8).is_err()); }
109}