Skip to main content

alibi_plugins/organization/
native.rs

1//! Typed logical organization operations, independent of HTTP request construction.
2use super::{OrganizationPlugin, handlers, types};
3use crate::authentication_helpers::JsonField;
4use crate::endpoint::{definition, error_response, validate_fields, validation};
5use alibi_core::endpoint::{
6    EndpointCall, EndpointDefinition, EndpointInput, EndpointResponse, ServerEndpoint,
7};
8use alibi_core::entity::{AuthOrganization, AuthUser};
9use alibi_core::session::SessionRequest;
10use alibi_core::types::{Team, TeamMember};
11use alibi_core::utils::json::JsValue;
12use alibi_core::wire::InvitationView;
13use alibi_core::{AuthContext, AuthError, AuthResponse, AuthResult, AuthSchema, HttpMethod};
14use std::collections::HashMap;
15
16pub(super) fn definitions() -> Vec<EndpointDefinition> {
17    vec![
18        definition(
19            "listOrganizations",
20            "listOrganizations",
21            Some("/organization/list"),
22            HttpMethod::Get,
23        ),
24        definition(
25            "updateOrganization",
26            "updateOrganization",
27            Some("/organization/update"),
28            HttpMethod::Post,
29        ),
30        definition(
31            "getOrganization",
32            "getOrganization",
33            Some("/organization/get-organization"),
34            HttpMethod::Get,
35        ),
36        definition(
37            "getFullOrganization",
38            "getFullOrganization",
39            Some("/organization/get-full-organization"),
40            HttpMethod::Get,
41        ),
42        definition(
43            "checkOrganizationSlug",
44            "checkOrganizationSlug",
45            Some("/organization/check-slug"),
46            HttpMethod::Post,
47        ),
48        definition(
49            "setActiveOrganization",
50            "setActiveOrganization",
51            Some("/organization/set-active"),
52            HttpMethod::Post,
53        ),
54        definition(
55            "leaveOrganization",
56            "leaveOrganization",
57            Some("/organization/leave"),
58            HttpMethod::Post,
59        ),
60        definition(
61            "createInvitation",
62            "createOrganizationInvitation",
63            Some("/organization/invite-member"),
64            HttpMethod::Post,
65        ),
66        definition(
67            "inviteMember",
68            "inviteMember",
69            Some("/organization/invite-member"),
70            HttpMethod::Post,
71        ),
72        definition(
73            "getInvitation",
74            "getInvitation",
75            Some("/organization/get-invitation"),
76            HttpMethod::Get,
77        ),
78        definition(
79            "listInvitations",
80            "listInvitations",
81            Some("/organization/list-invitations"),
82            HttpMethod::Get,
83        ),
84        definition(
85            "listUserInvitations",
86            "listUserInvitations",
87            Some("/organization/list-user-invitations"),
88            HttpMethod::Get,
89        ),
90        definition(
91            "acceptInvitation",
92            "acceptInvitation",
93            Some("/organization/accept-invitation"),
94            HttpMethod::Post,
95        ),
96        definition(
97            "rejectInvitation",
98            "rejectInvitation",
99            Some("/organization/reject-invitation"),
100            HttpMethod::Post,
101        ),
102        definition(
103            "cancelInvitation",
104            "cancelInvitation",
105            Some("/organization/cancel-invitation"),
106            HttpMethod::Post,
107        ),
108        definition(
109            "getActiveMember",
110            "getActiveMember",
111            Some("/organization/get-active-member"),
112            HttpMethod::Get,
113        ),
114        definition(
115            "getActiveMemberRole",
116            "getActiveMemberRole",
117            Some("/organization/get-active-member-role"),
118            HttpMethod::Get,
119        ),
120        definition(
121            "listMembers",
122            "listMembers",
123            Some("/organization/list-members"),
124            HttpMethod::Get,
125        ),
126        definition(
127            "updateMemberRole",
128            "updateMemberRole",
129            Some("/organization/update-member-role"),
130            HttpMethod::Post,
131        ),
132        definition(
133            "hasPermission",
134            "hasPermission",
135            Some("/organization/has-permission"),
136            HttpMethod::Post,
137        ),
138        definition(
139            "createTeam",
140            "createTeam",
141            Some("/organization/create-team"),
142            HttpMethod::Post,
143        ),
144        definition(
145            "updateTeam",
146            "updateTeam",
147            Some("/organization/update-team"),
148            HttpMethod::Post,
149        ),
150        definition(
151            "removeTeam",
152            "removeTeam",
153            Some("/organization/remove-team"),
154            HttpMethod::Post,
155        ),
156        definition(
157            "setActiveTeam",
158            "setActiveTeam",
159            Some("/organization/set-active-team"),
160            HttpMethod::Post,
161        ),
162        definition(
163            "addTeamMember",
164            "addTeamMember",
165            Some("/organization/add-team-member"),
166            HttpMethod::Post,
167        ),
168        definition(
169            "removeTeamMember",
170            "removeTeamMember",
171            Some("/organization/remove-team-member"),
172            HttpMethod::Post,
173        ),
174        definition(
175            "listOrganizationTeams",
176            "listOrganizationTeams",
177            Some("/organization/list-teams"),
178            HttpMethod::Get,
179        ),
180        definition(
181            "listTeams",
182            "listTeams",
183            Some("/organization/list-teams"),
184            HttpMethod::Get,
185        ),
186        definition(
187            "listUserTeams",
188            "listUserTeams",
189            Some("/organization/list-user-teams"),
190            HttpMethod::Get,
191        ),
192        definition(
193            "listTeamMembers",
194            "listTeamMembers",
195            Some("/organization/list-team-members"),
196            HttpMethod::Get,
197        ),
198        definition(
199            "createOrgRole",
200            "createOrgRole",
201            Some("/organization/create-role"),
202            HttpMethod::Post,
203        ),
204        definition(
205            "createOrganizationRole",
206            "createOrganizationRole",
207            Some("/organization/create-role"),
208            HttpMethod::Post,
209        ),
210        definition(
211            "updateOrgRole",
212            "updateOrgRole",
213            Some("/organization/update-role"),
214            HttpMethod::Post,
215        ),
216        definition(
217            "updateOrganizationRole",
218            "updateOrganizationRole",
219            Some("/organization/update-role"),
220            HttpMethod::Post,
221        ),
222        definition(
223            "deleteOrgRole",
224            "deleteOrgRole",
225            Some("/organization/delete-role"),
226            HttpMethod::Post,
227        ),
228        definition(
229            "deleteOrganizationRole",
230            "deleteOrganizationRole",
231            Some("/organization/delete-role"),
232            HttpMethod::Post,
233        ),
234        definition(
235            "getOrgRole",
236            "getOrgRole",
237            Some("/organization/get-role"),
238            HttpMethod::Get,
239        ),
240        definition(
241            "getOrganizationRole",
242            "getOrganizationRole",
243            Some("/organization/get-role"),
244            HttpMethod::Get,
245        ),
246        definition(
247            "listOrgRoles",
248            "listOrgRoles",
249            Some("/organization/list-roles"),
250            HttpMethod::Get,
251        ),
252        definition(
253            "listOrganizationRoles",
254            "listOrganizationRoles",
255            Some("/organization/list-roles"),
256            HttpMethod::Get,
257        ),
258    ]
259}
260
261pub(super) fn validate(call: &EndpointCall) -> AuthResult<EndpointInput> {
262    // Native calls validate logical values after before hooks, without media-type or URL decoding.
263    match call.operation_id() {
264        "updateOrganization" => {
265            _ = handlers::org_input::update_value(call.body()).map_err(error_response)?;
266        }
267        "getOrganization" => {
268            let _: types::GetOrganizationQuery = call
269                .query_as()
270                .map_err(|error| validation(error.to_string()))?;
271        }
272        "getFullOrganization" => {
273            let _: types::GetFullOrganizationQuery = call
274                .query_as()
275                .map_err(|error| validation(error.to_string()))?;
276        }
277        "checkOrganizationSlug" => {
278            let _: types::CheckSlugRequest = call
279                .body_as()
280                .map_err(|error| validation(error.to_string()))?;
281        }
282        "setActiveOrganization" => {
283            _ = handlers::org_input::set_active_value(call.body()).map_err(error_response)?;
284        }
285        "leaveOrganization" => {
286            let _: types::LeaveOrganizationRequest = call
287                .body_as()
288                .map_err(|error| validation(error.to_string()))?;
289        }
290        "inviteMember" | "createOrganizationInvitation" => {
291            _ = handlers::org_input::invitation_create_value(call.body())
292                .map_err(error_response)?;
293            if call.operation_id() == "createOrganizationInvitation" && call.headers().is_none() {
294                return Err(validation("Headers is required"));
295            }
296        }
297        "getInvitation" => {
298            let _: types::GetInvitationQuery = call
299                .query_as()
300                .map_err(|error| validation(error.to_string()))?;
301        }
302        "listInvitations" => {
303            let _: types::ListInvitationsQuery = call
304                .query_as()
305                .map_err(|error| validation(error.to_string()))?;
306        }
307        "listUserInvitations" => {
308            let _: types::ListUserInvitationsQuery = call
309                .query_as()
310                .map_err(|error| validation(error.to_string()))?;
311        }
312        "acceptInvitation" => {
313            let _: types::AcceptInvitationRequest = call
314                .body_as()
315                .map_err(|error| validation(error.to_string()))?;
316        }
317        "rejectInvitation" => {
318            let _: types::RejectInvitationRequest = call
319                .body_as()
320                .map_err(|error| validation(error.to_string()))?;
321        }
322        "cancelInvitation" => {
323            let _: types::CancelInvitationRequest = call
324                .body_as()
325                .map_err(|error| validation(error.to_string()))?;
326        }
327        "getActiveMemberRole" => {
328            let _: types::GetActiveMemberRoleQuery = call
329                .query_as()
330                .map_err(|error| validation(error.to_string()))?;
331        }
332        "listMembers" => {
333            let _: types::ListMembersQuery = call
334                .query_as()
335                .map_err(|error| validation(error.to_string()))?;
336        }
337        "updateMemberRole" => {
338            _ = handlers::org_input::member_role_update_value(call.body())
339                .map_err(error_response)?;
340        }
341        "hasPermission" => {
342            let _: types::HasPermissionRequest = call
343                .body_as()
344                .map_err(|error| validation(error.to_string()))?;
345        }
346        "createTeam" => {
347            let _: types::CreateTeamRequest = call
348                .body_as()
349                .map_err(|error| validation(error.to_string()))?;
350        }
351        "updateTeam" => {
352            let _: types::UpdateTeamRequest = call
353                .body_as()
354                .map_err(|error| validation(error.to_string()))?;
355            if call.headers().is_none() {
356                return Err(validation("Headers is required"));
357            }
358        }
359        "removeTeam" => {
360            let _: types::RemoveTeamRequest = call
361                .body_as()
362                .map_err(|error| validation(error.to_string()))?;
363        }
364        "setActiveTeam" => {
365            let _: types::SetActiveTeamRequest = call
366                .body_as()
367                .map_err(|error| validation(error.to_string()))?;
368            if call.headers().is_none() {
369                return Err(validation("Headers is required"));
370            }
371        }
372        "addTeamMember" => {
373            let _: types::TeamMemberRequest = call
374                .body_as()
375                .map_err(|error| validation(error.to_string()))?;
376            if call.headers().is_none() {
377                return Err(validation("Headers is required"));
378            }
379        }
380        "removeTeamMember" => {
381            let _: types::TeamMemberRequest = call
382                .body_as()
383                .map_err(|error| validation(error.to_string()))?;
384            if call.headers().is_none() {
385                return Err(validation("Headers is required"));
386            }
387        }
388        "listOrganizationTeams" => {
389            if call.has_query() {
390                let _: types::TeamQuery = call
391                    .query_as()
392                    .map_err(|error| validation(error.to_string()))?;
393            }
394            if call.headers().is_none() {
395                return Err(validation("Headers is required"));
396            }
397        }
398        "listTeams" => {
399            let _: types::TeamQuery = call
400                .query_as()
401                .map_err(|error| validation(error.to_string()))?;
402        }
403        "listUserTeams" => {
404            let query = if call.has_query() {
405                Some(validate_fields(
406                    call.query(),
407                    "query",
408                    &[
409                        JsonField::string("organizationId", false),
410                        JsonField::string("userId", false),
411                    ],
412                )?)
413            } else {
414                None
415            };
416            if call.headers().is_none() {
417                return Err(validation("Headers is required"));
418            }
419            return Ok(EndpointInput {
420                body: call.body().cloned(),
421                query,
422            });
423        }
424        "listTeamMembers" => {
425            let query = if call.has_query() {
426                Some(validate_fields(
427                    call.query(),
428                    "query",
429                    &[JsonField::string("teamId", false)],
430                )?)
431            } else {
432                None
433            };
434            if call.headers().is_none() {
435                return Err(validation("Headers is required"));
436            }
437            return Ok(EndpointInput {
438                body: call.body().cloned(),
439                query,
440            });
441        }
442        "createOrganizationRole" | "createOrgRole" => {
443            let _: types::CreateRoleRequest = call
444                .body_as()
445                .map_err(|error| validation(error.to_string()))?;
446            if call.operation_id() == "createOrgRole" && call.headers().is_none() {
447                return Err(validation("Headers is required"));
448            }
449        }
450        "updateOrganizationRole" | "updateOrgRole" => {
451            let _: types::UpdateRoleRequest = call
452                .body_as()
453                .map_err(|error| validation(error.to_string()))?;
454            if call.operation_id() == "updateOrgRole" && call.headers().is_none() {
455                return Err(validation("Headers is required"));
456            }
457        }
458        "deleteOrganizationRole" | "deleteOrgRole" => {
459            let _: types::RoleQuery = call
460                .body_as()
461                .map_err(|error| validation(error.to_string()))?;
462            if call.operation_id() == "deleteOrgRole" && call.headers().is_none() {
463                return Err(validation("Headers is required"));
464            }
465        }
466        "getOrgRole" => {
467            if call.has_query() {
468                let _: types::RoleQuery = call
469                    .query_as()
470                    .map_err(|error| validation(error.to_string()))?;
471            }
472            if call.headers().is_none() {
473                return Err(validation("Headers is required"));
474            }
475        }
476        "getOrganizationRole" => {
477            let _: types::RoleQuery = call
478                .query_as()
479                .map_err(|error| validation(error.to_string()))?;
480        }
481        "listOrgRoles" => {
482            let query = if call.has_query() {
483                Some(validate_fields(
484                    call.query(),
485                    "query",
486                    &[JsonField::string("organizationId", false)],
487                )?)
488            } else {
489                None
490            };
491            if call.headers().is_none() {
492                return Err(validation("Headers is required"));
493            }
494            return Ok(EndpointInput {
495                body: call.body().cloned(),
496                query,
497            });
498        }
499        "listOrganizationRoles" => {
500            let _: types::RoleQuery = call
501                .query_as()
502                .map_err(|error| validation(error.to_string()))?;
503        }
504        _ => {}
505    }
506    Ok(EndpointInput {
507        body: call.body().cloned(),
508        query: call.query().cloned(),
509    })
510}
511
512impl OrganizationPlugin {
513    /// Construct a typed native call through installed dispatch hooks.
514    /// # Errors
515    /// Returns a serialization error for input that cannot be represented.
516    #[must_use]
517    pub fn list_organizations_endpoint() -> ServerEndpoint<Vec<types::OrganizationResponse>> {
518        ServerEndpoint::new("organization", "listOrganizations")
519    }
520    /// Construct a typed native call through installed dispatch hooks.
521    /// # Errors
522    /// Returns a serialization error for input that cannot be represented.
523    pub fn update_endpoint(
524        input: &types::UpdateOrganizationRequest,
525    ) -> AuthResult<ServerEndpoint<Option<types::CreatedOrganizationResponse>>> {
526        ServerEndpoint::new("organization", "updateOrganization").with_body(input)
527    }
528    /// Construct a typed native call through installed dispatch hooks.
529    /// # Errors
530    /// Returns a serialization error for input that cannot be represented.
531    pub fn get_organization_endpoint(
532        input: &types::GetOrganizationQuery,
533    ) -> AuthResult<ServerEndpoint<Option<types::OrganizationResponse>>> {
534        ServerEndpoint::new("organization", "getOrganization").with_query(input)
535    }
536    /// Construct a typed native call through installed dispatch hooks.
537    /// # Errors
538    /// Returns a serialization error for input that cannot be represented.
539    pub fn get_full_organization_endpoint(
540        input: &types::GetFullOrganizationQuery,
541    ) -> AuthResult<
542        ServerEndpoint<
543            Option<types::FullOrganizationResponse<types::OrganizationResponse, InvitationView>>,
544        >,
545    > {
546        ServerEndpoint::new("organization", "getFullOrganization").with_query(input)
547    }
548    /// Construct a typed native call through installed dispatch hooks.
549    /// # Errors
550    /// Returns a serialization error for input that cannot be represented.
551    pub fn check_slug_endpoint(
552        input: &types::CheckSlugRequest,
553    ) -> AuthResult<ServerEndpoint<types::CheckSlugResponse>> {
554        ServerEndpoint::new("organization", "checkOrganizationSlug").with_body(input)
555    }
556    /// Construct a typed native call through installed dispatch hooks.
557    /// # Errors
558    /// Returns a serialization error for input that cannot be represented.
559    pub fn set_active_endpoint(
560        input: &types::SetActiveOrganizationRequest,
561    ) -> AuthResult<ServerEndpoint<Option<types::OrganizationResponse>>> {
562        ServerEndpoint::new("organization", "setActiveOrganization").with_body(input)
563    }
564    /// Construct a typed native call through installed dispatch hooks.
565    /// # Errors
566    /// Returns a serialization error for input that cannot be represented.
567    pub fn leave_endpoint(
568        input: &types::LeaveOrganizationRequest,
569    ) -> AuthResult<ServerEndpoint<types::SuccessResponse>> {
570        ServerEndpoint::new("organization", "leaveOrganization").with_body(input)
571    }
572    /// Construct a typed native call through installed dispatch hooks.
573    /// # Errors
574    /// Returns a serialization error for input that cannot be represented.
575    pub fn create_invitation_endpoint(
576        input: &types::InviteMemberRequest,
577    ) -> AuthResult<ServerEndpoint<InvitationView>> {
578        ServerEndpoint::new("organization", "createInvitation").with_body(input)
579    }
580    /// Construct an invitation through the canonical server API.
581    /// # Errors
582    /// Returns a serialization error for input that cannot be represented.
583    pub fn invite_member_endpoint(
584        input: &types::InviteMemberRequest,
585    ) -> AuthResult<ServerEndpoint<InvitationView>> {
586        Self::create_invitation_endpoint(input)
587    }
588    /// Construct a typed native call through installed dispatch hooks.
589    /// # Errors
590    /// Returns a serialization error for input that cannot be represented.
591    pub fn get_invitation_endpoint(
592        input: &types::GetInvitationQuery,
593    ) -> AuthResult<ServerEndpoint<types::GetInvitationResponse<InvitationView>>> {
594        ServerEndpoint::new("organization", "getInvitation").with_query(input)
595    }
596    /// Construct a typed native call through installed dispatch hooks.
597    /// # Errors
598    /// Returns a serialization error for input that cannot be represented.
599    pub fn list_invitations_endpoint(
600        input: &types::ListInvitationsQuery,
601    ) -> AuthResult<ServerEndpoint<Vec<InvitationView>>> {
602        ServerEndpoint::new("organization", "listInvitations").with_query(input)
603    }
604    /// Construct a typed native call through installed dispatch hooks.
605    /// # Errors
606    /// Returns a serialization error for input that cannot be represented.
607    pub fn list_user_invitations_endpoint(
608        input: &types::ListUserInvitationsQuery,
609    ) -> AuthResult<ServerEndpoint<Vec<types::UserInvitationResponse<InvitationView>>>> {
610        ServerEndpoint::new("organization", "listUserInvitations").with_query(input)
611    }
612    /// Construct a typed native call through installed dispatch hooks.
613    /// # Errors
614    /// Returns a serialization error for input that cannot be represented.
615    pub fn accept_invitation_endpoint(
616        input: &types::AcceptInvitationRequest,
617    ) -> AuthResult<
618        ServerEndpoint<types::AcceptInvitationResponse<InvitationView, types::BasicMemberResponse>>,
619    > {
620        ServerEndpoint::new("organization", "acceptInvitation").with_body(input)
621    }
622    /// Construct a typed native call through installed dispatch hooks.
623    /// # Errors
624    /// Returns a serialization error for input that cannot be represented.
625    pub fn reject_invitation_endpoint(
626        input: &types::RejectInvitationRequest,
627    ) -> AuthResult<
628        ServerEndpoint<
629            types::AcceptInvitationResponse<InvitationView, Option<types::BasicMemberResponse>>,
630        >,
631    > {
632        ServerEndpoint::new("organization", "rejectInvitation").with_body(input)
633    }
634    /// Construct a typed native call through installed dispatch hooks.
635    /// # Errors
636    /// Returns a serialization error for input that cannot be represented.
637    pub fn cancel_invitation_endpoint(
638        input: &types::CancelInvitationRequest,
639    ) -> AuthResult<ServerEndpoint<InvitationView>> {
640        ServerEndpoint::new("organization", "cancelInvitation").with_body(input)
641    }
642    /// Construct a typed native call through installed dispatch hooks.
643    /// # Errors
644    /// Returns a serialization error for input that cannot be represented.
645    #[must_use]
646    pub fn get_active_member_endpoint() -> ServerEndpoint<types::MemberResponse> {
647        ServerEndpoint::new("organization", "getActiveMember")
648    }
649    /// Construct a typed native call through installed dispatch hooks.
650    /// # Errors
651    /// Returns a serialization error for input that cannot be represented.
652    pub fn get_active_member_role_endpoint(
653        input: &types::GetActiveMemberRoleQuery,
654    ) -> AuthResult<ServerEndpoint<types::GetActiveMemberRoleResponse>> {
655        ServerEndpoint::new("organization", "getActiveMemberRole").with_query(input)
656    }
657    /// Construct a typed native call through installed dispatch hooks.
658    /// # Errors
659    /// Returns a serialization error for input that cannot be represented.
660    pub fn list_members_endpoint(
661        input: &types::ListMembersQuery,
662    ) -> AuthResult<ServerEndpoint<types::ListMembersResponse>> {
663        ServerEndpoint::new("organization", "listMembers").with_query(input)
664    }
665    /// Construct a typed native call through installed dispatch hooks.
666    /// # Errors
667    /// Returns a serialization error for input that cannot be represented.
668    pub fn update_member_role_endpoint(
669        input: &types::UpdateMemberRoleRequest,
670    ) -> AuthResult<ServerEndpoint<types::BasicMemberResponse>> {
671        ServerEndpoint::new("organization", "updateMemberRole").with_body(input)
672    }
673    /// Construct a typed native call through installed dispatch hooks.
674    /// # Errors
675    /// Returns a serialization error for input that cannot be represented.
676    pub fn has_permission_endpoint(
677        input: &types::HasPermissionRequest,
678    ) -> AuthResult<ServerEndpoint<types::HasPermissionResponse>> {
679        ServerEndpoint::new("organization", "hasPermission").with_body(input)
680    }
681    /// Construct a typed native call through installed dispatch hooks.
682    /// # Errors
683    /// Returns a serialization error for input that cannot be represented.
684    pub fn create_team_endpoint(
685        input: &types::CreateTeamRequest,
686    ) -> AuthResult<ServerEndpoint<Team>> {
687        ServerEndpoint::new("organization", "createTeam").with_body(input)
688    }
689    /// Construct a typed native call through installed dispatch hooks.
690    /// # Errors
691    /// Returns a serialization error for input that cannot be represented.
692    pub fn update_team_endpoint(
693        input: &types::UpdateTeamRequest,
694    ) -> AuthResult<ServerEndpoint<Team>> {
695        ServerEndpoint::new("organization", "updateTeam").with_body(input)
696    }
697    /// Construct a typed native call through installed dispatch hooks.
698    /// # Errors
699    /// Returns a serialization error for input that cannot be represented.
700    pub fn remove_team_endpoint(
701        input: &types::RemoveTeamRequest,
702    ) -> AuthResult<ServerEndpoint<types::MessageResponse>> {
703        ServerEndpoint::new("organization", "removeTeam").with_body(input)
704    }
705    /// Construct a typed native call through installed dispatch hooks.
706    /// # Errors
707    /// Returns a serialization error for input that cannot be represented.
708    pub fn set_active_team_endpoint(
709        input: &types::SetActiveTeamRequest,
710    ) -> AuthResult<ServerEndpoint<Option<Team>>> {
711        ServerEndpoint::new("organization", "setActiveTeam").with_body(input)
712    }
713    /// Construct a typed native call through installed dispatch hooks.
714    /// # Errors
715    /// Returns a serialization error for input that cannot be represented.
716    pub fn add_team_member_endpoint(
717        input: &types::TeamMemberRequest,
718    ) -> AuthResult<ServerEndpoint<TeamMember>> {
719        ServerEndpoint::new("organization", "addTeamMember").with_body(input)
720    }
721    /// Construct a typed native call through installed dispatch hooks.
722    /// # Errors
723    /// Returns a serialization error for input that cannot be represented.
724    pub fn remove_team_member_endpoint(
725        input: &types::TeamMemberRequest,
726    ) -> AuthResult<ServerEndpoint<types::MessageResponse>> {
727        ServerEndpoint::new("organization", "removeTeamMember").with_body(input)
728    }
729    /// Construct a typed native call through installed dispatch hooks.
730    /// # Errors
731    /// Returns a serialization error for input that cannot be represented.
732    pub fn list_organization_teams_endpoint(
733        input: &types::TeamQuery,
734    ) -> AuthResult<ServerEndpoint<Vec<Team>>> {
735        ServerEndpoint::new("organization", "listOrganizationTeams").with_query(input)
736    }
737    /// Construct a team listing through the canonical server API.
738    /// # Errors
739    /// Returns a serialization error for input that cannot be represented.
740    pub fn list_teams_endpoint(input: &types::TeamQuery) -> AuthResult<ServerEndpoint<Vec<Team>>> {
741        Self::list_organization_teams_endpoint(input)
742    }
743    /// Construct a typed native call through installed dispatch hooks.
744    /// # Errors
745    /// Returns a serialization error for input that cannot be represented.
746    pub fn list_user_teams_endpoint(
747        input: &types::TeamQuery,
748    ) -> AuthResult<ServerEndpoint<Vec<Team>>> {
749        ServerEndpoint::new("organization", "listUserTeams").with_query(input)
750    }
751    /// Construct a typed native call through installed dispatch hooks.
752    /// # Errors
753    /// Returns a serialization error for input that cannot be represented.
754    pub fn list_team_members_endpoint(
755        input: &types::TeamQuery,
756    ) -> AuthResult<ServerEndpoint<Vec<TeamMember>>> {
757        ServerEndpoint::new("organization", "listTeamMembers").with_query(input)
758    }
759    /// Construct a typed native call through installed dispatch hooks.
760    /// # Errors
761    /// Returns a serialization error for input that cannot be represented.
762    pub fn create_org_role_endpoint(
763        input: &types::CreateRoleRequest,
764    ) -> AuthResult<ServerEndpoint<types::CreateRoleResponse>> {
765        ServerEndpoint::new("organization", "createOrgRole").with_body(input)
766    }
767    /// Construct role creation through the canonical server API.
768    /// # Errors
769    /// Returns a serialization error for input that cannot be represented.
770    pub fn create_role_endpoint(
771        input: &types::CreateRoleRequest,
772    ) -> AuthResult<ServerEndpoint<types::CreateRoleResponse>> {
773        Self::create_org_role_endpoint(input)
774    }
775    /// Construct a typed native call through installed dispatch hooks.
776    /// # Errors
777    /// Returns a serialization error for input that cannot be represented.
778    pub fn update_org_role_endpoint(
779        input: &types::UpdateRoleRequest,
780    ) -> AuthResult<ServerEndpoint<types::UpdateRoleResponse>> {
781        ServerEndpoint::new("organization", "updateOrgRole").with_body(input)
782    }
783    /// Construct a role update through the canonical server API.
784    /// # Errors
785    /// Returns a serialization error for input that cannot be represented.
786    pub fn update_role_endpoint(
787        input: &types::UpdateRoleRequest,
788    ) -> AuthResult<ServerEndpoint<types::UpdateRoleResponse>> {
789        Self::update_org_role_endpoint(input)
790    }
791    /// Construct a typed native call through installed dispatch hooks.
792    /// # Errors
793    /// Returns a serialization error for input that cannot be represented.
794    pub fn delete_org_role_endpoint(
795        input: &types::RoleQuery,
796    ) -> AuthResult<ServerEndpoint<types::SuccessResponse>> {
797        ServerEndpoint::new("organization", "deleteOrgRole").with_body(input)
798    }
799    /// Construct role deletion through the canonical server API.
800    /// # Errors
801    /// Returns a serialization error for input that cannot be represented.
802    pub fn delete_role_endpoint(
803        input: &types::RoleQuery,
804    ) -> AuthResult<ServerEndpoint<types::SuccessResponse>> {
805        Self::delete_org_role_endpoint(input)
806    }
807    /// Construct a typed native call through installed dispatch hooks.
808    /// # Errors
809    /// Returns a serialization error for input that cannot be represented.
810    pub fn get_org_role_endpoint(
811        input: &types::RoleQuery,
812    ) -> AuthResult<ServerEndpoint<types::RoleResponse>> {
813        ServerEndpoint::new("organization", "getOrgRole").with_query(input)
814    }
815    /// Construct a role read through the canonical server API.
816    /// # Errors
817    /// Returns a serialization error for input that cannot be represented.
818    pub fn get_role_endpoint(
819        input: &types::RoleQuery,
820    ) -> AuthResult<ServerEndpoint<types::RoleResponse>> {
821        Self::get_org_role_endpoint(input)
822    }
823    /// Construct a typed native call through installed dispatch hooks.
824    /// # Errors
825    /// Returns a serialization error for input that cannot be represented.
826    pub fn list_org_roles_endpoint(
827        input: &types::RoleQuery,
828    ) -> AuthResult<ServerEndpoint<Vec<types::RoleResponse>>> {
829        ServerEndpoint::new("organization", "listOrgRoles").with_query(input)
830    }
831    /// Construct a role listing through the canonical server API.
832    /// # Errors
833    /// Returns a serialization error for input that cannot be represented.
834    pub fn list_roles_endpoint(
835        input: &types::RoleQuery,
836    ) -> AuthResult<ServerEndpoint<Vec<types::RoleResponse>>> {
837        Self::list_org_roles_endpoint(input)
838    }
839}
840
841fn response(output: AuthResponse) -> AuthResult<EndpointResponse> {
842    let value = if output.body.is_empty() {
843        JsValue::Null
844    } else {
845        alibi_core::utils::json::from_slice::<JsValue>(&output.body)?
846    };
847    let mut response = if output.status >= 400 {
848        EndpointResponse::error(error_response(output.clone())).with_error_body(value)
849    } else {
850        EndpointResponse::value(value)
851    };
852    response.set_status(Some(output.status));
853    response.merge_headers(output.headers);
854    Ok(response)
855}
856
857fn query_pairs(call: &EndpointCall) -> AuthResult<HashMap<String, String>> {
858    let Some(fields) = call.query().and_then(JsValue::as_object) else {
859        return Ok(HashMap::new());
860    };
861    fields
862        .iter()
863        .map(|(key, value)| {
864            value
865                .coerce_string()
866                .map(|value| (key.clone(), value))
867                .map_err(validation)
868        })
869        .collect()
870}
871
872pub(super) async fn execute<S: AuthSchema>(
873    plugin: &OrganizationPlugin,
874    call: &EndpointCall,
875    ctx: &AuthContext<S>,
876) -> AuthResult<EndpointResponse> {
877    let config = &plugin.config;
878    // The reference request-only guard permits trusted calls that carry
879    // neither HTTP request metadata nor logical headers.
880    if call.operation_id() == "checkOrganizationSlug"
881        && call.request().is_none()
882        && call.headers().is_none()
883    {
884        return EndpointResponse::json(
885            &handlers::org::check_slug_core(&call.body_as()?, ctx).await?,
886        );
887    }
888    if call.operation_id() == "createTeam" && call.request().is_none() && call.headers().is_none() {
889        let body: types::CreateTeamRequest = call.body_as()?;
890        let organization_id = body
891            .organization_id
892            .filter(|id| !id.is_empty())
893            .ok_or_else(|| handlers::extension_common::org_error(400, "NO_ACTIVE_ORGANIZATION"))?;
894        return EndpointResponse::json(
895            &handlers::team::create_team_core(
896                alibi_core::types::CreateTeam {
897                    name: body.name,
898                    organization_id,
899                    updated_at: Some(chrono::Utc::now()),
900                },
901                None,
902                None,
903                ctx,
904                config,
905            )
906            .await?,
907        );
908    }
909    if call.operation_id() == "removeTeam" && call.request().is_none() && call.headers().is_none() {
910        let body: types::RemoveTeamRequest = call.body_as()?;
911        let organization_id = body
912            .organization_id
913            .filter(|id| !id.is_empty())
914            .ok_or_else(|| handlers::extension_common::org_error(400, "NO_ACTIVE_ORGANIZATION"))?;
915        handlers::team::remove_team_core(&organization_id, &body.team_id, None, ctx, config)
916            .await?;
917        return EndpointResponse::json(&types::MessageResponse {
918            message: "Team removed successfully.".into(),
919        });
920    }
921    if call.operation_id() == "listUserInvitations" {
922        let query: types::ListUserInvitationsQuery = call.query_as()?;
923        if let Some(email) = query.email.as_deref().filter(|email| !email.is_empty()) {
924            if call.request().is_some() {
925                return Err(AuthError::bad_request(
926                    "User email cannot be passed for client side API calls.",
927                ));
928            }
929            return EndpointResponse::json(
930                &handlers::invitation::list_user_invitations_for_email_core(email, ctx).await?,
931            );
932        }
933    }
934    let (user, session) = ctx.require_cached_session(call).await.map_err(|error| {
935        if matches!(
936            error,
937            AuthError::Unauthenticated | AuthError::SessionNotFound
938        ) {
939            handlers::extension_common::org_error(401, "UNAUTHORIZED")
940        } else {
941            error
942        }
943    })?;
944    match call.operation_id() {
945        "listOrganizations" => {
946            EndpointResponse::json(&handlers::org::list_organizations_core(&user, ctx).await?)
947        }
948        "updateOrganization" => {
949            let (body, metadata) =
950                handlers::org_input::update_value(call.body()).map_err(error_response)?;
951            EndpointResponse::json(
952                &handlers::org::update_organization_core(
953                    &body, metadata, &user, &session, config, ctx,
954                )
955                .await?,
956            )
957        }
958        "getOrganization" => {
959            let query: types::GetOrganizationQuery = call.query_as()?;
960            let organization =
961                if let Some(slug) = query.organization_slug.as_deref().filter(|s| !s.is_empty()) {
962                    ctx.database.get_organization_by_slug(slug).await?
963                } else if let Some(id) = query
964                    .organization_id
965                    .as_deref()
966                    .filter(|s| !s.is_empty())
967                    .or(session.active_organization_id.as_deref())
968                {
969                    ctx.database.get_organization_by_id(id).await?
970                } else {
971                    return EndpointResponse::json(&Option::<types::OrganizationResponse>::None);
972                }
973                .ok_or_else(|| AuthError::bad_request("Organization not found"))?;
974            if ctx
975                .database
976                .get_member(&organization.id(), &user.id())
977                .await?
978                .is_none()
979            {
980                _ = ctx
981                    .database
982                    .update_session_active_organization_record(&session.token, None)
983                    .await?;
984                return Err(AuthError::forbidden(
985                    "User is not a member of the organization",
986                ));
987            }
988            EndpointResponse::json(&types::OrganizationResponse::from_stored_organization(
989                &organization,
990            )?)
991        }
992        "getFullOrganization" => EndpointResponse::json(
993            &handlers::org::get_full_organization_core(
994                &call.query_as()?,
995                &user,
996                &session,
997                config,
998                ctx,
999            )
1000            .await
1001            .map_err(AuthError::from)?,
1002        ),
1003        "checkOrganizationSlug" => {
1004            EndpointResponse::json(&handlers::org::check_slug_core(&call.body_as()?, ctx).await?)
1005        }
1006        "setActiveOrganization" => {
1007            let body =
1008                handlers::org_input::set_active_value(call.body()).map_err(error_response)?;
1009            let organization =
1010                handlers::org::set_active_organization_core(&body, &user, &session, ctx).await?;
1011            if organization.is_some()
1012                || session
1013                    .active_organization_id
1014                    .as_deref()
1015                    .is_some_and(|id| !id.is_empty())
1016            {
1017                use alibi_core::utils::cookie_utils::{
1018                    create_session_cookie_with_max_age, create_session_like_cookie,
1019                    related_cookie_name, sign_cookie_value, verify_cookie_value,
1020                };
1021                let preference = related_cookie_name(&ctx.config, "dont_remember");
1022                let dont_remember = call.session_headers().get("cookie").is_some_and(|header| {
1023                    cookie::Cookie::split_parse(header)
1024                        .flatten()
1025                        .find(|cookie| cookie.name() == preference)
1026                        .and_then(|cookie| {
1027                            verify_cookie_value(cookie.value(), ctx.config.current_secret())
1028                        })
1029                        .is_some_and(|value| !value.is_empty())
1030                });
1031                call.queue_response_header(
1032                    "set-cookie",
1033                    create_session_cookie_with_max_age(
1034                        Some(&session.token),
1035                        (!dont_remember).then(|| ctx.config.session.expires_in.num_seconds()),
1036                        &ctx.config,
1037                    )?,
1038                );
1039                if dont_remember {
1040                    call.queue_response_header(
1041                        "set-cookie",
1042                        create_session_like_cookie(
1043                            &preference,
1044                            &sign_cookie_value("true", ctx.config.current_secret()),
1045                            None,
1046                            &ctx.config,
1047                        )?,
1048                    );
1049                }
1050            }
1051            EndpointResponse::json(&organization)
1052        }
1053        "leaveOrganization" => EndpointResponse::json(
1054            &handlers::org::leave_organization_core(&call.body_as()?, &user, &session, config, ctx)
1055                .await?,
1056        ),
1057        "inviteMember" | "createOrganizationInvitation" => {
1058            let body = handlers::org_input::invitation_create_value(call.body())
1059                .map_err(error_response)?;
1060            EndpointResponse::json(
1061                &handlers::invitation::invite_member_core(&body, &user, &session, config, ctx)
1062                    .await?,
1063            )
1064        }
1065        "getInvitation" => EndpointResponse::json(
1066            &handlers::invitation::get_invitation_core(&call.query_as()?, &user, config, ctx)
1067                .await?,
1068        ),
1069        "listInvitations" => EndpointResponse::json(
1070            &handlers::invitation::list_invitations_core(&call.query_as()?, &user, &session, ctx)
1071                .await?,
1072        ),
1073        "listUserInvitations" => EndpointResponse::json(
1074            &handlers::invitation::list_user_invitations_core(&user, ctx).await?,
1075        ),
1076        "acceptInvitation" => {
1077            let transport = handlers::invitation_acceptance::AcceptanceTransport::native(call);
1078            let result = handlers::invitation_acceptance::accept(
1079                &call.body_as()?,
1080                &user,
1081                &session,
1082                config,
1083                ctx,
1084                &transport,
1085            )
1086            .await;
1087            if matches!(result, Err(AuthError::Internal(_) | AuthError::Database(_))) {
1088                transport.discard_cookies();
1089            }
1090            EndpointResponse::json(&result?)
1091        }
1092        "rejectInvitation" => EndpointResponse::json(
1093            &handlers::invitation::reject_invitation_core(&call.body_as()?, &user, config, ctx)
1094                .await?,
1095        ),
1096        "cancelInvitation" => EndpointResponse::json(
1097            &handlers::invitation::cancel_invitation_core(&call.body_as()?, &user, config, ctx)
1098                .await?,
1099        ),
1100        "getActiveMember" => EndpointResponse::json(
1101            &handlers::member::get_active_member_core(&user, &session, ctx).await?,
1102        ),
1103        "getActiveMemberRole" => EndpointResponse::json(
1104            &handlers::member::get_active_member_role_core(&call.query_as()?, &user, &session, ctx)
1105                .await?,
1106        ),
1107        "listMembers" => EndpointResponse::json(
1108            &handlers::member::list_members_core(&call.query_as()?, &user, &session, config, ctx)
1109                .await
1110                .map_err(AuthError::from)?,
1111        ),
1112        "updateMemberRole" => {
1113            let body = handlers::org_input::member_role_update_value(call.body())
1114                .map_err(error_response)?;
1115            response(
1116                handlers::member::update_member_role_response(&body, &user, &session, config, ctx)
1117                    .await?,
1118            )
1119        }
1120        "hasPermission" => EndpointResponse::json(
1121            &handlers::has_permission_core(&call.body_as()?, &user, &session, config, ctx).await?,
1122        ),
1123        "createTeam"
1124        | "updateTeam"
1125        | "removeTeam"
1126        | "setActiveTeam"
1127        | "addTeamMember"
1128        | "removeTeamMember"
1129        | "listTeams"
1130        | "listOrganizationTeams"
1131        | "listUserTeams"
1132        | "listTeamMembers" => {
1133            let path = match call.operation_id() {
1134                "createTeam" => "/organization/create-team",
1135                "updateTeam" => "/organization/update-team",
1136                "removeTeam" => "/organization/remove-team",
1137                "setActiveTeam" => "/organization/set-active-team",
1138                "addTeamMember" => "/organization/add-team-member",
1139                "removeTeamMember" => "/organization/remove-team-member",
1140                "listTeams" | "listOrganizationTeams" => "/organization/list-teams",
1141                "listUserTeams" => "/organization/list-user-teams",
1142                "listTeamMembers" => "/organization/list-team-members",
1143                _ => return Err(AuthError::not_found("Unknown operation")),
1144            };
1145            let method = call.default_method();
1146            let query = query_pairs(call)?;
1147            let body = call
1148                .body()
1149                .map(JsValue::to_json_value)
1150                .transpose()
1151                .map_err(|error| validation(error.to_string()))?;
1152            let result = handlers::team::team_core(
1153                method,
1154                path,
1155                body,
1156                &query,
1157                user,
1158                session,
1159                call.request(),
1160                false,
1161                ctx,
1162                config,
1163            )
1164            .await?;
1165            response(result.ok_or_else(|| AuthError::not_found("Unknown operation"))?)
1166        }
1167        "createOrganizationRole"
1168        | "createOrgRole"
1169        | "updateOrganizationRole"
1170        | "updateOrgRole"
1171        | "deleteOrganizationRole"
1172        | "deleteOrgRole"
1173        | "getOrganizationRole"
1174        | "getOrgRole"
1175        | "listOrgRoles"
1176        | "listOrganizationRoles" => {
1177            let path = match call.operation_id() {
1178                "createOrganizationRole" | "createOrgRole" => "/organization/create-role",
1179                "updateOrganizationRole" | "updateOrgRole" => "/organization/update-role",
1180                "deleteOrganizationRole" | "deleteOrgRole" => "/organization/delete-role",
1181                "getOrganizationRole" | "getOrgRole" => "/organization/get-role",
1182                "listOrganizationRoles" | "listOrgRoles" => "/organization/list-roles",
1183                _ => return Err(AuthError::not_found("Unknown operation")),
1184            };
1185            let method = call.default_method();
1186            let query = query_pairs(call)?;
1187            let body = call
1188                .body()
1189                .map(JsValue::to_json_value)
1190                .transpose()
1191                .map_err(|error| validation(error.to_string()))?
1192                .and_then(|value| value.as_object().cloned())
1193                .unwrap_or_default();
1194            let result =
1195                handlers::role::role_core(method, path, body, &query, call, ctx, config).await?;
1196            response(result.ok_or_else(|| AuthError::not_found("Unknown operation"))?)
1197        }
1198        _ => Err(AuthError::not_found("Unregistered organization operation")),
1199    }
1200}