Skip to main content

openauth_plugins/username/
options.rs

1use std::sync::Arc;
2
3pub type UsernameValidator = Arc<dyn Fn(&str) -> bool + Send + Sync>;
4pub type UsernameNormalizer = Arc<dyn Fn(&str) -> String + Send + Sync>;
5
6#[derive(Clone)]
7pub struct UsernameOptions {
8    pub min_username_length: usize,
9    pub max_username_length: usize,
10    pub username_validator: UsernameValidator,
11    pub display_username_validator: Option<UsernameValidator>,
12    pub username_normalization: Option<UsernameNormalizer>,
13    pub display_username_normalization: Option<UsernameNormalizer>,
14    pub validation_order: ValidationOrder,
15}
16
17impl Default for UsernameOptions {
18    fn default() -> Self {
19        Self {
20            min_username_length: 3,
21            max_username_length: 30,
22            username_validator: Arc::new(default_username_validator),
23            display_username_validator: None,
24            username_normalization: Some(Arc::new(|username| username.to_lowercase())),
25            display_username_normalization: None,
26            validation_order: ValidationOrder::default(),
27        }
28    }
29}
30
31impl UsernameOptions {
32    pub fn normalize_username(&self, username: &str) -> String {
33        self.username_normalization
34            .as_ref()
35            .map(|normalizer| normalizer(username))
36            .unwrap_or_else(|| username.to_owned())
37    }
38
39    pub fn normalize_display_username(&self, display_username: &str) -> String {
40        self.display_username_normalization
41            .as_ref()
42            .map(|normalizer| normalizer(display_username))
43            .unwrap_or_else(|| display_username.to_owned())
44    }
45
46    pub fn username_for_validation(&self, username: &str) -> String {
47        if self.validation_order.username == ValidationPhase::PostNormalization {
48            self.normalize_username(username)
49        } else {
50            username.to_owned()
51        }
52    }
53
54    pub fn display_username_for_validation(&self, display_username: &str) -> String {
55        if self.validation_order.display_username == ValidationPhase::PostNormalization {
56            self.normalize_display_username(display_username)
57        } else {
58            display_username.to_owned()
59        }
60    }
61
62    pub fn validate_username(
63        &self,
64        username: &str,
65        _phase: ValidationPhase,
66    ) -> Result<(), UsernameValidationError> {
67        if username.len() < self.min_username_length {
68            return Err(UsernameValidationError::TooShort);
69        }
70        if username.len() > self.max_username_length {
71            return Err(UsernameValidationError::TooLong);
72        }
73        if !(self.username_validator)(username) {
74            return Err(UsernameValidationError::Invalid);
75        }
76        Ok(())
77    }
78
79    pub fn validate_display_username(
80        &self,
81        display_username: &str,
82    ) -> Result<(), UsernameValidationError> {
83        if let Some(validator) = &self.display_username_validator {
84            if !validator(display_username) {
85                return Err(UsernameValidationError::InvalidDisplay);
86            }
87        }
88        Ok(())
89    }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub struct ValidationOrder {
94    pub username: ValidationPhase,
95    pub display_username: ValidationPhase,
96}
97
98impl Default for ValidationOrder {
99    fn default() -> Self {
100        Self {
101            username: ValidationPhase::PreNormalization,
102            display_username: ValidationPhase::PreNormalization,
103        }
104    }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub enum ValidationPhase {
109    PreNormalization,
110    PostNormalization,
111    Endpoint,
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum UsernameValidationError {
116    TooShort,
117    TooLong,
118    Invalid,
119    InvalidDisplay,
120}
121
122fn default_username_validator(username: &str) -> bool {
123    username
124        .chars()
125        .all(|character| character.is_ascii_alphanumeric() || character == '_' || character == '.')
126}