alibi_plugins/organization/
endpoint.rs1use super::{OrganizationPlugin, handlers, hooks, types};
2use crate::authentication_helpers::JsonField;
3use crate::endpoint::{definition, error_response, validate_fields, validation};
4use alibi_core::endpoint::{
5 EndpointCall, EndpointDefinition, EndpointInput, EndpointResponse, ServerEndpoint,
6};
7use alibi_core::session::SessionRequest;
8use alibi_core::utils::json::JsValue;
9use alibi_core::{AuthContext, AuthError, AuthResult, AuthSchema, HttpMethod};
10
11pub(super) fn definitions() -> Vec<EndpointDefinition> {
12 let mut endpoints = vec![
13 definition("addMember", "addMember", None, HttpMethod::Post),
14 definition(
15 "removeMember",
16 "removeMember",
17 Some("/organization/remove-member"),
18 HttpMethod::Post,
19 ),
20 definition(
21 "createOrganization",
22 "createOrganization",
23 Some("/organization/create"),
24 HttpMethod::Post,
25 ),
26 definition(
27 "deleteOrganization",
28 "deleteOrganization",
29 Some("/organization/delete"),
30 HttpMethod::Post,
31 ),
32 ];
33 endpoints.extend(super::native::definitions());
34 endpoints
35}
36
37pub(super) fn validate(
38 call: &EndpointCall,
39 organization_fields: &alibi_core::field_policy::SessionFields,
40) -> AuthResult<EndpointInput> {
41 let mut body = call.body().cloned();
42 match call.operation_id() {
43 "createOrganization" => {
44 let _validated =
45 handlers::org_input::create_value(body.as_ref()).map_err(error_response)?;
46 if let Some(JsValue::Object(body)) = &mut body {
47 body.retain(|key, _| {
49 [
50 "name",
51 "slug",
52 "userId",
53 "logo",
54 "metadata",
55 "keepCurrentActiveOrganization",
56 ]
57 .contains(&key.as_str())
58 || organization_fields.0.contains_key(key)
59 });
60 if let Some(value) = body.get("userId") {
61 let value = value.coerce_string().map_err(validation)?;
62 _ = body.insert("userId".into(), JsValue::String(value));
63 }
64 }
65 }
66 "addMember" => {
67 let Some(JsValue::Object(input)) = &mut body else {
68 return Err(validation(format!(
69 "[body] Invalid input: expected object, received {}",
70 crate::authentication_helpers::json_type(body.as_ref())
71 )));
72 };
73 let user_id = input
74 .get("userId")
75 .map_or_else(|| Ok("undefined".into()), JsValue::coerce_string)
76 .map_err(validation)?;
77 _ = input.insert("userId".into(), JsValue::String(user_id));
78 _ = validate_fields(
79 body.as_ref(),
80 "body",
81 &[
82 JsonField::string("userId", true),
83 JsonField::string("organizationId", false),
84 JsonField::string("teamId", false),
85 ],
86 )?;
87 let role = body.as_ref().and_then(|body| body.get("role"));
88 if !role.is_some_and(|role| {
89 role.is_string()
90 || role
91 .as_array()
92 .is_some_and(|values| values.iter().all(JsValue::is_string))
93 }) {
94 return Err(validation("[body.role] Invalid input"));
95 }
96 if let Some(JsValue::Object(body)) = &mut body {
97 body.retain(|key, _| {
98 ["userId", "role", "organizationId", "teamId"].contains(&key.as_str())
99 });
100 }
101 }
102 "removeMember" => {
103 body = Some(validate_fields(
104 body.as_ref(),
105 "body",
106 &[
107 JsonField::string("memberIdOrEmail", true),
108 JsonField::string("organizationId", false),
109 ],
110 )?);
111 }
112 "deleteOrganization" => {
113 body = Some(validate_fields(
114 body.as_ref(),
115 "body",
116 &[JsonField::string("organizationId", true)],
117 )?);
118 }
119 _ => return super::native::validate(call),
120 }
121 Ok(EndpointInput {
122 body,
123 query: call.query().cloned(),
124 })
125}
126
127impl OrganizationPlugin {
128 pub fn add_member_endpoint(
132 body: &types::AddOrganizationMemberRequest,
133 ) -> AuthResult<ServerEndpoint<types::BasicMemberResponse>> {
134 ServerEndpoint::new("organization", "addMember").with_body(body)
135 }
136
137 pub fn remove_member_endpoint(
141 body: &types::RemoveMemberRequest,
142 ) -> AuthResult<
143 ServerEndpoint<types::RemovedMemberResponse<types::OrganizationMemberRemovalSnapshot>>,
144 > {
145 ServerEndpoint::new("organization", "removeMember").with_body(body)
146 }
147
148 pub fn create_endpoint(
152 body: &types::CreateOrganizationRequest,
153 user_id: Option<&str>,
154 ) -> AuthResult<
155 ServerEndpoint<
156 types::CreateOrganizationResponse<
157 types::CreatedOrganizationResponse,
158 types::BasicMemberResponse,
159 >,
160 >,
161 > {
162 let mut value =
163 alibi_core::utils::json::parse_value(&alibi_core::utils::json::to_string(body)?)?;
164 if let Some(user_id) = user_id
165 && let JsValue::Object(body) = &mut value
166 {
167 _ = body.insert("userId".into(), JsValue::String(user_id.into()));
168 }
169 Ok(ServerEndpoint::new("organization", "createOrganization").with_body_value(value))
170 }
171
172 pub fn delete_endpoint(
176 body: &types::DeleteOrganizationRequest,
177 ) -> AuthResult<ServerEndpoint<Option<types::OrganizationResponse>>> {
178 ServerEndpoint::new("organization", "deleteOrganization").with_body(body)
179 }
180
181 pub(super) async fn call_endpoint<S: AuthSchema>(
182 &self,
183 call: &EndpointCall,
184 ctx: &AuthContext<S>,
185 ) -> AuthResult<EndpointResponse> {
186 match call.operation_id() {
187 "addMember" => {
188 let body: types::AddOrganizationMemberRequest = call.body_as()?;
189 let session = if body.user_id.is_empty() {
190 None
191 } else {
192 ctx.require_cached_session(call).await.ok()
193 };
194 EndpointResponse::json(
195 &handlers::member_addition::add_member_with_session(
196 &body,
197 session,
198 call.request(),
199 &self.config,
200 ctx,
201 )
202 .await?,
203 )
204 }
205 "removeMember" => {
206 let body: types::RemoveMemberRequest = call.body_as()?;
207 let (user, session) = ctx.require_cached_session(call).await.map_err(|error| {
208 if matches!(error, AuthError::Unauthenticated) {
209 handlers::extension_common::org_error(401, "UNAUTHORIZED")
210 } else {
211 error
212 }
213 })?;
214 EndpointResponse::json(
215 &handlers::member::remove_member_core(
216 &body,
217 &user,
218 &session,
219 &self.config,
220 ctx,
221 )
222 .await?,
223 )
224 }
225 "deleteOrganization" => {
226 let body: types::DeleteOrganizationRequest = call.body_as()?;
227 if self.config.disable_organization_deletion {
228 return Err(handlers::extension_common::org_error(
229 404,
230 "ORGANIZATION_DELETION_DISABLED",
231 ));
232 }
233 let (user, session) = ctx.require_cached_session(call).await.map_err(|error| {
234 if matches!(error, AuthError::Unauthenticated) {
235 handlers::extension_common::org_error(401, "UNAUTHORIZED")
236 } else {
237 error
238 }
239 })?;
240 EndpointResponse::json(
241 &handlers::org::delete_organization_core(
242 &body,
243 &user,
244 &session,
245 hooks::organization::DeleteInvocation {
246 headers: call.session_headers(),
247 request: call.request(),
248 },
249 &self.config,
250 ctx,
251 )
252 .await?,
253 )
254 }
255 "createOrganization" => {
256 let body: types::CreateOrganizationRequest = call.body_as()?;
257 let session = ctx.require_cached_session(call).await.ok();
258 if session.is_none() && (call.request().is_some() || call.headers().is_some()) {
259 return Err(AuthError::Api {
260 status: 401,
261 code: None,
262 message: String::new(),
263 });
264 }
265 if let Some((user, session)) = session {
266 let response = handlers::org::create_organization_core(
267 &body,
268 &user,
269 call.request(),
270 Some(&session),
271 &self.config,
272 ctx,
273 )
274 .await?;
275 if !body.keep_current_active_organization.unwrap_or(false) {
276 handlers::org::activate_created_organization(
277 &response,
278 &session.token,
279 ctx,
280 )
281 .await?;
282 }
283 EndpointResponse::json(&response)
284 } else {
285 let user_id = call
286 .body()
287 .and_then(|body| body.get("userId"))
288 .and_then(JsValue::as_str)
289 .filter(|id| !id.is_empty())
290 .ok_or(AuthError::Api {
291 status: 401,
292 code: None,
293 message: String::new(),
294 })?;
295 let user =
296 ctx.database
297 .get_user_by_id(user_id)
298 .await?
299 .ok_or(AuthError::Api {
300 status: 401,
301 code: None,
302 message: String::new(),
303 })?;
304 EndpointResponse::json(
305 &handlers::org::create_organization_core(
306 &body,
307 &user,
308 None,
309 None,
310 &self.config,
311 ctx,
312 )
313 .await?,
314 )
315 }
316 }
317 _ => Box::pin(super::native::execute(self, call, ctx)).await,
319 }
320 }
321}