rustauth_plugins/organization/
provisioning.rs1use 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
18pub 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}