rustauth_plugins/organization/
options.rs1use 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}