rustauth-plugins 0.2.0

Official RustAuth plugin modules.
Documentation
use ::http::{Method, StatusCode};
use rustauth_core::api::{create_auth_endpoint, AsyncAuthEndpoint, AuthEndpointOptions};
use serde::Deserialize;

use crate::organization::hooks::{AfterRemoveMember, BeforeRemoveMember};
use crate::organization::http;
use crate::organization::options::OrganizationOptions;
use crate::organization::store::OrganizationStore;

use super::validation::{is_last_owner, require_session};

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct LeaveBody {
    #[serde(default)]
    organization_id: Option<String>,
}

pub(super) fn leave(options: OrganizationOptions) -> AsyncAuthEndpoint {
    let options = std::sync::Arc::new(options);
    create_auth_endpoint(
        "/organization/leave",
        Method::POST,
        AuthEndpointOptions::new().operation_id("organizationLeave"),
        move |context, request| {
            let options = std::sync::Arc::clone(&options);
            async move {
                let adapter = context.require_adapter()?;
                let store = OrganizationStore::new(adapter.as_ref());
                let session = require_session(&context, &request, &store).await?;
                let body: LeaveBody = http::body(&request)?;
                let Some(organization_id) = super::resolve_organization_id(
                    body.organization_id,
                    session.active_organization_id.as_deref(),
                ) else {
                    return http::organization_error(
                        StatusCode::BAD_REQUEST,
                        "NO_ACTIVE_ORGANIZATION",
                    );
                };
                let Some(member) = store
                    .member_by_org_user(&organization_id, &session.user.id)
                    .await?
                else {
                    return http::organization_error(StatusCode::BAD_REQUEST, "MEMBER_NOT_FOUND");
                };
                if is_last_owner(&store, &organization_id, &member, &options).await? {
                    return http::organization_error(
                        StatusCode::BAD_REQUEST,
                        "YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER",
                    );
                }
                let Some(organization) = store.organization_by_id(&organization_id).await? else {
                    return http::organization_error(
                        StatusCode::BAD_REQUEST,
                        "ORGANIZATION_NOT_FOUND",
                    );
                };
                if let Some(hook) = &options.hooks.before_remove_member {
                    hook(&BeforeRemoveMember {
                        organization: organization.clone(),
                        member: member.clone(),
                        user: session.user.clone(),
                    })?;
                }
                if options.teams.enabled {
                    store
                        .delete_team_members_for_user(&organization_id, &session.user.id)
                        .await?;
                }
                store.delete_member(&member.id).await?;
                if let Some(hook) = &options.hooks.after_remove_member {
                    hook(&AfterRemoveMember {
                        organization,
                        member: member.clone(),
                        user: session.user.clone(),
                    })?;
                }
                let cookies = if session.active_organization_id.as_deref() == Some(&organization_id)
                {
                    store
                        .set_active_organization(&session.session.token, None)
                        .await?;
                    if options.teams.enabled {
                        store.set_active_team(&session.session.token, None).await?;
                    }
                    http::refreshed_session_cookies(&context, &session.session, &session.user)?
                } else {
                    Vec::new()
                };
                http::json_with_cookies(
                    StatusCode::OK,
                    &serde_json::json!({ "member": member }),
                    cookies,
                )
            }
        },
    )
}