1use 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 #[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 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 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 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 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 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 #[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 #[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 #[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 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 #[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 #[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 #[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 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 #[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 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}