use super::types::OrganizationResponse;
use async_trait::async_trait;
use better_auth_core::{
AuthConfig, AuthContext, AuthRequest, AuthResult, AuthSchema, CreateInvitation, CreateMember,
CreateOrganization, CreateTeam, FieldValue, Invitation, Member, Team, TeamMember,
UpdateOrganization, UpdateTeam,
session::NativeSessionData,
store::{InvitationStore, MemberStore, OrganizationStore, TeamStore},
};
pub struct OrganizationEndpoint<'a> {
pub request: Option<&'a AuthRequest>,
pub auth_config: &'a AuthConfig,
pub organizations: &'a dyn OrganizationStore,
pub teams: &'a dyn TeamStore,
pub members: &'a dyn MemberStore,
pub invitations: &'a dyn InvitationStore,
}
impl<'a> OrganizationEndpoint<'a> {
pub(crate) fn new<S: AuthSchema>(
ctx: &'a AuthContext<S>,
request: Option<&'a AuthRequest>,
) -> Self {
Self {
request,
auth_config: &ctx.config,
organizations: ctx.database.as_ref(),
teams: ctx.database.as_ref(),
members: ctx.database.as_ref(),
invitations: ctx.database.as_ref(),
}
}
}
#[derive(Clone, Copy)]
pub struct OrganizationUser<'a> {
pub organization: &'a OrganizationResponse,
pub user: &'a FieldValue,
}
#[derive(Clone, Copy)]
pub struct OrganizationActor<'a> {
pub member: &'a Member,
pub user: &'a FieldValue,
}
#[derive(Clone, Copy)]
pub struct OrganizationMemberEvent<'a> {
pub member: &'a Member,
pub user: &'a FieldValue,
pub organization: &'a OrganizationResponse,
}
#[derive(Clone, Copy)]
pub struct OrganizationInvitationEvent<'a> {
pub invitation: &'a Invitation,
pub user: &'a FieldValue,
pub organization: &'a OrganizationResponse,
}
#[derive(Clone, Copy)]
pub struct OrganizationTeamEvent<'a> {
pub team: &'a Team,
pub user: Option<&'a FieldValue>,
pub organization: &'a OrganizationResponse,
}
#[derive(Clone, Copy)]
pub struct OrganizationTeamMemberTarget<'a> {
pub team: &'a Team,
pub user: &'a FieldValue,
pub organization: &'a OrganizationResponse,
}
#[derive(Debug, Clone)]
pub struct OrganizationMemberDraft {
pub additional_fields: better_auth_core::FieldMap,
pub organization_id: better_auth_core::SchemaValue<String>,
pub user_id: better_auth_core::SchemaValue<String>,
pub role: better_auth_core::SchemaValue<String>,
pub team_id: better_auth_core::SchemaValue<String>,
pub created_at: Option<better_auth_core::FieldDate>,
}
impl OrganizationMemberDraft {
pub(crate) fn into_create(mut self) -> CreateMember {
if !self.team_id.is_undefined() {
let _ = self
.additional_fields
.insert("teamId".into(), self.team_id.field_value());
}
CreateMember {
organization_id: self.organization_id,
user_id: self.user_id,
role: self.role,
additional_fields: self.additional_fields,
}
}
}
#[derive(Debug, Clone)]
pub struct OrganizationTeamDraft {
pub additional_fields: better_auth_core::FieldMap,
pub id: Option<String>,
pub name: better_auth_core::SchemaValue<String>,
pub organization_id: better_auth_core::SchemaValue<String>,
pub created_at: Option<better_auth_core::FieldDate>,
pub updated_at: Option<Option<better_auth_core::FieldDate>>,
}
impl OrganizationTeamDraft {
pub(crate) fn into_create(
mut self,
created_at: chrono::DateTime<chrono::Utc>,
updated_at: Option<chrono::DateTime<chrono::Utc>>,
) -> CreateTeam {
if self.created_at.is_some() {
let _ = self.additional_fields.remove("createdAt");
}
if self.updated_at.is_some() {
let _ = self.additional_fields.remove("updatedAt");
}
CreateTeam {
additional_fields: self.additional_fields,
id: self.id,
name: self.name,
organization_id: self.organization_id,
created_at: Some(self.created_at.unwrap_or_else(|| created_at.into())),
updated_at: self
.updated_at
.unwrap_or_else(|| updated_at.map(Into::into)),
}
}
}
#[derive(Debug, Clone)]
pub struct OrganizationInvitationDraft {
pub additional_fields: better_auth_core::FieldMap,
pub id: Option<String>,
pub organization_id: better_auth_core::SchemaValue<String>,
pub email: String,
pub role: String,
pub inviter_id: better_auth_core::SchemaValue<String>,
pub created_at: better_auth_core::SchemaValue<better_auth_core::FieldDate>,
pub status: better_auth_core::SchemaValue<better_auth_core::InvitationStatus>,
pub expires_at: better_auth_core::SchemaValue<better_auth_core::FieldDate>,
pub team_id: better_auth_core::SchemaValue<String>,
pub team_ids: better_auth_core::SchemaValue<Vec<String>>,
}
impl OrganizationInvitationDraft {
pub(crate) fn into_create(
mut self,
expires_at: chrono::DateTime<chrono::Utc>,
inviter_id: &FieldValue,
) -> AuthResult<CreateInvitation> {
for (name, value) in [
("createdAt", self.created_at.field_value()),
("status", self.status.field_value()),
("expiresAt", self.expires_at.field_value()),
] {
if !value.is_undefined() {
let _ = self.additional_fields.insert(name.into(), value);
}
}
if !self.inviter_id.field_value().strict_equals(inviter_id) {
let _ = self.additional_fields.remove("inviterId");
}
Ok(CreateInvitation {
additional_fields: self.additional_fields,
id: self.id,
created_at: None,
status: None,
organization_id: self.organization_id,
email: self.email,
role: self.role,
inviter_id: self.inviter_id,
expires_at: expires_at.into(),
team_id: super::input::join_invitation_teams(&self.team_ids)?,
})
}
}
#[async_trait]
pub trait OrganizationHooks: Send + Sync {
async fn before_create_organization(
&self,
_data: &mut CreateOrganization,
_user: &FieldValue,
) -> AuthResult<()> {
Ok(())
}
async fn after_create_organization(
&self,
_event: OrganizationMemberEvent<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn before_update_organization(
&self,
_data: &mut UpdateOrganization,
_actor: OrganizationActor<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn after_update_organization(
&self,
_organization: &OrganizationResponse,
_actor: OrganizationActor<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn before_delete_organization(
&self,
_event: OrganizationUser<'_>,
_ctx: OrganizationEndpoint<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn after_delete_organization(
&self,
_event: OrganizationUser<'_>,
_ctx: OrganizationEndpoint<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn before_add_member(
&self,
_data: &mut OrganizationMemberDraft,
_event: OrganizationUser<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn after_add_member(&self, _event: OrganizationMemberEvent<'_>) -> AuthResult<()> {
Ok(())
}
async fn before_remove_member(&self, _event: OrganizationMemberEvent<'_>) -> AuthResult<()> {
Ok(())
}
async fn after_remove_member(&self, _event: OrganizationMemberEvent<'_>) -> AuthResult<()> {
Ok(())
}
async fn before_update_member_role(
&self,
_role: &mut String,
_event: OrganizationMemberEvent<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn after_update_member_role(
&self,
_previous_role: &str,
_event: OrganizationMemberEvent<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn before_create_invitation(
&self,
_data: &mut OrganizationInvitationDraft,
_event: OrganizationUser<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn after_create_invitation(
&self,
_event: OrganizationInvitationEvent<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn before_accept_invitation(
&self,
_event: OrganizationInvitationEvent<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn after_accept_invitation(
&self,
_member: &Member,
_event: OrganizationInvitationEvent<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn before_reject_invitation(
&self,
_event: OrganizationInvitationEvent<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn after_reject_invitation(
&self,
_event: OrganizationInvitationEvent<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn before_cancel_invitation(
&self,
_event: OrganizationInvitationEvent<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn after_cancel_invitation(
&self,
_event: OrganizationInvitationEvent<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn before_create_team(
&self,
_data: &mut OrganizationTeamDraft,
_organization: &OrganizationResponse,
_user: Option<&FieldValue>,
) -> AuthResult<()> {
Ok(())
}
async fn after_create_team(&self, _event: OrganizationTeamEvent<'_>) -> AuthResult<()> {
Ok(())
}
async fn before_update_team(
&self,
_updates: &mut UpdateTeam,
_event: OrganizationTeamEvent<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn after_update_team(&self, _event: OrganizationTeamEvent<'_>) -> AuthResult<()> {
Ok(())
}
async fn before_delete_team(&self, _event: OrganizationTeamEvent<'_>) -> AuthResult<()> {
Ok(())
}
async fn after_delete_team(&self, _event: OrganizationTeamEvent<'_>) -> AuthResult<()> {
Ok(())
}
async fn before_add_team_member(
&self,
_target: OrganizationTeamMemberTarget<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn after_add_team_member(
&self,
_member: &TeamMember,
_target: OrganizationTeamMemberTarget<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn before_remove_team_member(
&self,
_member: &TeamMember,
_target: OrganizationTeamMemberTarget<'_>,
) -> AuthResult<()> {
Ok(())
}
async fn after_remove_team_member(
&self,
_member: &TeamMember,
_target: OrganizationTeamMemberTarget<'_>,
) -> AuthResult<()> {
Ok(())
}
}
pub type OrganizationSession<'a> = &'a NativeSessionData;
#[derive(Clone, Copy)]
pub struct OrganizationTeamLimit<'a> {
pub organization_id: &'a better_auth_core::FieldValue,
pub session: Option<OrganizationSession<'a>>,
}
#[derive(Clone, Copy)]
pub struct OrganizationTeamMemberLimit<'a> {
pub organization_id: &'a better_auth_core::FieldValue,
pub team_id: &'a better_auth_core::FieldValue,
pub session: OrganizationSession<'a>,
}
#[async_trait]
pub trait OrganizationPolicy: Send + Sync {
async fn allow_user_to_create_organization(
&self,
_user: &FieldValue,
) -> AuthResult<Option<bool>> {
Ok(None)
}
async fn organization_limit_reached(&self, _user: &FieldValue) -> AuthResult<Option<bool>> {
Ok(None)
}
async fn invitation_limit(
&self,
_event: OrganizationMemberEvent<'_>,
_ctx: OrganizationEndpoint<'_>,
) -> AuthResult<Option<usize>> {
Ok(None)
}
async fn maximum_teams(
&self,
_data: OrganizationTeamLimit<'_>,
_ctx: OrganizationEndpoint<'_>,
) -> AuthResult<Option<usize>> {
Ok(None)
}
async fn maximum_roles_per_organization(
&self,
_organization_id: &better_auth_core::FieldValue,
) -> AuthResult<Option<usize>> {
Ok(None)
}
async fn create_default_team(
&self,
_organization: &OrganizationResponse,
_ctx: OrganizationEndpoint<'_>,
) -> AuthResult<Option<Team>> {
Ok(None)
}
}
#[async_trait]
pub trait TeamMemberLimitPolicy: Send + Sync {
async fn maximum_members_per_team(
&self,
data: OrganizationTeamMemberLimit<'_>,
) -> AuthResult<usize>;
}
#[async_trait]
pub trait MembershipLimitPolicy: Send + Sync {
async fn membership_limit(&self, data: OrganizationUser<'_>) -> AuthResult<usize>;
}