openauth-plugins 0.0.4

Official OpenAuth plugin modules.
Documentation
use std::sync::Arc;

pub type UsernameValidator = Arc<dyn Fn(&str) -> bool + Send + Sync>;
pub type UsernameNormalizer = Arc<dyn Fn(&str) -> String + Send + Sync>;

#[derive(Clone)]
pub struct UsernameOptions {
    pub min_username_length: usize,
    pub max_username_length: usize,
    pub username_validator: UsernameValidator,
    pub display_username_validator: Option<UsernameValidator>,
    pub username_normalization: Option<UsernameNormalizer>,
    pub display_username_normalization: Option<UsernameNormalizer>,
    pub validation_order: ValidationOrder,
}

impl Default for UsernameOptions {
    fn default() -> Self {
        Self {
            min_username_length: 3,
            max_username_length: 30,
            username_validator: Arc::new(default_username_validator),
            display_username_validator: None,
            username_normalization: Some(Arc::new(|username| username.to_lowercase())),
            display_username_normalization: None,
            validation_order: ValidationOrder::default(),
        }
    }
}

impl UsernameOptions {
    pub fn normalize_username(&self, username: &str) -> String {
        self.username_normalization
            .as_ref()
            .map(|normalizer| normalizer(username))
            .unwrap_or_else(|| username.to_owned())
    }

    pub fn normalize_display_username(&self, display_username: &str) -> String {
        self.display_username_normalization
            .as_ref()
            .map(|normalizer| normalizer(display_username))
            .unwrap_or_else(|| display_username.to_owned())
    }

    pub fn username_for_validation(&self, username: &str) -> String {
        if self.validation_order.username == ValidationPhase::PostNormalization {
            self.normalize_username(username)
        } else {
            username.to_owned()
        }
    }

    pub fn display_username_for_validation(&self, display_username: &str) -> String {
        if self.validation_order.display_username == ValidationPhase::PostNormalization {
            self.normalize_display_username(display_username)
        } else {
            display_username.to_owned()
        }
    }

    pub fn validate_username(
        &self,
        username: &str,
        _phase: ValidationPhase,
    ) -> Result<(), UsernameValidationError> {
        if username.len() < self.min_username_length {
            return Err(UsernameValidationError::TooShort);
        }
        if username.len() > self.max_username_length {
            return Err(UsernameValidationError::TooLong);
        }
        if !(self.username_validator)(username) {
            return Err(UsernameValidationError::Invalid);
        }
        Ok(())
    }

    pub fn validate_display_username(
        &self,
        display_username: &str,
    ) -> Result<(), UsernameValidationError> {
        if let Some(validator) = &self.display_username_validator {
            if !validator(display_username) {
                return Err(UsernameValidationError::InvalidDisplay);
            }
        }
        Ok(())
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ValidationOrder {
    pub username: ValidationPhase,
    pub display_username: ValidationPhase,
}

impl Default for ValidationOrder {
    fn default() -> Self {
        Self {
            username: ValidationPhase::PreNormalization,
            display_username: ValidationPhase::PreNormalization,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValidationPhase {
    PreNormalization,
    PostNormalization,
    Endpoint,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UsernameValidationError {
    TooShort,
    TooLong,
    Invalid,
    InvalidDisplay,
}

fn default_username_validator(username: &str) -> bool {
    username
        .chars()
        .all(|character| character.is_ascii_alphanumeric() || character == '_' || character == '.')
}