mas_handlers/admin/v1/users/
set_admin.rs1use aide::{OperationIo, transform::TransformOperation};
8use axum::{Json, response::IntoResponse};
9use hyper::StatusCode;
10use mas_axum_utils::record_error;
11use schemars::JsonSchema;
12use serde::Deserialize;
13use ulid::Ulid;
14
15use crate::{
16 admin::{
17 call_context::CallContext,
18 model::{Resource, User},
19 params::UlidPathParam,
20 response::{ErrorResponse, SingleResponse},
21 },
22 impl_from_error_for_route,
23};
24
25#[derive(Debug, thiserror::Error, OperationIo)]
26#[aide(output_with = "Json<ErrorResponse>")]
27pub enum RouteError {
28 #[error(transparent)]
29 Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
30
31 #[error("User ID {0} not found")]
32 NotFound(Ulid),
33}
34
35impl_from_error_for_route!(mas_storage::RepositoryError);
36
37impl IntoResponse for RouteError {
38 fn into_response(self) -> axum::response::Response {
39 let error = ErrorResponse::from_error(&self);
40 let sentry_event_id = record_error!(self, Self::Internal(_));
41 let status = match self {
42 Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
43 Self::NotFound(_) => StatusCode::NOT_FOUND,
44 };
45 (status, sentry_event_id, Json(error)).into_response()
46 }
47}
48
49#[derive(Deserialize, JsonSchema)]
51#[serde(rename = "UserSetAdminRequest")]
52pub struct Request {
53 admin: bool,
55}
56
57pub fn doc(operation: TransformOperation) -> TransformOperation {
58 operation
59 .id("userSetAdmin")
60 .summary("Set whether a user can request admin")
61 .description("Calling this endpoint will not have any effect on existing sessions, meaning that their existing sessions will keep admin access if they were granted it.")
62 .tag("user")
63 .response_with::<200, Json<SingleResponse<User>>, _>(|t| {
64 let [_alice, bob, ..] = User::samples();
66 let id = bob.id();
67 let response = SingleResponse::new(bob, format!("/api/admin/v1/users/{id}/set-admin"));
68 t.description("User had admin privileges set").example(response)
69 })
70 .response_with::<404, RouteError, _>(|t| {
71 let response = ErrorResponse::from_error(&RouteError::NotFound(Ulid::nil()));
72 t.description("User ID not found").example(response)
73 })
74}
75
76#[tracing::instrument(name = "handler.admin.v1.users.set_admin", skip_all)]
77pub async fn handler(
78 CallContext { mut repo, .. }: CallContext,
79 id: UlidPathParam,
80 Json(params): Json<Request>,
81) -> Result<Json<SingleResponse<User>>, RouteError> {
82 let id = *id;
83 let user = repo
84 .user()
85 .lookup(id)
86 .await?
87 .ok_or(RouteError::NotFound(id))?;
88
89 let user = repo
90 .user()
91 .set_can_request_admin(user, params.admin)
92 .await?;
93
94 repo.save().await?;
95
96 Ok(Json(SingleResponse::new(
97 User::from(user),
98 format!("/api/admin/v1/users/{id}/set-admin"),
99 )))
100}
101
102#[cfg(test)]
103mod tests {
104 use hyper::{Request, StatusCode};
105 use mas_storage::{RepositoryAccess, user::UserRepository};
106 use sqlx::PgPool;
107
108 use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup};
109
110 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
111 async fn test_change_can_request_admin(pool: PgPool) {
112 setup();
113 let mut state = TestState::from_pool(pool).await.unwrap();
114 let token = state.token_with_scope("urn:mas:admin").await;
115
116 let mut repo = state.repository().await.unwrap();
117 let user = repo
118 .user()
119 .add(&mut state.rng(), &state.clock, "alice".to_owned())
120 .await
121 .unwrap();
122 repo.save().await.unwrap();
123
124 let request = Request::post(format!("/api/admin/v1/users/{}/set-admin", user.id))
125 .bearer(&token)
126 .json(serde_json::json!({
127 "admin": true,
128 }));
129
130 let response = state.request(request).await;
131 response.assert_status(StatusCode::OK);
132 let body: serde_json::Value = response.json();
133
134 assert_eq!(body["data"]["attributes"]["admin"], true);
135
136 let mut repo = state.repository().await.unwrap();
138 let user = repo.user().lookup(user.id).await.unwrap().unwrap();
139 assert!(user.can_request_admin);
140 repo.save().await.unwrap();
141
142 let request = Request::post(format!("/api/admin/v1/users/{}/set-admin", user.id))
144 .bearer(&token)
145 .json(serde_json::json!({
146 "admin": false,
147 }));
148
149 let response = state.request(request).await;
150 response.assert_status(StatusCode::OK);
151 let body: serde_json::Value = response.json();
152
153 assert_eq!(body["data"]["attributes"]["admin"], false);
154
155 let mut repo = state.repository().await.unwrap();
157 let user = repo.user().lookup(user.id).await.unwrap().unwrap();
158 assert!(!user.can_request_admin);
159 repo.save().await.unwrap();
160 }
161}