1use super::types::OrganizationResponse;
4
5use async_trait::async_trait;
6use better_auth_core::{
7 AuthConfig, AuthContext, AuthRequest, AuthResult, AuthSchema, CreateInvitation, CreateMember,
8 CreateOrganization, CreateTeam, Invitation, Member, Team, TeamMember, UpdateOrganization,
9 UpdateTeam,
10 store::{InvitationStore, MemberStore, OrganizationStore, TeamStore},
11 wire::{SessionView, UserView},
12};
13
14pub struct OrganizationEndpoint<'a> {
16 pub request: Option<&'a AuthRequest>,
18 pub auth_config: &'a AuthConfig,
20 pub organizations: &'a dyn OrganizationStore,
22 pub teams: &'a dyn TeamStore,
24 pub members: &'a dyn MemberStore,
26 pub invitations: &'a dyn InvitationStore,
28}
29
30impl<'a> OrganizationEndpoint<'a> {
31 pub(crate) fn new<S: AuthSchema>(
32 ctx: &'a AuthContext<S>,
33 request: Option<&'a AuthRequest>,
34 ) -> Self {
35 Self {
36 request,
37 auth_config: &ctx.config,
38 organizations: ctx.database.as_ref(),
39 teams: ctx.database.as_ref(),
40 members: ctx.database.as_ref(),
41 invitations: ctx.database.as_ref(),
42 }
43 }
44}
45
46#[derive(Clone, Copy)]
48pub struct OrganizationUser<'a> {
49 pub organization: &'a OrganizationResponse,
51 pub user: &'a UserView,
53}
54
55#[derive(Clone, Copy)]
57pub struct OrganizationActor<'a> {
58 pub member: &'a Member,
60 pub user: &'a UserView,
62}
63
64#[derive(Clone, Copy)]
66pub struct OrganizationMemberEvent<'a> {
67 pub member: &'a Member,
69 pub user: &'a UserView,
71 pub organization: &'a OrganizationResponse,
73}
74
75#[derive(Clone, Copy)]
77pub struct OrganizationInvitationEvent<'a> {
78 pub invitation: &'a Invitation,
80 pub user: &'a UserView,
82 pub organization: &'a OrganizationResponse,
84}
85
86#[derive(Clone, Copy)]
88pub struct OrganizationTeamEvent<'a> {
89 pub team: &'a Team,
91 pub user: Option<&'a UserView>,
93 pub organization: &'a OrganizationResponse,
95}
96
97#[derive(Clone, Copy)]
99pub struct OrganizationTeamMemberTarget<'a> {
100 pub team: &'a Team,
102 pub user: &'a UserView,
104 pub organization: &'a OrganizationResponse,
106}
107
108#[derive(Debug, Clone)]
110pub struct OrganizationMemberDraft {
111 pub additional_fields: better_auth_core::FieldMap,
112
113 pub organization_id: better_auth_core::SchemaValue<String>,
114 pub user_id: better_auth_core::SchemaValue<String>,
115 pub role: better_auth_core::SchemaValue<String>,
116 pub team_id: Option<String>,
118 pub created_at: Option<better_auth_core::FieldDate>,
120}
121impl OrganizationMemberDraft {
122 pub(crate) fn into_create(mut self) -> CreateMember {
123 if let Some(team_id) = self.team_id {
124 let _ = self
125 .additional_fields
126 .insert("teamId".into(), team_id.into());
127 }
128 CreateMember {
129 organization_id: self.organization_id,
130 user_id: self.user_id,
131 role: self.role,
132 additional_fields: self.additional_fields,
133 }
134 }
135}
136
137#[derive(Debug, Clone)]
139pub struct OrganizationTeamDraft {
140 pub additional_fields: better_auth_core::FieldMap,
141
142 pub id: Option<String>,
143 pub name: better_auth_core::SchemaValue<String>,
144 pub organization_id: better_auth_core::SchemaValue<String>,
145 pub created_at: Option<better_auth_core::FieldDate>,
146 pub updated_at: Option<Option<better_auth_core::FieldDate>>,
148}
149impl OrganizationTeamDraft {
150 pub(crate) fn into_create(
151 mut self,
152 created_at: chrono::DateTime<chrono::Utc>,
153 updated_at: Option<chrono::DateTime<chrono::Utc>>,
154 ) -> CreateTeam {
155 if self.created_at.is_some() {
156 let _ = self.additional_fields.remove("createdAt");
157 }
158 if self.updated_at.is_some() {
159 let _ = self.additional_fields.remove("updatedAt");
160 }
161 CreateTeam {
162 additional_fields: self.additional_fields,
163 id: self.id,
164 name: self.name,
165 organization_id: self.organization_id,
166 created_at: Some(self.created_at.unwrap_or_else(|| created_at.into())),
167 updated_at: self
168 .updated_at
169 .unwrap_or_else(|| updated_at.map(Into::into)),
170 }
171 }
172}
173
174#[derive(Debug, Clone)]
176pub struct OrganizationInvitationDraft {
177 pub additional_fields: better_auth_core::FieldMap,
178
179 pub id: Option<String>,
180 pub organization_id: String,
181 pub email: String,
182 pub role: String,
183 pub inviter_id: String,
184 pub created_at: better_auth_core::SchemaValue<better_auth_core::FieldDate>,
185 pub status: better_auth_core::SchemaValue<better_auth_core::InvitationStatus>,
186 pub expires_at: better_auth_core::SchemaValue<better_auth_core::FieldDate>,
187 pub team_id: better_auth_core::SchemaValue<String>,
189 pub team_ids: better_auth_core::SchemaValue<Vec<String>>,
191}
192impl OrganizationInvitationDraft {
193 pub(crate) fn into_create(
194 mut self,
195 expires_at: chrono::DateTime<chrono::Utc>,
196 inviter_id: &str,
197 ) -> AuthResult<CreateInvitation> {
198 for (name, value) in [
199 ("createdAt", self.created_at.field_value()),
200 ("status", self.status.field_value()),
201 ("expiresAt", self.expires_at.field_value()),
202 ] {
203 if !value.is_undefined() {
204 let _ = self.additional_fields.insert(name.into(), value);
205 }
206 }
207 if self.inviter_id != inviter_id {
208 let _ = self.additional_fields.remove("inviterId");
209 }
210 Ok(CreateInvitation {
211 additional_fields: self.additional_fields,
212 id: self.id,
213 created_at: None,
214 status: None,
215 organization_id: self.organization_id,
216 email: self.email,
217 role: self.role,
218 inviter_id: self.inviter_id,
219 expires_at: expires_at.into(),
220 team_id: super::input::join_invitation_teams(&self.team_ids)?,
221 })
222 }
223}
224
225#[async_trait]
230pub trait OrganizationHooks: Send + Sync {
231 async fn before_create_organization(
233 &self,
234 _data: &mut CreateOrganization,
235 _user: &UserView,
236 ) -> AuthResult<()> {
237 Ok(())
238 }
239 async fn after_create_organization(
241 &self,
242 _event: OrganizationMemberEvent<'_>,
243 ) -> AuthResult<()> {
244 Ok(())
245 }
246 async fn before_update_organization(
248 &self,
249 _data: &mut UpdateOrganization,
250 _actor: OrganizationActor<'_>,
251 ) -> AuthResult<()> {
252 Ok(())
253 }
254 async fn after_update_organization(
256 &self,
257 _organization: &OrganizationResponse,
258 _actor: OrganizationActor<'_>,
259 ) -> AuthResult<()> {
260 Ok(())
261 }
262 async fn before_delete_organization(
264 &self,
265 _event: OrganizationUser<'_>,
266 _ctx: OrganizationEndpoint<'_>,
267 ) -> AuthResult<()> {
268 Ok(())
269 }
270 async fn after_delete_organization(
272 &self,
273 _event: OrganizationUser<'_>,
274 _ctx: OrganizationEndpoint<'_>,
275 ) -> AuthResult<()> {
276 Ok(())
277 }
278 async fn before_add_member(
280 &self,
281 _data: &mut OrganizationMemberDraft,
282 _event: OrganizationUser<'_>,
283 ) -> AuthResult<()> {
284 Ok(())
285 }
286 async fn after_add_member(&self, _event: OrganizationMemberEvent<'_>) -> AuthResult<()> {
288 Ok(())
289 }
290 async fn before_remove_member(&self, _event: OrganizationMemberEvent<'_>) -> AuthResult<()> {
292 Ok(())
293 }
294 async fn after_remove_member(&self, _event: OrganizationMemberEvent<'_>) -> AuthResult<()> {
296 Ok(())
297 }
298 async fn before_update_member_role(
300 &self,
301 _role: &mut String,
302 _event: OrganizationMemberEvent<'_>,
303 ) -> AuthResult<()> {
304 Ok(())
305 }
306 async fn after_update_member_role(
308 &self,
309 _previous_role: &str,
310 _event: OrganizationMemberEvent<'_>,
311 ) -> AuthResult<()> {
312 Ok(())
313 }
314 async fn before_create_invitation(
316 &self,
317 _data: &mut OrganizationInvitationDraft,
318 _event: OrganizationUser<'_>,
319 ) -> AuthResult<()> {
320 Ok(())
321 }
322 async fn after_create_invitation(
324 &self,
325 _event: OrganizationInvitationEvent<'_>,
326 ) -> AuthResult<()> {
327 Ok(())
328 }
329 async fn before_accept_invitation(
331 &self,
332 _event: OrganizationInvitationEvent<'_>,
333 ) -> AuthResult<()> {
334 Ok(())
335 }
336 async fn after_accept_invitation(
338 &self,
339 _member: &Member,
340 _event: OrganizationInvitationEvent<'_>,
341 ) -> AuthResult<()> {
342 Ok(())
343 }
344 async fn before_reject_invitation(
346 &self,
347 _event: OrganizationInvitationEvent<'_>,
348 ) -> AuthResult<()> {
349 Ok(())
350 }
351 async fn after_reject_invitation(
353 &self,
354 _event: OrganizationInvitationEvent<'_>,
355 ) -> AuthResult<()> {
356 Ok(())
357 }
358 async fn before_cancel_invitation(
360 &self,
361 _event: OrganizationInvitationEvent<'_>,
362 ) -> AuthResult<()> {
363 Ok(())
364 }
365 async fn after_cancel_invitation(
367 &self,
368 _event: OrganizationInvitationEvent<'_>,
369 ) -> AuthResult<()> {
370 Ok(())
371 }
372 async fn before_create_team(
374 &self,
375 _data: &mut OrganizationTeamDraft,
376 _organization: &OrganizationResponse,
377 _user: Option<&UserView>,
378 ) -> AuthResult<()> {
379 Ok(())
380 }
381 async fn after_create_team(&self, _event: OrganizationTeamEvent<'_>) -> AuthResult<()> {
383 Ok(())
384 }
385 async fn before_update_team(
387 &self,
388 _updates: &mut UpdateTeam,
389 _event: OrganizationTeamEvent<'_>,
390 ) -> AuthResult<()> {
391 Ok(())
392 }
393 async fn after_update_team(&self, _event: OrganizationTeamEvent<'_>) -> AuthResult<()> {
395 Ok(())
396 }
397 async fn before_delete_team(&self, _event: OrganizationTeamEvent<'_>) -> AuthResult<()> {
399 Ok(())
400 }
401 async fn after_delete_team(&self, _event: OrganizationTeamEvent<'_>) -> AuthResult<()> {
403 Ok(())
404 }
405 async fn before_add_team_member(
407 &self,
408 _target: OrganizationTeamMemberTarget<'_>,
409 ) -> AuthResult<()> {
410 Ok(())
411 }
412 async fn after_add_team_member(
414 &self,
415 _member: &TeamMember,
416 _target: OrganizationTeamMemberTarget<'_>,
417 ) -> AuthResult<()> {
418 Ok(())
419 }
420 async fn before_remove_team_member(
422 &self,
423 _member: &TeamMember,
424 _target: OrganizationTeamMemberTarget<'_>,
425 ) -> AuthResult<()> {
426 Ok(())
427 }
428 async fn after_remove_team_member(
430 &self,
431 _member: &TeamMember,
432 _target: OrganizationTeamMemberTarget<'_>,
433 ) -> AuthResult<()> {
434 Ok(())
435 }
436}
437
438#[derive(Clone, Copy)]
440pub struct OrganizationSession<'a> {
441 pub user: &'a UserView,
443 pub session: &'a SessionView,
445}
446
447#[derive(Clone, Copy)]
449pub struct OrganizationTeamLimit<'a> {
450 pub organization_id: &'a str,
452 pub session: Option<OrganizationSession<'a>>,
454}
455
456#[derive(Clone, Copy)]
458pub struct OrganizationTeamMemberLimit<'a> {
459 pub organization_id: &'a str,
461 pub team_id: &'a str,
463 pub session: OrganizationSession<'a>,
465}
466
467#[async_trait]
469pub trait OrganizationPolicy: Send + Sync {
470 async fn allow_user_to_create_organization(
472 &self,
473 _user: &UserView,
474 ) -> AuthResult<Option<bool>> {
475 Ok(None)
476 }
477 async fn organization_limit_reached(&self, _user: &UserView) -> AuthResult<Option<bool>> {
479 Ok(None)
480 }
481 async fn invitation_limit(
483 &self,
484 _event: OrganizationMemberEvent<'_>,
485 _ctx: OrganizationEndpoint<'_>,
486 ) -> AuthResult<Option<usize>> {
487 Ok(None)
488 }
489 async fn maximum_teams(
491 &self,
492 _data: OrganizationTeamLimit<'_>,
493 _ctx: OrganizationEndpoint<'_>,
494 ) -> AuthResult<Option<usize>> {
495 Ok(None)
496 }
497 async fn maximum_roles_per_organization(
499 &self,
500 _organization_id: &str,
501 ) -> AuthResult<Option<usize>> {
502 Ok(None)
503 }
504 async fn create_default_team(
506 &self,
507 _organization: &OrganizationResponse,
508 _ctx: OrganizationEndpoint<'_>,
509 ) -> AuthResult<Option<Team>> {
510 Ok(None)
511 }
512}
513
514#[async_trait]
516pub trait TeamMemberLimitPolicy: Send + Sync {
517 async fn maximum_members_per_team(
518 &self,
519 data: OrganizationTeamMemberLimit<'_>,
520 ) -> AuthResult<usize>;
521}
522
523#[async_trait]
525pub trait MembershipLimitPolicy: Send + Sync {
526 async fn membership_limit(&self, data: OrganizationUser<'_>) -> AuthResult<usize>;
527}