rustauth-plugins 0.2.0

Official RustAuth plugin modules.
Documentation
use std::collections::BTreeMap;

use serde_json::json;
use time::Duration;

use super::access::{default_roles, AdminRole};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AdminOptions {
    pub default_role: String,
    pub admin_roles: Vec<String>,
    pub default_ban_reason: Option<String>,
    pub default_ban_expires_in: Option<Duration>,
    pub impersonation_session_duration: Duration,
    pub roles: BTreeMap<String, AdminRole>,
    pub admin_user_ids: Vec<String>,
    pub banned_user_message: String,
    pub allow_impersonating_admins: bool,
    pub schema: AdminSchemaOptions,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AdminSchemaOptions {
    pub user_role_field: String,
    pub user_banned_field: String,
    pub user_ban_reason_field: String,
    pub user_ban_expires_field: String,
    pub session_impersonated_by_field: String,
}

impl Default for AdminOptions {
    fn default() -> Self {
        Self {
            default_role: "user".to_owned(),
            admin_roles: vec!["admin".to_owned()],
            default_ban_reason: None,
            default_ban_expires_in: None,
            impersonation_session_duration: Duration::hours(1),
            roles: default_roles(),
            admin_user_ids: Vec::new(),
            banned_user_message: "You have been banned from this application. Please contact support if you believe this is an error.".to_owned(),
            allow_impersonating_admins: false,
            schema: AdminSchemaOptions::default(),
        }
    }
}

impl Default for AdminSchemaOptions {
    fn default() -> Self {
        Self {
            user_role_field: "role".to_owned(),
            user_banned_field: "banned".to_owned(),
            user_ban_reason_field: "ban_reason".to_owned(),
            user_ban_expires_field: "ban_expires".to_owned(),
            session_impersonated_by_field: "impersonated_by".to_owned(),
        }
    }
}

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

    pub fn with_defaults(mut self) -> Self {
        if self.default_role.trim().is_empty() {
            self.default_role = "user".to_owned();
        }
        if self.admin_roles.is_empty() {
            self.admin_roles = vec!["admin".to_owned()];
        }
        if self.impersonation_session_duration.is_zero() {
            self.impersonation_session_duration = Duration::hours(1);
        }
        if self.roles.is_empty() {
            self.roles = default_roles();
        }
        if self.banned_user_message.trim().is_empty() {
            self.banned_user_message = Self::default().banned_user_message;
        }
        self.schema = self.schema.with_defaults();
        self
    }

    pub fn validate(&self) -> Result<(), String> {
        for role in &self.admin_roles {
            if !self
                .roles
                .keys()
                .any(|candidate: &String| candidate.eq_ignore_ascii_case(role))
            {
                return Err(format!(
                    "Invalid admin role `{role}`. Admin roles must be defined in roles."
                ));
            }
        }
        Ok(())
    }

    pub fn to_json(&self) -> serde_json::Value {
        json!({
            "defaultRole": self.default_role,
            "adminRoles": self.admin_roles,
            "adminUserIds": self.admin_user_ids,
            "bannedUserMessage": self.banned_user_message,
            "allowImpersonatingAdmins": self.allow_impersonating_admins,
        })
    }
}

#[derive(Clone, Default)]
pub struct AdminOptionsBuilder {
    default_role: Option<String>,
    admin_roles: Option<Vec<String>>,
    default_ban_reason: Option<Option<String>>,
    default_ban_expires_in: Option<Option<Duration>>,
    impersonation_session_duration: Option<Duration>,
    roles: Option<BTreeMap<String, AdminRole>>,
    admin_user_ids: Option<Vec<String>>,
    banned_user_message: Option<String>,
    allow_impersonating_admins: Option<bool>,
    schema: Option<AdminSchemaOptions>,
}

impl AdminOptionsBuilder {
    #[must_use]
    pub fn default_role(mut self, default_role: impl Into<String>) -> Self {
        self.default_role = Some(default_role.into());
        self
    }

    #[must_use]
    pub fn admin_roles(mut self, admin_roles: Vec<String>) -> Self {
        self.admin_roles = Some(admin_roles);
        self
    }

    #[must_use]
    pub fn admin_role(mut self, admin_role: impl Into<String>) -> Self {
        self.admin_roles = Some(vec![admin_role.into()]);
        self
    }

    pub fn build(self) -> Result<AdminOptions, rustauth_core::error::RustAuthError> {
        let defaults = AdminOptions::default();
        let options = AdminOptions {
            default_role: self.default_role.unwrap_or(defaults.default_role),
            admin_roles: self.admin_roles.unwrap_or(defaults.admin_roles),
            default_ban_reason: self
                .default_ban_reason
                .unwrap_or(defaults.default_ban_reason),
            default_ban_expires_in: self
                .default_ban_expires_in
                .unwrap_or(defaults.default_ban_expires_in),
            impersonation_session_duration: self
                .impersonation_session_duration
                .unwrap_or(defaults.impersonation_session_duration),
            roles: self.roles.unwrap_or(defaults.roles),
            admin_user_ids: self.admin_user_ids.unwrap_or(defaults.admin_user_ids),
            banned_user_message: self
                .banned_user_message
                .unwrap_or(defaults.banned_user_message),
            allow_impersonating_admins: self
                .allow_impersonating_admins
                .unwrap_or(defaults.allow_impersonating_admins),
            schema: self.schema.unwrap_or(defaults.schema),
        }
        .with_defaults();
        options
            .validate()
            .map_err(rustauth_core::error::RustAuthError::InvalidConfig)?;
        Ok(options)
    }
}

impl AdminSchemaOptions {
    fn with_defaults(mut self) -> Self {
        let defaults = Self::default();
        if self.user_role_field.trim().is_empty() {
            self.user_role_field = defaults.user_role_field;
        }
        if self.user_banned_field.trim().is_empty() {
            self.user_banned_field = defaults.user_banned_field;
        }
        if self.user_ban_reason_field.trim().is_empty() {
            self.user_ban_reason_field = defaults.user_ban_reason_field;
        }
        if self.user_ban_expires_field.trim().is_empty() {
            self.user_ban_expires_field = defaults.user_ban_expires_field;
        }
        if self.session_impersonated_by_field.trim().is_empty() {
            self.session_impersonated_by_field = defaults.session_impersonated_by_field;
        }
        self
    }
}