Skip to main content

rustauth_plugins/organization/
provisioning.rs

1use rustauth_core::db::{DbAdapter, User};
2use rustauth_core::error::RustAuthError;
3
4use super::additional_fields;
5use super::hooks::{AfterAddMember, BeforeAddMember, MemberHookData};
6use super::options::OrganizationOptions;
7use super::permissions::is_known_static_role;
8use super::store::OrganizationStore;
9use super::Member;
10
11#[derive(Debug, Clone)]
12pub struct ProvisionOrganizationMemberInput<'a> {
13    pub organization_id: &'a str,
14    pub user: &'a User,
15    pub role: &'a str,
16}
17
18/// Provision an organization membership through the same server-side
19/// semantics used by the organization plugin routes.
20pub async fn provision_organization_member(
21    adapter: &dyn DbAdapter,
22    options: &OrganizationOptions,
23    input: ProvisionOrganizationMemberInput<'_>,
24) -> Result<Option<Member>, RustAuthError> {
25    let store = OrganizationStore::new(adapter);
26    if store
27        .member_by_org_user(input.organization_id, &input.user.id)
28        .await?
29        .is_some()
30    {
31        return Ok(None);
32    }
33    if super::limits::membership_limit_reached(options, &store, input.organization_id, input.user)
34        .await?
35    {
36        return Err(RustAuthError::Api(
37            "ORGANIZATION_MEMBERSHIP_LIMIT_REACHED".to_owned(),
38        ));
39    }
40    let Some(organization) = store.organization_by_id(input.organization_id).await? else {
41        return Err(RustAuthError::Api("ORGANIZATION_NOT_FOUND".to_owned()));
42    };
43    let mut member_data = MemberHookData {
44        organization_id: input.organization_id.to_owned(),
45        user_id: input.user.id.clone(),
46        role: super::permissions::parse_roles(input.role),
47    };
48    if !roles_exist(&store, input.organization_id, &member_data.role, options).await? {
49        return Err(RustAuthError::Api("ROLE_NOT_FOUND".to_owned()));
50    }
51    if let Some(hook) = &options.hooks.before_add_member {
52        member_data = hook(&BeforeAddMember {
53            organization: organization.clone(),
54            user: input.user.clone(),
55            member: member_data,
56        })?;
57    }
58    if member_data.organization_id != input.organization_id || member_data.user_id != input.user.id
59    {
60        return Err(RustAuthError::Api("INVALID_REQUEST_BODY".to_owned()));
61    }
62    if !roles_exist(&store, input.organization_id, &member_data.role, options).await? {
63        return Err(RustAuthError::Api("ROLE_NOT_FOUND".to_owned()));
64    }
65    let mut member = store
66        .create_member(
67            &member_data.organization_id,
68            &member_data.user_id,
69            &member_data.role,
70            rustauth_core::db::DbRecord::new(),
71        )
72        .await?;
73    additional_fields::retain_returned(
74        &mut member.additional_fields,
75        &options.schema.member.additional_fields,
76    );
77    if let Some(hook) = &options.hooks.after_add_member {
78        hook(&AfterAddMember {
79            organization,
80            member: member.clone(),
81            user: input.user.clone(),
82        })?;
83    }
84    Ok(Some(member))
85}
86
87async fn roles_exist(
88    store: &OrganizationStore<'_>,
89    organization_id: &str,
90    roles: &str,
91    options: &OrganizationOptions,
92) -> Result<bool, RustAuthError> {
93    for role in roles
94        .split(',')
95        .map(str::trim)
96        .filter(|role| !role.is_empty())
97    {
98        if is_known_static_role(role, options) {
99            continue;
100        }
101        if !options.dynamic_access_control.enabled {
102            return Ok(false);
103        }
104        if store
105            .organization_role_by_name(organization_id, role)
106            .await?
107            .is_none()
108        {
109            return Ok(false);
110        }
111    }
112    Ok(true)
113}