1pub mod handlers;
2pub mod rbac;
3pub mod types;
4
5use std::collections::HashMap;
6
7use async_trait::async_trait;
8use better_auth_core::error::AuthResult;
9use better_auth_core::plugin::{AuthContext, AuthPlugin, AuthRoute};
10use better_auth_core::types::{AuthRequest, AuthResponse, HttpMethod};
11
12#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
14pub struct RolePermissions {
15 pub organization: Vec<String>,
16 pub member: Vec<String>,
17 pub invitation: Vec<String>,
18 pub api_key: Vec<String>,
22}
23
24#[derive(Debug, Clone, better_auth_core::PluginConfig)]
26#[plugin(name = "OrganizationPlugin")]
27pub struct OrganizationConfig {
28 #[config(default = true)]
30 pub allow_user_to_create_organization: bool,
31 #[config(default = None)]
33 pub organization_limit: Option<usize>,
34 #[config(default = Some(100))]
36 pub membership_limit: Option<usize>,
37 #[config(default = "owner".to_string())]
39 pub creator_role: String,
40 #[config(default = 60 * 60 * 48)]
42 pub invitation_expires_in: u64,
43 #[config(default = Some(100))]
45 pub invitation_limit: Option<usize>,
46 #[config(default = false)]
48 pub disable_organization_deletion: bool,
49 #[config(default = HashMap::new(), skip)]
51 pub roles: HashMap<String, RolePermissions>,
52}
53
54pub struct OrganizationPlugin {
56 config: OrganizationConfig,
57}
58
59pub(crate) const METADATA_ENABLED: &str = "organization.enabled";
61pub(crate) const METADATA_ROLES: &str = "organization.roles";
64pub(crate) const METADATA_CREATOR_ROLE: &str = "organization.creator_role";
66
67#[async_trait]
68impl<S: better_auth_core::AuthSchema> AuthPlugin<S> for OrganizationPlugin {
69 fn name(&self) -> &'static str {
70 "organization"
71 }
72
73 async fn on_init(
74 &self,
75 ctx: &mut better_auth_core::AuthInitContext<S>,
76 ) -> better_auth_core::AuthResult<()> {
77 ctx.set_metadata(METADATA_ENABLED, serde_json::Value::Bool(true));
78 ctx.set_metadata(
79 METADATA_ROLES,
80 serde_json::to_value(&self.config.roles).unwrap_or_default(),
81 );
82 ctx.set_metadata(
83 METADATA_CREATOR_ROLE,
84 serde_json::Value::String(self.config.creator_role.clone()),
85 );
86 Ok(())
87 }
88
89 fn routes(&self) -> Vec<AuthRoute> {
90 vec![
91 AuthRoute::post("/organization/create", "create_organization"),
93 AuthRoute::post("/organization/update", "update_organization"),
94 AuthRoute::post("/organization/delete", "delete_organization"),
95 AuthRoute::get("/organization/list", "list_organizations"),
96 AuthRoute::get(
97 "/organization/get-full-organization",
98 "get_full_organization",
99 ),
100 AuthRoute::post("/organization/check-slug", "check_slug"),
101 AuthRoute::post("/organization/set-active", "set_active_organization"),
102 AuthRoute::post("/organization/leave", "leave_organization"),
103 AuthRoute::get("/organization/get-active-member", "get_active_member"),
105 AuthRoute::get(
106 "/organization/get-active-member-role",
107 "get_active_member_role",
108 ),
109 AuthRoute::get("/organization/list-members", "list_members"),
110 AuthRoute::post("/organization/remove-member", "remove_member"),
111 AuthRoute::post("/organization/update-member-role", "update_member_role"),
112 AuthRoute::post("/organization/invite-member", "invite_member"),
114 AuthRoute::get("/organization/get-invitation", "get_invitation"),
115 AuthRoute::get("/organization/list-invitations", "list_invitations"),
116 AuthRoute::get(
117 "/organization/list-user-invitations",
118 "list_user_invitations",
119 ),
120 AuthRoute::post("/organization/accept-invitation", "accept_invitation"),
121 AuthRoute::post("/organization/reject-invitation", "reject_invitation"),
122 AuthRoute::post("/organization/cancel-invitation", "cancel_invitation"),
123 AuthRoute::post("/organization/has-permission", "has_permission"),
125 ]
126 }
127
128 async fn on_request(
129 &self,
130 req: &AuthRequest,
131 ctx: &AuthContext<S>,
132 ) -> AuthResult<Option<AuthResponse>> {
133 match (req.method(), req.path()) {
134 (HttpMethod::Post, "/organization/create") => Ok(Some(
136 handlers::org::handle_create_organization(req, ctx, &self.config).await?,
137 )),
138 (HttpMethod::Post, "/organization/update") => Ok(Some(
139 handlers::org::handle_update_organization(req, ctx, &self.config).await?,
140 )),
141 (HttpMethod::Post, "/organization/delete") => Ok(Some(
142 handlers::org::handle_delete_organization(req, ctx, &self.config).await?,
143 )),
144 (HttpMethod::Get, "/organization/list") => Ok(Some(
145 handlers::org::handle_list_organizations(req, ctx).await?,
146 )),
147 (HttpMethod::Get, "/organization/get-full-organization") => Ok(Some(
148 handlers::org::handle_get_full_organization(req, ctx, &self.config).await?,
149 )),
150 (HttpMethod::Post, "/organization/check-slug") => {
151 Ok(Some(handlers::org::handle_check_slug(req, ctx).await?))
152 }
153 (HttpMethod::Post, "/organization/set-active") => Ok(Some(
154 handlers::org::handle_set_active_organization(req, ctx).await?,
155 )),
156 (HttpMethod::Post, "/organization/leave") => Ok(Some(
157 handlers::org::handle_leave_organization(req, ctx, &self.config).await?,
158 )),
159 (HttpMethod::Get, "/organization/get-active-member") => Ok(Some(
161 handlers::member::handle_get_active_member(req, ctx).await?,
162 )),
163 (HttpMethod::Get, "/organization/get-active-member-role") => Ok(Some(
164 handlers::member::handle_get_active_member_role(req, ctx).await?,
165 )),
166 (HttpMethod::Get, "/organization/list-members") => {
167 Ok(Some(handlers::member::handle_list_members(req, ctx).await?))
168 }
169 (HttpMethod::Post, "/organization/remove-member") => Ok(Some(
170 handlers::member::handle_remove_member(req, ctx, &self.config).await?,
171 )),
172 (HttpMethod::Post, "/organization/update-member-role") => Ok(Some(
173 handlers::member::handle_update_member_role(req, ctx, &self.config).await?,
174 )),
175 (HttpMethod::Post, "/organization/invite-member") => Ok(Some(
177 handlers::invitation::handle_invite_member(req, ctx, &self.config).await?,
178 )),
179 (HttpMethod::Get, "/organization/get-invitation") => Ok(Some(
180 handlers::invitation::handle_get_invitation(req, ctx).await?,
181 )),
182 (HttpMethod::Get, "/organization/list-invitations") => Ok(Some(
183 handlers::invitation::handle_list_invitations(req, ctx).await?,
184 )),
185 (HttpMethod::Get, "/organization/list-user-invitations") => Ok(Some(
186 handlers::invitation::handle_list_user_invitations(req, ctx).await?,
187 )),
188 (HttpMethod::Post, "/organization/accept-invitation") => Ok(Some(
189 handlers::invitation::handle_accept_invitation(req, ctx, &self.config).await?,
190 )),
191 (HttpMethod::Post, "/organization/reject-invitation") => Ok(Some(
192 handlers::invitation::handle_reject_invitation(req, ctx).await?,
193 )),
194 (HttpMethod::Post, "/organization/cancel-invitation") => Ok(Some(
195 handlers::invitation::handle_cancel_invitation(req, ctx, &self.config).await?,
196 )),
197 (HttpMethod::Post, "/organization/has-permission") => Ok(Some(
199 handlers::handle_has_permission(req, ctx, &self.config).await?,
200 )),
201 _ => Ok(None),
202 }
203 }
204}