Skip to main content

alibi_plugins/admin/
endpoint.rs

1//! Installed admin server operations share authorization and stored output with HTTP.
2use super::{
3    AdminPlugin, MESSAGE_BAN_USERS, MESSAGE_CHANGE_ROLE, MESSAGE_DELETE_USERS, MESSAGE_GET_USER,
4    MESSAGE_IMPERSONATE_USERS, MESSAGE_LIST_USER_SESSIONS, MESSAGE_LIST_USERS,
5    MESSAGE_REVOKE_USER_SESSIONS, MESSAGE_SET_USER_PASSWORD, MESSAGE_UPDATE_USERS, ban_user_core,
6    create_user_core, get_user_core, impersonate_user_core, list_user_sessions_core,
7    list_users_core, remove_user_core, revoke_user_session_core, revoke_user_sessions_core,
8    set_role_core, set_user_password_core,
9    types::{
10        AdminUpdateUserRequest, BanUserRequest, CreateUserRequest, GetUserQuery,
11        HasPermissionRequest, PermissionResponse, RevokeSessionRequest, SetRoleRequest,
12        SetUserPasswordRequest, UserIdRequest,
13    },
14    unban_user_core, update_user_core,
15};
16use alibi_core::endpoint::{
17    EndpointCall, EndpointDefinition, EndpointInput, EndpointResponse, ServerEndpoint,
18};
19use alibi_core::utils::json::JsValue;
20use alibi_core::{AuthContext, AuthError, AuthResult, AuthSchema, HttpMethod};
21
22pub(super) fn definitions() -> Vec<EndpointDefinition> {
23    vec![
24        crate::endpoint::definition(
25            "getUser",
26            "getUser",
27            Some("/admin/get-user"),
28            HttpMethod::Get,
29        ),
30        crate::endpoint::definition(
31            "stopImpersonating",
32            "stopImpersonating",
33            Some("/admin/stop-impersonating"),
34            HttpMethod::Post,
35        ),
36        crate::endpoint::definition(
37            "impersonateUser",
38            "impersonateUser",
39            Some("/admin/impersonate-user"),
40            HttpMethod::Post,
41        ),
42        crate::endpoint::definition(
43            "setUserPassword",
44            "setUserPassword",
45            Some("/admin/set-user-password"),
46            HttpMethod::Post,
47        ),
48        crate::endpoint::definition(
49            "removeUser",
50            "removeUser",
51            Some("/admin/remove-user"),
52            HttpMethod::Post,
53        ),
54        crate::endpoint::definition(
55            "revokeUserSessions",
56            "revokeUserSessions",
57            Some("/admin/revoke-user-sessions"),
58            HttpMethod::Post,
59        ),
60        crate::endpoint::definition(
61            "revokeUserSession",
62            "revokeUserSession",
63            Some("/admin/revoke-user-session"),
64            HttpMethod::Post,
65        ),
66        crate::endpoint::definition(
67            "banUser",
68            "banUser",
69            Some("/admin/ban-user"),
70            HttpMethod::Post,
71        ),
72        crate::endpoint::definition(
73            "unbanUser",
74            "unbanUser",
75            Some("/admin/unban-user"),
76            HttpMethod::Post,
77        ),
78        crate::endpoint::definition(
79            "listUserSessions",
80            "adminListUserSessions",
81            Some("/admin/list-user-sessions"),
82            HttpMethod::Post,
83        ),
84        crate::endpoint::definition(
85            "listUsers",
86            "listUsers",
87            Some("/admin/list-users"),
88            HttpMethod::Get,
89        ),
90        crate::endpoint::definition(
91            "adminUpdateUser",
92            "adminUpdateUser",
93            Some("/admin/update-user"),
94            HttpMethod::Post,
95        ),
96        crate::endpoint::definition(
97            "userHasPermission",
98            "userHasPermission",
99            Some("/admin/has-permission"),
100            HttpMethod::Post,
101        ),
102        crate::endpoint::definition(
103            "createUser",
104            "createUser",
105            Some("/admin/create-user"),
106            HttpMethod::Post,
107        ),
108        crate::endpoint::definition(
109            "setRole",
110            "setUserRole",
111            Some("/admin/set-role"),
112            HttpMethod::Post,
113        ),
114    ]
115}
116
117pub(super) fn validate(call: &EndpointCall) -> AuthResult<EndpointInput> {
118    if call.operation_id() == "stopImpersonating" {
119        if call.headers().is_none() {
120            return Err(crate::endpoint::validation("Headers is required"));
121        }
122        return Ok(EndpointInput {
123            body: call.body().cloned(),
124            query: call.query().cloned(),
125        });
126    }
127    if call.operation_id() == "impersonateUser" {
128        let body: UserIdRequest =
129            super::validation::body_value("impersonate-user", call.body().cloned())
130                .map_err(crate::endpoint::error_response)?;
131        return Ok(EndpointInput {
132            body: Some(JsValue::Object(
133                [("userId".into(), JsValue::String(body.user_id))]
134                    .into_iter()
135                    .collect(),
136            )),
137            query: call.query().cloned(),
138        });
139    }
140    if call.operation_id() == "setUserPassword" {
141        let body: SetUserPasswordRequest =
142            super::validation::body_value("set-user-password", call.body().cloned())
143                .map_err(crate::endpoint::error_response)?;
144        return Ok(EndpointInput {
145            body: Some(alibi_core::utils::json::to_value(&body)?.into()),
146            query: call.query().cloned(),
147        });
148    }
149    if call.operation_id() == "removeUser" {
150        let body: UserIdRequest =
151            super::validation::body_value("remove-user", call.body().cloned())
152                .map_err(crate::endpoint::error_response)?;
153        return Ok(EndpointInput {
154            body: Some(JsValue::Object(
155                [("userId".into(), JsValue::String(body.user_id))]
156                    .into_iter()
157                    .collect(),
158            )),
159            query: call.query().cloned(),
160        });
161    }
162    if call.operation_id() == "revokeUserSessions" {
163        let body: UserIdRequest =
164            super::validation::body_value("revoke-user-sessions", call.body().cloned())
165                .map_err(crate::endpoint::error_response)?;
166        return Ok(EndpointInput {
167            body: Some(JsValue::Object(
168                [("userId".into(), JsValue::String(body.user_id))]
169                    .into_iter()
170                    .collect(),
171            )),
172            query: call.query().cloned(),
173        });
174    }
175    if call.operation_id() == "revokeUserSession" {
176        let body: RevokeSessionRequest =
177            super::validation::body_value("revoke-user-session", call.body().cloned())
178                .map_err(crate::endpoint::error_response)?;
179        return Ok(EndpointInput {
180            body: Some(JsValue::Object(
181                [("sessionToken".into(), JsValue::String(body.session_token))]
182                    .into_iter()
183                    .collect(),
184            )),
185            query: call.query().cloned(),
186        });
187    }
188    if call.operation_id() == "banUser" {
189        let body: BanUserRequest = super::validation::body_value("ban-user", call.body().cloned())
190            .map_err(crate::endpoint::error_response)?;
191        return Ok(EndpointInput {
192            body: Some(alibi_core::utils::json::to_value(&body)?.into()),
193            query: call.query().cloned(),
194        });
195    }
196    if call.operation_id() == "unbanUser" {
197        let body: UserIdRequest = super::validation::body_value("unban-user", call.body().cloned())
198            .map_err(crate::endpoint::error_response)?;
199        return Ok(EndpointInput {
200            body: Some(JsValue::Object(
201                [("userId".into(), JsValue::String(body.user_id))]
202                    .into_iter()
203                    .collect(),
204            )),
205            query: call.query().cloned(),
206        });
207    }
208    if call.operation_id() == "adminListUserSessions" {
209        let body: UserIdRequest =
210            super::validation::body_value("list-user-sessions", call.body().cloned())
211                .map_err(crate::endpoint::error_response)?;
212        return Ok(EndpointInput {
213            body: Some(JsValue::Object(
214                [("userId".into(), JsValue::String(body.user_id))]
215                    .into_iter()
216                    .collect(),
217            )),
218            query: call.query().cloned(),
219        });
220    }
221    if call.operation_id() == "listUsers" {
222        return super::listing::validate(call);
223    }
224    if call.operation_id() == "adminUpdateUser" {
225        let body: AdminUpdateUserRequest =
226            super::validation::body_value("update-user", call.body().cloned())
227                .map_err(crate::endpoint::error_response)?;
228        return Ok(EndpointInput {
229            body: Some(JsValue::from(serde_json::to_value(body)?)),
230            query: call.query().cloned(),
231        });
232    }
233    if call.operation_id() == "userHasPermission" {
234        let body: HasPermissionRequest =
235            super::validation::body_value("has-permission", call.body().cloned())
236                .map_err(crate::endpoint::error_response)?;
237        return Ok(EndpointInput {
238            body: Some(JsValue::from(serde_json::to_value(body)?)),
239            query: call.query().cloned(),
240        });
241    }
242    if call.operation_id() == "createUser" {
243        let body: CreateUserRequest =
244            super::validation::body_value("create-user", call.body().cloned())
245                .map_err(crate::endpoint::error_response)?;
246        return Ok(EndpointInput {
247            body: Some(JsValue::from(serde_json::to_value(body)?)),
248            query: call.query().cloned(),
249        });
250    }
251    if call.operation_id() == "setUserRole" {
252        let body: SetRoleRequest = super::validation::body_value("set-role", call.body().cloned())
253            .map_err(crate::endpoint::error_response)?;
254        if call.headers().is_none() {
255            return Err(crate::endpoint::validation("Headers is required"));
256        }
257        return Ok(EndpointInput {
258            body: Some(JsValue::from(serde_json::to_value(body)?)),
259            query: call.query().cloned(),
260        });
261    }
262    let query = crate::endpoint::validate_fields(
263        call.query(),
264        "query",
265        &[crate::authentication_helpers::JsonField::string("id", true)],
266    )?;
267    Ok(EndpointInput {
268        query: Some(query),
269        body: call.body().cloned(),
270    })
271}
272
273impl AdminPlugin {
274    /// Read a user through the installed admin policy and server endpoint hooks.
275    #[must_use]
276    pub fn get_user_endpoint(id: impl Into<String>) -> ServerEndpoint<super::AdminUserOutput> {
277        ServerEndpoint::new("admin", "getUser").with_query_value(JsValue::Object(
278            [("id".into(), JsValue::String(id.into()))]
279                .into_iter()
280                .collect(),
281        ))
282    }
283
284    /// Assign literal roles through installed server policy and hooks.
285    pub fn set_role_endpoint(
286        body: &super::AdminSetRoleRequest,
287    ) -> AuthResult<ServerEndpoint<super::AdminUserResponse>> {
288        ServerEndpoint::new("admin", "setRole").with_body(body)
289    }
290
291    /// Provision a user through installed server policy and hooks.
292    /// Without a Request or headers, the call is trusted; supplying either requires a session.
293    pub fn create_user_endpoint(
294        body: &super::AdminCreateUserRequest,
295    ) -> AuthResult<ServerEndpoint<super::AdminUserResponse>> {
296        ServerEndpoint::new("admin", "createUser").with_body(body)
297    }
298
299    /// Evaluate permissions through the installed admin policy and server hooks.
300    /// Trusted selectors apply only without a Request or headers; a session always wins.
301    pub fn user_has_permission_endpoint(
302        body: &super::AdminPermissionRequest,
303    ) -> AuthResult<ServerEndpoint<super::AdminPermissionResponse>> {
304        ServerEndpoint::new("admin", "userHasPermission").with_body(body)
305    }
306
307    /// Update a user through authoritative admin policy and installed server hooks.
308    pub fn admin_update_user_endpoint(
309        body: &super::AdminUpdateUserRequest,
310    ) -> AuthResult<ServerEndpoint<super::AdminUserOutput>> {
311        ServerEndpoint::new("admin", "adminUpdateUser").with_body(body)
312    }
313
314    /// List users through authoritative admin policy and installed server hooks.
315    pub fn list_users_endpoint(
316        query: &super::AdminListUsersQuery,
317    ) -> AuthResult<ServerEndpoint<super::AdminListUsersResponse>> {
318        ServerEndpoint::new("admin", "listUsers").with_query(query)
319    }
320
321    /// List a user's stored sessions through authoritative admin policy and server hooks.
322    #[must_use]
323    pub fn list_user_sessions_endpoint(
324        user_id: impl Into<String>,
325    ) -> ServerEndpoint<super::AdminUserSessionsResponse> {
326        ServerEndpoint::new("admin", "listUserSessions").with_body_value(JsValue::Object(
327            [("userId".into(), JsValue::String(user_id.into()))]
328                .into_iter()
329                .collect(),
330        ))
331    }
332
333    /// Restore the original session from a signed impersonation cookie.
334    #[must_use]
335    pub fn stop_impersonating_endpoint() -> ServerEndpoint<super::AdminImpersonationResponse> {
336        ServerEndpoint::new("admin", "stopImpersonating")
337    }
338
339    async fn call_stop_impersonating(
340        call: &EndpointCall,
341        ctx: &AuthContext<impl AuthSchema>,
342    ) -> AuthResult<EndpointResponse> {
343        let (user, session) = match ctx.require_cached_session(call).await {
344            Ok(read) => read,
345            Err(AuthError::Unauthenticated) => {
346                return Ok(EndpointResponse::error(AuthError::Api {
347                    status: 401,
348                    code: None,
349                    message: String::new(),
350                })
351                .without_error_body());
352            }
353            Err(error) => return Err(error),
354        };
355        let user = match &user {
356            alibi_core::AuthenticatedUser::Stored(user) => ctx.user_view(user),
357            alibi_core::AuthenticatedUser::Cached(user) => (**user).clone(),
358        };
359        call.record_authenticated_session(user, session.clone());
360        let (value, token, dont_remember) =
361            super::stop_impersonating_core(&session, call, ctx).await?;
362        let mut response = EndpointResponse::json(&value)?;
363        response.merge_headers(super::cookies::restoration_cookie_headers(
364            &ctx.config,
365            &token,
366            dont_remember,
367        )?);
368        Ok(response)
369    }
370
371    /// Create an impersonation session and publish cookies through authoritative admin policy.
372    #[must_use]
373    pub fn impersonate_user_endpoint(
374        user_id: impl Into<String>,
375    ) -> ServerEndpoint<super::AdminImpersonationResponse> {
376        ServerEndpoint::new("admin", "impersonateUser").with_body_value(JsValue::Object(
377            [("userId".into(), JsValue::String(user_id.into()))]
378                .into_iter()
379                .collect(),
380        ))
381    }
382
383    /// Replace or create a credential through authoritative admin policy and server hooks.
384    ///
385    /// # Errors
386    /// Returns an error if the logical input cannot be serialized.
387    pub fn set_user_password_endpoint(
388        input: &super::AdminSetUserPasswordRequest,
389    ) -> AuthResult<ServerEndpoint<alibi_core::StatusResponse>> {
390        ServerEndpoint::new("admin", "setUserPassword").with_body(input)
391    }
392
393    /// Remove a stored user, their accounts and sessions through authoritative admin policy.
394    #[must_use]
395    pub fn remove_user_endpoint(
396        user_id: impl Into<String>,
397    ) -> ServerEndpoint<super::AdminSuccessResponse> {
398        ServerEndpoint::new("admin", "removeUser").with_body_value(JsValue::Object(
399            [("userId".into(), JsValue::String(user_id.into()))]
400                .into_iter()
401                .collect(),
402        ))
403    }
404
405    /// Revoke all stored sessions for a user through authoritative administrative policy.
406    #[must_use]
407    pub fn revoke_user_sessions_endpoint(
408        user_id: impl Into<String>,
409    ) -> ServerEndpoint<super::AdminSuccessResponse> {
410        ServerEndpoint::new("admin", "revokeUserSessions").with_body_value(JsValue::Object(
411            [("userId".into(), JsValue::String(user_id.into()))]
412                .into_iter()
413                .collect(),
414        ))
415    }
416
417    /// Revoke one stored session token through authoritative administrative policy.
418    #[must_use]
419    pub fn revoke_user_session_endpoint(
420        session_token: impl Into<String>,
421    ) -> ServerEndpoint<super::AdminSuccessResponse> {
422        ServerEndpoint::new("admin", "revokeUserSession").with_body_value(JsValue::Object(
423            [("sessionToken".into(), JsValue::String(session_token.into()))]
424                .into_iter()
425                .collect(),
426        ))
427    }
428
429    /// Ban a stored user and revoke their sessions through authoritative admin policy.
430    ///
431    /// # Errors
432    /// Returns an error if the logical input cannot be serialized.
433    pub fn ban_user_endpoint(
434        input: &super::AdminBanUserRequest,
435    ) -> AuthResult<ServerEndpoint<super::AdminUserResponse>> {
436        ServerEndpoint::new("admin", "banUser").with_body(input)
437    }
438
439    /// Clear a stored user's ban through authoritative admin policy and server hooks.
440    #[must_use]
441    pub fn unban_user_endpoint(
442        user_id: impl Into<String>,
443    ) -> ServerEndpoint<super::AdminUserResponse> {
444        ServerEndpoint::new("admin", "unbanUser").with_body_value(JsValue::Object(
445            [("userId".into(), JsValue::String(user_id.into()))]
446                .into_iter()
447                .collect(),
448        ))
449    }
450
451    async fn call_permission_endpoint(
452        &self,
453        call: &EndpointCall,
454        ctx: &AuthContext<impl AuthSchema>,
455    ) -> AuthResult<EndpointResponse> {
456        let body: HasPermissionRequest = call.body_as()?;
457        let requested = body.permissions.as_ref().ok_or_else(|| {
458            message_error("invalid permission check. no permission(s) were passed.")
459        })?;
460        let read = ctx.require_authoritative_session_record(call).await.ok();
461        let (user_id, role) = if let Some((user, session)) = read {
462            let user = ctx.user_view(&user);
463            call.record_authenticated_session(user.clone(), session);
464            (Some(user.id), user.role)
465        } else {
466            if call.request().is_some() || call.headers().is_some() {
467                return Ok(EndpointResponse::error(AuthError::Api {
468                    status: 401,
469                    code: None,
470                    message: String::new(),
471                })
472                .without_error_body());
473            }
474            let id = body.user_id.as_ref().filter(|id| !id.is_empty());
475            if let Some(role) = body.role.as_ref().filter(|role| !role.is_empty()) {
476                (id.cloned(), Some(role.clone()))
477            } else if let Some(id) = id {
478                let user = ctx
479                    .database
480                    .get_user_by_id_record(id)
481                    .await?
482                    .ok_or_else(|| message_error("user not found"))?;
483                let user = ctx.user_view(&user);
484                (Some(user.id), user.role)
485            } else {
486                return Err(message_error("user id or role is required"));
487            }
488        };
489        EndpointResponse::json(&PermissionResponse {
490            error: None,
491            success: super::access::has_permission(
492                user_id.as_deref(),
493                role.as_deref(),
494                &self.config,
495                requested,
496            ),
497        })
498    }
499
500    pub(super) async fn call_admin_endpoint(
501        &self,
502        call: &EndpointCall,
503        ctx: &AuthContext<impl AuthSchema>,
504    ) -> AuthResult<EndpointResponse> {
505        if call.operation_id() == "stopImpersonating" {
506            return Self::call_stop_impersonating(call, ctx).await;
507        }
508        if call.operation_id() == "userHasPermission" {
509            return self.call_permission_endpoint(call, ctx).await;
510        }
511        // The reference middleware resolves a fresh authoritative session and
512        // converts a failed nested session read to an absent session.
513        let read = ctx.require_authoritative_session_record(call).await.ok();
514        if call.operation_id() == "createUser" {
515            let body: CreateUserRequest = call.body_as()?;
516            if let Some((user, session)) = read {
517                let principal = ctx.user_view(&user);
518                call.record_authenticated_session(principal.clone(), session);
519                self.authorize_create_user(&principal, &body)?;
520            } else if call.request().is_some() || call.headers().is_some() {
521                return Ok(EndpointResponse::error(AuthError::Api {
522                    status: 401,
523                    code: None,
524                    message: String::new(),
525                })
526                .without_error_body());
527            }
528            return EndpointResponse::json(&create_user_core(&body, &self.config, ctx).await?);
529        }
530        let Some((user, session)) = read else {
531            return Ok(EndpointResponse::error(AuthError::Api {
532                status: 401,
533                code: None,
534                message: String::new(),
535            })
536            .without_error_body());
537        };
538        let principal = ctx.user_view(&user);
539        call.record_authenticated_session(principal.clone(), session.clone());
540        if call.operation_id() == "impersonateUser" {
541            self.authorize(&principal, "user", "impersonate", MESSAGE_IMPERSONATE_USERS)?;
542            let body: UserIdRequest = call.body_as()?;
543            let headers = call.headers().cloned().unwrap_or_default();
544            return match impersonate_user_core(
545                &body,
546                principal.id.as_str(),
547                principal.role.as_deref(),
548                call,
549                &self.config,
550                ctx,
551            )
552            .await
553            {
554                Ok((user, token)) => {
555                    let mut response = EndpointResponse::json(&user)?;
556                    response.merge_headers(super::cookies::impersonation_cookie_headers(
557                        &ctx.config,
558                        &headers,
559                        &session.token,
560                        &token,
561                    )?);
562                    Ok(response)
563                }
564                Err(super::AdminDateOperationError::Auth(error)) => Err(error),
565                Err(super::AdminDateOperationError::InvalidDate) => {
566                    Ok(EndpointResponse::error(AuthError::Api {
567                        status: 500,
568                        code: None,
569                        message: String::new(),
570                    })
571                    .without_error_body())
572                }
573            };
574        }
575        if call.operation_id() == "setUserPassword" {
576            self.authorize(
577                &principal,
578                "user",
579                "set-password",
580                MESSAGE_SET_USER_PASSWORD,
581            )?;
582            let body: SetUserPasswordRequest = call.body_as()?;
583            return EndpointResponse::json(&set_user_password_core(&body, ctx).await?);
584        }
585        if call.operation_id() == "removeUser" {
586            self.authorize(&principal, "user", "delete", MESSAGE_DELETE_USERS)?;
587            let body: UserIdRequest = call.body_as()?;
588            return EndpointResponse::json(
589                &remove_user_core(&body, principal.id.as_str(), ctx).await?,
590            );
591        }
592        if call.operation_id() == "revokeUserSessions" {
593            self.authorize(
594                &principal,
595                "session",
596                "revoke",
597                MESSAGE_REVOKE_USER_SESSIONS,
598            )?;
599            let body: UserIdRequest = call.body_as()?;
600            return EndpointResponse::json(&revoke_user_sessions_core(&body, ctx).await?);
601        }
602        if call.operation_id() == "revokeUserSession" {
603            self.authorize(
604                &principal,
605                "session",
606                "revoke",
607                MESSAGE_REVOKE_USER_SESSIONS,
608            )?;
609            let body: RevokeSessionRequest = call.body_as()?;
610            return EndpointResponse::json(&revoke_user_session_core(&body, ctx).await?);
611        }
612        if call.operation_id() == "banUser" {
613            self.authorize(&principal, "user", "ban", MESSAGE_BAN_USERS)?;
614            let body: BanUserRequest = call.body_as()?;
615            return match ban_user_core(&body, principal.id.as_str(), &self.config, ctx).await {
616                Ok(user) => EndpointResponse::json(&user),
617                Err(super::AdminDateOperationError::Auth(error)) => Err(error),
618                Err(super::AdminDateOperationError::InvalidDate) => {
619                    Ok(EndpointResponse::error(AuthError::Api {
620                        status: 500,
621                        code: None,
622                        message: String::new(),
623                    })
624                    .without_error_body())
625                }
626            };
627        }
628        if call.operation_id() == "unbanUser" {
629            self.authorize(&principal, "user", "ban", MESSAGE_BAN_USERS)?;
630            let body: UserIdRequest = call.body_as()?;
631            return EndpointResponse::json(&unban_user_core(&body, ctx).await?);
632        }
633        if call.operation_id() == "adminListUserSessions" {
634            self.authorize(&principal, "session", "list", MESSAGE_LIST_USER_SESSIONS)?;
635            let body: UserIdRequest = call.body_as()?;
636            return EndpointResponse::json(&list_user_sessions_core(&body, ctx).await?);
637        }
638        if call.operation_id() == "listUsers" {
639            self.authorize(&principal, "user", "list", MESSAGE_LIST_USERS)?;
640            let query = super::listing::params(call)?;
641            return EndpointResponse::json(&list_users_core(&query, ctx).await?);
642        }
643        if call.operation_id() == "adminUpdateUser" {
644            self.authorize(&principal, "user", "update", MESSAGE_UPDATE_USERS)?;
645            let body: AdminUpdateUserRequest = call.body_as()?;
646            return EndpointResponse::json(
647                &update_user_core(&body, &principal, &self.config, ctx).await?,
648            );
649        }
650        if call.operation_id() == "setUserRole" {
651            self.authorize(&principal, "user", "set-role", MESSAGE_CHANGE_ROLE)?;
652            let body: SetRoleRequest = call.body_as()?;
653            return EndpointResponse::json(&set_role_core(&body, &self.config, ctx).await?);
654        }
655        self.authorize(&principal, "user", "get", MESSAGE_GET_USER)?;
656        let query: GetUserQuery = call.query_as()?;
657        EndpointResponse::json(&get_user_core(&query, ctx).await?)
658    }
659}
660
661fn message_error(message: &str) -> AuthError {
662    AuthError::Api {
663        status: 400,
664        code: None,
665        message: message.into(),
666    }
667}