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