rustauth-plugins 0.2.0

Official RustAuth 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,
    pub schema: super::schema::UsernameSchemaOptions,
}

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(),
            schema: super::schema::UsernameSchemaOptions::default(),
        }
    }
}

impl UsernameOptions {
    #[must_use]
    pub fn builder() -> UsernameOptionsBuilder {
        UsernameOptionsBuilder::default()
    }

    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 == '.')
}

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

impl UsernameOptionsBuilder {
    #[must_use]
    pub fn min_username_length(mut self, length: usize) -> Self {
        self.min_username_length = Some(length);
        self
    }

    #[must_use]
    pub fn max_username_length(mut self, length: usize) -> Self {
        self.max_username_length = Some(length);
        self
    }

    #[must_use]
    pub fn username_validator(mut self, validator: UsernameValidator) -> Self {
        self.username_validator = Some(validator);
        self
    }

    #[must_use]
    pub fn display_username_validator(mut self, validator: UsernameValidator) -> Self {
        self.display_username_validator = Some(Some(validator));
        self
    }

    #[must_use]
    pub fn username_normalization(mut self, normalizer: UsernameNormalizer) -> Self {
        self.username_normalization = Some(Some(normalizer));
        self
    }

    #[must_use]
    pub fn display_username_normalization(mut self, normalizer: UsernameNormalizer) -> Self {
        self.display_username_normalization = Some(Some(normalizer));
        self
    }

    #[must_use]
    pub fn validation_order(mut self, validation_order: ValidationOrder) -> Self {
        self.validation_order = Some(validation_order);
        self
    }

    #[must_use]
    pub fn schema(mut self, schema: super::schema::UsernameSchemaOptions) -> Self {
        self.schema = Some(schema);
        self
    }

    #[must_use]
    pub fn build(self) -> UsernameOptions {
        let defaults = UsernameOptions::default();
        UsernameOptions {
            min_username_length: self
                .min_username_length
                .unwrap_or(defaults.min_username_length),
            max_username_length: self
                .max_username_length
                .unwrap_or(defaults.max_username_length),
            username_validator: self
                .username_validator
                .unwrap_or(defaults.username_validator),
            display_username_validator: self
                .display_username_validator
                .unwrap_or(defaults.display_username_validator),
            username_normalization: self
                .username_normalization
                .unwrap_or(defaults.username_normalization),
            display_username_normalization: self
                .display_username_normalization
                .unwrap_or(defaults.display_username_normalization),
            validation_order: self.validation_order.unwrap_or(defaults.validation_order),
            schema: self.schema.unwrap_or(defaults.schema),
        }
    }
}