Skip to main content

hypercall_db/types/
username_validation.rs

1//! Username validation rules shared between submission and approval paths.
2
3const MAX_LEN: usize = 20;
4const MIN_LEN: usize = 3;
5const RESERVED_USERNAMES: &[&str] = &[
6    "admin",
7    "administrator",
8    "api",
9    "help",
10    "hypercall",
11    "moderator",
12    "root",
13    "staff",
14    "support",
15    "system",
16];
17
18pub fn validate_username(name: &str) -> Result<(), String> {
19    if name.trim() != name {
20        return Err("Username cannot start or end with whitespace".to_string());
21    }
22    if name.len() < MIN_LEN || name.len() > MAX_LEN {
23        return Err(format!(
24            "Username must be between {} and {} characters",
25            MIN_LEN, MAX_LEN
26        ));
27    }
28
29    let normalized = name.to_ascii_lowercase();
30    if RESERVED_USERNAMES.contains(&normalized.as_str()) {
31        return Err("Username is reserved".to_string());
32    }
33    if normalized.starts_with("0x") {
34        return Err("Username cannot look like a wallet address".to_string());
35    }
36    if normalized.contains(".hl") {
37        return Err("Username cannot contain \".hl\"".to_string());
38    }
39
40    for ch in name.chars() {
41        if !ch.is_ascii_alphanumeric() && ch != '_' {
42            return Err(format!(
43                "Username can only contain alphanumeric characters and underscores (found '{}')",
44                ch
45            ));
46        }
47    }
48
49    if let Some(first) = name.chars().next() {
50        if !first.is_ascii_alphanumeric() {
51            return Err("Username must start with an alphanumeric character".to_string());
52        }
53    }
54
55    Ok(())
56}