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