Skip to main content

rustauth_plugins/organization/
options.rs

1use rustauth_core::db::TableOptions;
2use rustauth_core::outbound::OutboundSendFuture;
3use serde_json::{json, Value};
4use std::collections::BTreeMap;
5use std::sync::Arc;
6use time::Duration;
7
8use crate::access::{AccessControl, Role};
9
10use super::hooks::OrganizationHooks;
11use super::limits::{MembershipLimit, OrganizationLimit};
12use super::{Invitation, Member, Organization};
13
14pub type SendInvitationEmailHook = Arc<dyn Fn(InvitationEmail) -> OutboundSendFuture + Send + Sync>;
15
16pub type CustomCreateDefaultTeamFuture = std::pin::Pin<
17    Box<
18        dyn std::future::Future<
19                Output = Result<DefaultTeamSpec, rustauth_core::error::RustAuthError>,
20            > + Send,
21    >,
22>;
23pub type CustomCreateDefaultTeamHook =
24    Arc<dyn Fn(Organization) -> CustomCreateDefaultTeamFuture + Send + Sync>;
25
26#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct DefaultTeamSpec {
28    pub name: String,
29}
30
31#[derive(Clone)]
32pub struct OrganizationOptions {
33    pub allow_user_to_create_organization: bool,
34    pub organization_limit: Option<OrganizationLimit>,
35    pub creator_role: String,
36    pub membership_limit: MembershipLimit,
37    pub invitation_expires_in: Duration,
38    pub invitation_limit: usize,
39    pub cancel_pending_invitations_on_re_invite: bool,
40    pub require_email_verification_on_invitation: bool,
41    pub disable_organization_deletion: bool,
42    pub hooks: OrganizationHooks,
43    pub send_invitation_email: Option<SendInvitationEmailHook>,
44    pub teams: TeamOptions,
45    pub dynamic_access_control: DynamicAccessControlOptions,
46    pub access_control: Option<AccessControl>,
47    pub roles: Option<BTreeMap<String, Role>>,
48    pub custom_roles: BTreeMap<String, serde_json::Value>,
49    pub schema: OrganizationSchemaOptions,
50}
51
52#[derive(Clone)]
53pub struct TeamOptions {
54    pub enabled: bool,
55    pub create_default_team: bool,
56    pub custom_create_default_team: Option<CustomCreateDefaultTeamHook>,
57    pub maximum_teams: Option<usize>,
58    pub maximum_members_per_team: Option<usize>,
59    pub allow_removing_all_teams: bool,
60}
61
62impl std::fmt::Debug for TeamOptions {
63    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        formatter
65            .debug_struct("TeamOptions")
66            .field("enabled", &self.enabled)
67            .field("create_default_team", &self.create_default_team)
68            .field(
69                "custom_create_default_team",
70                &self
71                    .custom_create_default_team
72                    .as_ref()
73                    .map(|_| "<custom-create-default-team>"),
74            )
75            .field("maximum_teams", &self.maximum_teams)
76            .field("maximum_members_per_team", &self.maximum_members_per_team)
77            .field("allow_removing_all_teams", &self.allow_removing_all_teams)
78            .finish()
79    }
80}
81
82#[derive(Clone, Debug, Default, PartialEq, Eq)]
83pub struct DynamicAccessControlOptions {
84    pub enabled: bool,
85    pub maximum_roles_per_organization: Option<usize>,
86}
87
88#[derive(Clone, Debug, Default, PartialEq, Eq)]
89pub struct OrganizationSchemaOptions {
90    pub organization: TableOptions,
91    pub member: TableOptions,
92    pub invitation: TableOptions,
93    pub team: TableOptions,
94    pub team_member: TableOptions,
95    pub organization_role: TableOptions,
96}
97
98#[derive(Debug, Clone)]
99pub struct InvitationEmail {
100    pub id: String,
101    pub role: String,
102    pub email: String,
103    pub organization: Organization,
104    pub invitation: Invitation,
105    pub inviter: Member,
106}
107
108impl Default for OrganizationOptions {
109    fn default() -> Self {
110        Self {
111            allow_user_to_create_organization: true,
112            organization_limit: None,
113            creator_role: "owner".to_owned(),
114            membership_limit: MembershipLimit::default(),
115            invitation_expires_in: Duration::hours(48),
116            invitation_limit: 100,
117            cancel_pending_invitations_on_re_invite: false,
118            require_email_verification_on_invitation: false,
119            disable_organization_deletion: false,
120            hooks: OrganizationHooks::default(),
121            send_invitation_email: None,
122            teams: TeamOptions::default(),
123            dynamic_access_control: DynamicAccessControlOptions::default(),
124            access_control: None,
125            roles: None,
126            custom_roles: BTreeMap::new(),
127            schema: OrganizationSchemaOptions::default(),
128        }
129    }
130}
131
132impl Default for TeamOptions {
133    fn default() -> Self {
134        Self {
135            enabled: false,
136            create_default_team: true,
137            custom_create_default_team: None,
138            maximum_teams: None,
139            maximum_members_per_team: None,
140            allow_removing_all_teams: false,
141        }
142    }
143}
144
145impl OrganizationOptions {
146    pub fn builder() -> OrganizationOptionsBuilder {
147        OrganizationOptionsBuilder::default()
148    }
149
150    pub(crate) fn to_metadata(&self) -> Value {
151        json!({
152            "allowUserToCreateOrganization": self.allow_user_to_create_organization,
153            "organizationLimit": self.organization_limit.as_ref().map(|_| "<organization-limit>"),
154            "creatorRole": self.creator_role,
155            "membershipLimit": match &self.membership_limit {
156                MembershipLimit::Fixed(limit) => json!(limit),
157                MembershipLimit::Dynamic(_) => json!("<membership-limit>"),
158            },
159            "invitationExpiresIn": self.invitation_expires_in.whole_seconds(),
160            "invitationLimit": self.invitation_limit,
161            "cancelPendingInvitationsOnReInvite": self.cancel_pending_invitations_on_re_invite,
162            "requireEmailVerificationOnInvitation": self.require_email_verification_on_invitation,
163            "disableOrganizationDeletion": self.disable_organization_deletion,
164            "teams": {
165                "enabled": self.teams.enabled,
166                "defaultTeam": {
167                    "enabled": self.teams.create_default_team,
168                    "customCreateDefaultTeam": self.teams.custom_create_default_team.is_some(),
169                },
170                "maximumTeams": self.teams.maximum_teams,
171                "maximumMembersPerTeam": self.teams.maximum_members_per_team,
172                "allowRemovingAllTeams": self.teams.allow_removing_all_teams,
173            },
174            "dynamicAccessControl": {
175                "enabled": self.dynamic_access_control.enabled,
176                "maximumRolesPerOrganization": self.dynamic_access_control.maximum_roles_per_organization,
177            },
178            "ac": self.access_control.is_some(),
179            "roles": self.roles.as_ref().map(|roles| roles.keys().collect::<Vec<_>>()),
180            "customRoles": self.custom_roles,
181        })
182    }
183}
184
185#[derive(Clone, Default)]
186pub struct OrganizationOptionsBuilder {
187    options: OrganizationOptions,
188}
189
190impl OrganizationOptionsBuilder {
191    pub fn allow_user_to_create_organization(mut self, allow: bool) -> Self {
192        self.options.allow_user_to_create_organization = allow;
193        self
194    }
195
196    pub fn organization_limit(mut self, limit: usize) -> Self {
197        self.options.organization_limit = Some(OrganizationLimit::Fixed(limit));
198        self
199    }
200
201    pub fn organization_limit_dynamic(
202        mut self,
203        callback: super::limits::OrganizationLimitCallback,
204    ) -> Self {
205        self.options.organization_limit = Some(OrganizationLimit::Dynamic(callback));
206        self
207    }
208
209    pub fn creator_role(mut self, role: impl Into<String>) -> Self {
210        self.options.creator_role = role.into();
211        self
212    }
213
214    pub fn membership_limit(mut self, limit: usize) -> Self {
215        self.options.membership_limit = MembershipLimit::Fixed(limit);
216        self
217    }
218
219    pub fn membership_limit_dynamic(
220        mut self,
221        callback: super::limits::MembershipLimitCallback,
222    ) -> Self {
223        self.options.membership_limit = MembershipLimit::Dynamic(callback);
224        self
225    }
226
227    pub fn invitation_expires_in(mut self, expires_in: Duration) -> Self {
228        self.options.invitation_expires_in = expires_in;
229        self
230    }
231
232    pub fn invitation_limit(mut self, limit: usize) -> Self {
233        self.options.invitation_limit = limit;
234        self
235    }
236
237    pub fn cancel_pending_invitations_on_re_invite(mut self, cancel: bool) -> Self {
238        self.options.cancel_pending_invitations_on_re_invite = cancel;
239        self
240    }
241
242    pub fn require_email_verification_on_invitation(mut self, require: bool) -> Self {
243        self.options.require_email_verification_on_invitation = require;
244        self
245    }
246
247    pub fn disable_organization_deletion(mut self, disable: bool) -> Self {
248        self.options.disable_organization_deletion = disable;
249        self
250    }
251
252    pub fn hooks(mut self, hooks: OrganizationHooks) -> Self {
253        self.options.hooks = hooks;
254        self
255    }
256
257    pub fn send_invitation_email(mut self, hook: SendInvitationEmailHook) -> Self {
258        self.options.send_invitation_email = Some(hook);
259        self
260    }
261
262    pub fn teams(mut self, teams: TeamOptions) -> Self {
263        self.options.teams = teams;
264        self
265    }
266
267    pub fn dynamic_access_control(mut self, options: DynamicAccessControlOptions) -> Self {
268        self.options.dynamic_access_control = options;
269        self
270    }
271
272    pub fn access_control(mut self, access_control: AccessControl) -> Self {
273        self.options.access_control = Some(access_control);
274        self
275    }
276
277    pub fn roles(mut self, roles: BTreeMap<String, Role>) -> Self {
278        self.options.roles = Some(roles);
279        self
280    }
281
282    pub fn custom_role(mut self, role: impl Into<String>, permissions: serde_json::Value) -> Self {
283        self.options.custom_roles.insert(role.into(), permissions);
284        self
285    }
286
287    pub fn schema(mut self, schema: OrganizationSchemaOptions) -> Self {
288        self.options.schema = schema;
289        self
290    }
291
292    pub fn build(self) -> OrganizationOptions {
293        self.options
294    }
295}