rustauth-plugins 0.3.0

Official RustAuth plugin modules.
Documentation
use rustauth_core::db::TableOptions;
use rustauth_core::outbound::OutboundSendFuture;
use serde_json::{json, Value};
use std::collections::BTreeMap;
use std::sync::Arc;
use time::Duration;

use crate::access::{AccessControl, Role};

use super::hooks::OrganizationHooks;
use super::limits::{MembershipLimit, OrganizationLimit};
use super::{Invitation, Member, Organization};

pub type SendInvitationEmailHook = Arc<dyn Fn(InvitationEmail) -> OutboundSendFuture + Send + Sync>;

pub type CustomCreateDefaultTeamFuture = std::pin::Pin<
    Box<
        dyn std::future::Future<
                Output = Result<DefaultTeamSpec, rustauth_core::error::RustAuthError>,
            > + Send,
    >,
>;
pub type CustomCreateDefaultTeamHook =
    Arc<dyn Fn(Organization) -> CustomCreateDefaultTeamFuture + Send + Sync>;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DefaultTeamSpec {
    pub name: String,
}

#[derive(Clone)]
pub struct OrganizationOptions {
    pub allow_user_to_create_organization: bool,
    pub organization_limit: Option<OrganizationLimit>,
    pub creator_role: String,
    pub membership_limit: MembershipLimit,
    pub invitation_expires_in: Duration,
    pub invitation_limit: usize,
    pub cancel_pending_invitations_on_re_invite: bool,
    pub require_email_verification_on_invitation: bool,
    pub disable_organization_deletion: bool,
    pub hooks: OrganizationHooks,
    pub send_invitation_email: Option<SendInvitationEmailHook>,
    pub teams: TeamOptions,
    pub dynamic_access_control: DynamicAccessControlOptions,
    pub access_control: Option<AccessControl>,
    pub roles: Option<BTreeMap<String, Role>>,
    pub custom_roles: BTreeMap<String, serde_json::Value>,
    pub schema: OrganizationSchemaOptions,
}

#[derive(Clone)]
pub struct TeamOptions {
    pub enabled: bool,
    pub create_default_team: bool,
    pub custom_create_default_team: Option<CustomCreateDefaultTeamHook>,
    pub maximum_teams: Option<usize>,
    pub maximum_members_per_team: Option<usize>,
    pub allow_removing_all_teams: bool,
}

impl std::fmt::Debug for TeamOptions {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter
            .debug_struct("TeamOptions")
            .field("enabled", &self.enabled)
            .field("create_default_team", &self.create_default_team)
            .field(
                "custom_create_default_team",
                &self
                    .custom_create_default_team
                    .as_ref()
                    .map(|_| "<custom-create-default-team>"),
            )
            .field("maximum_teams", &self.maximum_teams)
            .field("maximum_members_per_team", &self.maximum_members_per_team)
            .field("allow_removing_all_teams", &self.allow_removing_all_teams)
            .finish()
    }
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DynamicAccessControlOptions {
    pub enabled: bool,
    pub maximum_roles_per_organization: Option<usize>,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct OrganizationSchemaOptions {
    pub organization: TableOptions,
    pub member: TableOptions,
    pub invitation: TableOptions,
    pub team: TableOptions,
    pub team_member: TableOptions,
    pub organization_role: TableOptions,
}

#[derive(Debug, Clone)]
pub struct InvitationEmail {
    pub id: String,
    pub role: String,
    pub email: String,
    pub organization: Organization,
    pub invitation: Invitation,
    pub inviter: Member,
}

impl Default for OrganizationOptions {
    fn default() -> Self {
        Self {
            allow_user_to_create_organization: true,
            organization_limit: None,
            creator_role: "owner".to_owned(),
            membership_limit: MembershipLimit::default(),
            invitation_expires_in: Duration::hours(48),
            invitation_limit: 100,
            cancel_pending_invitations_on_re_invite: false,
            require_email_verification_on_invitation: false,
            disable_organization_deletion: false,
            hooks: OrganizationHooks::default(),
            send_invitation_email: None,
            teams: TeamOptions::default(),
            dynamic_access_control: DynamicAccessControlOptions::default(),
            access_control: None,
            roles: None,
            custom_roles: BTreeMap::new(),
            schema: OrganizationSchemaOptions::default(),
        }
    }
}

impl Default for TeamOptions {
    fn default() -> Self {
        Self {
            enabled: false,
            create_default_team: true,
            custom_create_default_team: None,
            maximum_teams: None,
            maximum_members_per_team: None,
            allow_removing_all_teams: false,
        }
    }
}

impl OrganizationOptions {
    pub fn builder() -> OrganizationOptionsBuilder {
        OrganizationOptionsBuilder::default()
    }

    pub(crate) fn to_metadata(&self) -> Value {
        json!({
            "allowUserToCreateOrganization": self.allow_user_to_create_organization,
            "organizationLimit": self.organization_limit.as_ref().map(|_| "<organization-limit>"),
            "creatorRole": self.creator_role,
            "membershipLimit": match &self.membership_limit {
                MembershipLimit::Fixed(limit) => json!(limit),
                MembershipLimit::Dynamic(_) => json!("<membership-limit>"),
            },
            "invitationExpiresIn": self.invitation_expires_in.whole_seconds(),
            "invitationLimit": self.invitation_limit,
            "cancelPendingInvitationsOnReInvite": self.cancel_pending_invitations_on_re_invite,
            "requireEmailVerificationOnInvitation": self.require_email_verification_on_invitation,
            "disableOrganizationDeletion": self.disable_organization_deletion,
            "teams": {
                "enabled": self.teams.enabled,
                "defaultTeam": {
                    "enabled": self.teams.create_default_team,
                    "customCreateDefaultTeam": self.teams.custom_create_default_team.is_some(),
                },
                "maximumTeams": self.teams.maximum_teams,
                "maximumMembersPerTeam": self.teams.maximum_members_per_team,
                "allowRemovingAllTeams": self.teams.allow_removing_all_teams,
            },
            "dynamicAccessControl": {
                "enabled": self.dynamic_access_control.enabled,
                "maximumRolesPerOrganization": self.dynamic_access_control.maximum_roles_per_organization,
            },
            "ac": self.access_control.is_some(),
            "roles": self.roles.as_ref().map(|roles| roles.keys().collect::<Vec<_>>()),
            "customRoles": self.custom_roles,
        })
    }
}

#[derive(Clone, Default)]
pub struct OrganizationOptionsBuilder {
    options: OrganizationOptions,
}

impl OrganizationOptionsBuilder {
    pub fn allow_user_to_create_organization(mut self, allow: bool) -> Self {
        self.options.allow_user_to_create_organization = allow;
        self
    }

    pub fn organization_limit(mut self, limit: usize) -> Self {
        self.options.organization_limit = Some(OrganizationLimit::Fixed(limit));
        self
    }

    pub fn organization_limit_dynamic(
        mut self,
        callback: super::limits::OrganizationLimitCallback,
    ) -> Self {
        self.options.organization_limit = Some(OrganizationLimit::Dynamic(callback));
        self
    }

    pub fn creator_role(mut self, role: impl Into<String>) -> Self {
        self.options.creator_role = role.into();
        self
    }

    pub fn membership_limit(mut self, limit: usize) -> Self {
        self.options.membership_limit = MembershipLimit::Fixed(limit);
        self
    }

    pub fn membership_limit_dynamic(
        mut self,
        callback: super::limits::MembershipLimitCallback,
    ) -> Self {
        self.options.membership_limit = MembershipLimit::Dynamic(callback);
        self
    }

    pub fn invitation_expires_in(mut self, expires_in: Duration) -> Self {
        self.options.invitation_expires_in = expires_in;
        self
    }

    pub fn invitation_limit(mut self, limit: usize) -> Self {
        self.options.invitation_limit = limit;
        self
    }

    pub fn cancel_pending_invitations_on_re_invite(mut self, cancel: bool) -> Self {
        self.options.cancel_pending_invitations_on_re_invite = cancel;
        self
    }

    pub fn require_email_verification_on_invitation(mut self, require: bool) -> Self {
        self.options.require_email_verification_on_invitation = require;
        self
    }

    pub fn disable_organization_deletion(mut self, disable: bool) -> Self {
        self.options.disable_organization_deletion = disable;
        self
    }

    pub fn hooks(mut self, hooks: OrganizationHooks) -> Self {
        self.options.hooks = hooks;
        self
    }

    pub fn send_invitation_email(mut self, hook: SendInvitationEmailHook) -> Self {
        self.options.send_invitation_email = Some(hook);
        self
    }

    pub fn teams(mut self, teams: TeamOptions) -> Self {
        self.options.teams = teams;
        self
    }

    pub fn dynamic_access_control(mut self, options: DynamicAccessControlOptions) -> Self {
        self.options.dynamic_access_control = options;
        self
    }

    pub fn access_control(mut self, access_control: AccessControl) -> Self {
        self.options.access_control = Some(access_control);
        self
    }

    pub fn roles(mut self, roles: BTreeMap<String, Role>) -> Self {
        self.options.roles = Some(roles);
        self
    }

    pub fn custom_role(mut self, role: impl Into<String>, permissions: serde_json::Value) -> Self {
        self.options.custom_roles.insert(role.into(), permissions);
        self
    }

    pub fn schema(mut self, schema: OrganizationSchemaOptions) -> Self {
        self.options.schema = schema;
        self
    }

    pub fn build(self) -> OrganizationOptions {
        self.options
    }
}