Skip to main content

alibi_core/utils/
username.rs

1//! Shared username normalization and validation helpers.
2
3mod policy;
4pub use policy::{
5    UsernameConfig, UsernameNormalization, UsernameNormalizer, UsernameValidationOrder,
6    UsernameValidator,
7};
8
9pub const USERNAME_MIN_LENGTH: usize = 3;
10pub const USERNAME_MAX_LENGTH: usize = 30;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum UsernameValidationError {
14    TooShort,
15    TooLong,
16    Invalid,
17}
18
19#[must_use]
20pub fn normalize_username(username: &str) -> String {
21    username.to_lowercase()
22}
23
24///
25/// # Errors
26///
27/// Returns an error if the username fails the configured length or character validation.
28pub fn validate_username(username: &str) -> Result<(), UsernameValidationError> {
29    if username.encode_utf16().count() < USERNAME_MIN_LENGTH {
30        return Err(UsernameValidationError::TooShort);
31    }
32
33    if username.encode_utf16().count() > USERNAME_MAX_LENGTH {
34        return Err(UsernameValidationError::TooLong);
35    }
36
37    if valid_default_characters(username) {
38        Ok(())
39    } else {
40        Err(UsernameValidationError::Invalid)
41    }
42}
43
44#[must_use]
45pub fn normalize_username_fields(
46    mut username: Option<String>,
47    mut display_username: Option<String>,
48) -> (Option<String>, Option<String>) {
49    if username.is_some() && display_username.is_none() {
50        display_username.clone_from(&username);
51    }
52
53    if display_username.is_some() && username.is_none() {
54        username.clone_from(&display_username);
55    }
56
57    if let Some(username_value) = username.as_mut() {
58        *username_value = normalize_username(username_value);
59    }
60
61    (username, display_username)
62}
63
64fn valid_default_characters(username: &str) -> bool {
65    username
66        .chars()
67        .all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '.')
68}
69impl UsernameValidationError {
70    #[must_use]
71    pub const fn auth_error(self, status: u16) -> crate::AuthError {
72        let (code, message) = match self {
73            Self::TooShort => ("USERNAME_TOO_SHORT", "Username is too short"),
74            Self::TooLong => ("USERNAME_TOO_LONG", "Username is too long"),
75            Self::Invalid => ("INVALID_USERNAME", "Username is invalid"),
76        };
77        crate::AuthError::Upstream {
78            status,
79            code,
80            message,
81        }
82    }
83}