better-auth-api 1.0.0-alpha.5

Plugin implementations for better-auth
use better_auth_core::{
    AuthContext, AuthError, AuthRecordFields, AuthRequest, AuthResponse, AuthResult, AuthSchema,
    FieldValue,
};

use super::{CreateBody, TeamBody, authorize, find_team, list_teams, optional_string};
use crate::plugins::organization::{
    OrganizationConfig, fields,
    handlers::{optional_session, request_present},
    hooks::*,
    request,
};

pub(super) async fn create(
    req: &AuthRequest,
    ctx: &AuthContext<impl AuthSchema>,
    config: &OrganizationConfig,
) -> AuthResult<AuthResponse> {
    let session = optional_session(req, ctx).await?;
    if session.is_none() && request_present(req, ctx) {
        return Err(AuthError::from_status(
            "UNAUTHORIZED",
            better_auth_core::FieldValue::Undefined,
        ));
    }
    let body: CreateBody = request::read(req, &config.schema)?;
    let additional_fields = body.additional_fields.clone();
    let organization_id = optional_string(body.organization_id, "body.organizationId")?;
    let org = organization_id
        .as_deref()
        .filter(|id| !id.is_empty())
        .map(FieldValue::from)
        .map(Ok)
        .or_else(|| {
            session
                .as_ref()
                .map(|session| session.session_property("activeOrganizationId"))
        })
        .transpose()?
        .filter(FieldValue::is_truthy)
        .ok_or_else(|| {
            AuthError::from_code(
                "BAD_REQUEST",
                "NO_ACTIVE_ORGANIZATION",
                "No active organization",
            )
        })?;
    if let Some(session) = &session {
        let member = ctx
            .database
            .get_member_with_user_value(&org, &session.user_property("id")?)
            .await?
            .ok_or_else(|| {
                AuthError::from_code(
                    "FORBIDDEN",
                    "YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION",
                    "You are not allowed to invite users to this organization",
                )
            })?;
        authorize(
            &member.member,
            &org,
            ("team", "create"),
            AuthError::from_code(
                "FORBIDDEN",
                "YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION",
                "You are not allowed to create teams in this organization",
            ),
            config,
            ctx,
        )
        .await?;
    }
    let teams = list_teams(&org, ctx, config).await?;
    if let Some(maximum) = config
        .team_limit(
            OrganizationTeamLimit {
                organization_id: &org,
                session: session.as_ref(),
            },
            OrganizationEndpoint::new(ctx, Some(req)),
        )
        .await?
        .filter(|limit| *limit > 0)
        && teams.len() >= maximum
    {
        return Err(AuthError::from_code(
            "BAD_REQUEST",
            "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS",
            "You have reached the maximum number of teams",
        ));
    }
    let organization = ctx
        .database
        .get_organization_by_id_value(&org)
        .await?
        .ok_or_else(|| {
            AuthError::from_code(
                "BAD_REQUEST",
                "ORGANIZATION_NOT_FOUND",
                "Organization not found",
            )
        })?;
    let organization_view = fields::organization(&organization, ctx)?;
    let created_at = chrono::Utc::now();
    let mut data = OrganizationTeamDraft {
        id: additional_fields
            .get("id")
            .and_then(better_auth_core::FieldValue::as_str)
            .map(str::to_owned),
        additional_fields,
        name: body.name,
        organization_id: better_auth_core::SchemaValue::from_field(org),
        created_at: None,
        updated_at: None,
    };
    if let Some(hooks) = &config.hooks {
        hooks
            .before_create_team(
                &mut data,
                &organization_view,
                session
                    .as_ref()
                    .map(|session| session.user_value())
                    .transpose()?
                    .as_ref(),
            )
            .await?;
    }
    let team = ctx
        .database
        .create_team(data.into_create(created_at, Some(created_at)))
        .await?;
    let team = fields::team(team, config)?;
    if let Some(hooks) = &config.hooks {
        hooks
            .after_create_team(OrganizationTeamEvent {
                team: &team,
                user: session
                    .as_ref()
                    .map(|session| session.user_value())
                    .transpose()?
                    .as_ref(),
                organization: &organization_view,
            })
            .await?;
    }
    Ok(AuthResponse::native(None, team.field_values()?.into()))
}

pub(super) async fn remove(
    req: &AuthRequest,
    ctx: &AuthContext<impl AuthSchema>,
    config: &OrganizationConfig,
) -> AuthResult<AuthResponse> {
    let session = optional_session(req, ctx).await?;
    let body: TeamBody = request::read(req, &config.schema)?;
    let org = body
        .organization_id
        .as_deref()
        .filter(|id| !id.is_empty())
        .map(FieldValue::from)
        .map(Ok)
        .or_else(|| {
            session
                .as_ref()
                .map(|session| session.session_property("activeOrganizationId"))
        })
        .transpose()?
        .filter(FieldValue::is_truthy)
        .ok_or_else(|| {
            AuthError::from_code(
                "BAD_REQUEST",
                "NO_ACTIVE_ORGANIZATION",
                "No active organization",
            )
        })?;
    if session.is_none() && request_present(req, ctx) {
        return Err(AuthError::from_status(
            "UNAUTHORIZED",
            better_auth_core::FieldValue::Undefined,
        ));
    }
    if let Some(session) = &session {
        let member = ctx
            .database
            .get_member_with_user_value(&org, &session.user_property("id")?)
            .await?
            .ok_or_else(|| {
                AuthError::from_code(
                    "FORBIDDEN",
                    "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM",
                    "You are not allowed to delete this team",
                )
            })?;
        if session
            .session_property("activeTeamId")?
            .strict_equals(&body.team_id.as_str().into())
        {
            return Err(AuthError::from_code(
                "FORBIDDEN",
                "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM",
                "You are not allowed to delete this team",
            ));
        }
        authorize(
            &member.member,
            &org,
            ("team", "delete"),
            AuthError::from_code(
                "FORBIDDEN",
                "YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION",
                "You are not allowed to delete teams in this organization",
            ),
            config,
            ctx,
        )
        .await?;
    }
    let team = find_team(&body.team_id.as_str().into(), &org, ctx, config).await?;
    if !team.organization_id.field_value().strict_equals(&org) {
        return Err(AuthError::from_code(
            "BAD_REQUEST",
            "TEAM_NOT_FOUND",
            "Team not found",
        ));
    }
    if !config.teams.allow_removing_all_teams && list_teams(&org, ctx, config).await?.len() <= 1 {
        return Err(AuthError::from_code(
            "BAD_REQUEST",
            "UNABLE_TO_REMOVE_LAST_TEAM",
            "Unable to remove last team",
        ));
    }
    let organization = ctx
        .database
        .get_organization_by_id_value(&org)
        .await?
        .ok_or_else(|| {
            AuthError::from_code(
                "BAD_REQUEST",
                "ORGANIZATION_NOT_FOUND",
                "Organization not found",
            )
        })?;
    let organization_view = fields::organization(&organization, ctx)?;
    if let Some(hooks) = &config.hooks {
        hooks
            .before_delete_team(OrganizationTeamEvent {
                team: &team,
                user: session
                    .as_ref()
                    .map(|session| session.user_value())
                    .transpose()?
                    .as_ref(),
                organization: &organization_view,
            })
            .await?;
    }
    ctx.database
        .delete_team_for_organization_value(&team.id.field_value(), &org)
        .await?;
    if let Some(hooks) = &config.hooks {
        hooks
            .after_delete_team(OrganizationTeamEvent {
                team: &team,
                user: session
                    .as_ref()
                    .map(|session| session.user_value())
                    .transpose()?
                    .as_ref(),
                organization: &organization_view,
            })
            .await?;
    }
    AuthResponse::json(
        None,
        &serde_json::json!({"message":"Team removed successfully."}),
    )
}