1#![doc = include_str!("../../../doc/EXT_USERS.md")]
4
5use crate::{
6 DataError, MyError,
7 db::user::{
8 batch_update_users, find_all_ids, find_group_member_ids, find_group_user, find_user,
9 insert_user, update_user,
10 },
11 emit_response, eval_preconditions,
12 lrs::{
13 DB, Headers, Role, User, etag_from_str, resources::WithResource,
14 server::get_consistent_thru,
15 },
16};
17use chrono::SecondsFormat;
18use rocket::{
19 FromForm, Route, State,
20 form::Form,
21 futures::TryFutureExt,
22 get,
23 http::{Header, Status, hyper::header},
24 post, put, routes,
25 serde::json::Json,
26};
27use tracing::{debug, info};
28
29#[derive(Debug, FromForm)]
31struct CreateForm<'a> {
32 email: &'a str,
33 password: &'a str,
34 #[field(validate = range(0..4))]
36 role: u16,
37}
38
39#[derive(Debug, FromForm)]
40#[field(validate = range(0..4))]
41pub(crate) struct RoleUI(pub(crate) u16);
42
43#[derive(Debug, FromForm)]
45pub(crate) struct UpdateForm<'a> {
46 pub(crate) enabled: Option<bool>,
47 pub(crate) email: Option<&'a str>,
48 pub(crate) password: Option<&'a str>,
49 pub(crate) role: Option<RoleUI>,
50 #[field(name = uncased("managerId"))]
51 pub(crate) manager_id: Option<i32>,
52}
53
54#[derive(Debug, FromForm)]
56pub(crate) struct BatchUpdateForm {
57 pub(crate) ids: Vec<i32>,
58 pub(crate) enabled: Option<bool>,
59 pub(crate) role: Option<RoleUI>,
60 #[field(name = uncased("managerId"))]
61 pub(crate) manager_id: Option<i32>,
62}
63
64#[doc(hidden)]
65pub fn routes() -> Vec<Route> {
66 routes![post, get_one, get_ids, update_one, update_many]
67}
68
69#[post("/", data = "<form>")]
79async fn post(
80 form: Form<CreateForm<'_>>,
81 db: &State<DB>,
82 user: User,
83) -> Result<WithResource<User>, MyError> {
84 debug!("----- post ----- {}", user);
85 user.can_manage_users()?;
86
87 let z_role = Role::from(form.role);
91 if !user.is_root() {
94 if !matches!(z_role, Role::User | Role::AuthUser) {
96 return Err(MyError::HTTP {
97 status: Status::Forbidden,
98 info: format!("Admin ({user}) can only create users w/ [Auth]User roles").into(),
99 });
100 }
101 }
102 let x = insert_user(db.pool(), (form.email, form.password, z_role, user.id)).await?;
103 emit_user_response(x, false).await
104}
105
106#[get("/<id>")]
112async fn get_one(id: i32, db: &State<DB>, user: User) -> Result<WithResource<User>, MyError> {
113 debug!("----- get_one ----- {}", user);
114 user.can_manage_users()?;
115
116 if user.is_root() {
117 let x = find_user(db.pool(), id)
118 .map_err(|x| x.with_status(Status::NotFound))
119 .await?;
120 match x {
121 Some(y) => emit_response!(Headers::default(), y => User),
122 None => Err(MyError::HTTP {
123 status: Status::NotFound,
124 info: format!("User #{id} not found").into(),
125 }),
126 }
127 } else if user.is_admin() {
128 let x = find_group_user(db.pool(), id, user.id)
129 .map_err(|x| x.with_status(Status::NotFound))
130 .await?;
131 match x {
132 Some(y) => emit_response!(Headers::default(), y => User),
133 None => Err(MyError::HTTP {
134 status: Status::NotFound,
135 info: format!("User #{id} not found").into(),
136 }),
137 }
138 } else {
139 Err(MyError::HTTP {
140 status: Status::Forbidden,
141 info: "Only Root and Admins can fetch users".into(),
142 })
143 }
144}
145
146#[get("/")]
149async fn get_ids(db: &State<DB>, user: User) -> Result<Json<Vec<i32>>, MyError> {
150 debug!("----- get_ids ----- {}", user);
151 user.can_manage_users()?;
152
153 if user.is_root() {
154 let x = find_all_ids(db.pool())
155 .map_err(|x| x.with_status(Status::NotFound))
156 .await?;
157 Ok(Json(x))
158 } else if user.is_admin() {
159 let x = find_group_member_ids(db.pool(), user.id)
160 .map_err(|x| x.with_status(Status::NotFound))
161 .await?;
162 Ok(Json(x))
163 } else {
164 Err(MyError::HTTP {
165 status: Status::Forbidden,
166 info: "Only Root and Admins can fetch users IDs".into(),
167 })
168 }
169}
170
171#[put("/<id>", data = "<form>")]
180async fn update_one(
181 c: Headers,
182 id: i32,
183 form: Form<UpdateForm<'_>>,
184 db: &State<DB>,
185 user: User,
186) -> Result<WithResource<User>, MyError> {
187 debug!("----- update_one ----- {user:?}");
188 debug!("form = {form:?}");
189
190 let x = find_user(db.pool(), id)
191 .map_err(|x| x.with_status(Status::NotFound))
192 .await?;
193 let old_user = match x {
194 Some(y) => y,
195 None => {
196 return Err(MyError::HTTP {
197 status: Status::NotFound,
198 info: format!("User #{id} not found").into(),
199 });
200 }
201 };
202 debug!("old_user = {old_user:?}");
203 if old_user.is_root() {
204 return Err(MyError::HTTP {
205 status: Status::BadRequest,
206 info: "Root properties are immutable".into(),
207 });
208 }
209
210 if form.enabled.is_some_and(|x| x != old_user.enabled) {
214 if !(user.is_root() || user.id == old_user.manager_id) {
216 return Err(MyError::HTTP {
217 status: Status::BadRequest,
218 info: "Only Root and the user's Admin can alter enabled flag".into(),
219 });
220 }
221 debug!("Will update enabled flag...")
222 } else if form
223 .role
224 .as_ref()
225 .is_some_and(|x| Role::from(x.0) != old_user.role)
226 {
227 if !user.is_root() {
231 if user.id != old_user.manager_id {
232 return Err(MyError::HTTP {
233 status: Status::Forbidden,
234 info: "Only Root and the user's Admin can alter roles".into(),
235 });
236 }
237
238 let new_role = Role::from(form.role.as_ref().unwrap().0);
239 if !matches!(new_role, Role::User | Role::AuthUser) {
240 return Err(MyError::HTTP {
241 status: Status::BadRequest,
242 info: "Admins can alter roles from User to AuthUser or vice-versa only".into(),
243 });
244 }
245 }
246 debug!("Will update role...")
247 } else if form.manager_id.is_some_and(|x| x != old_user.manager_id) {
248 if !user.is_root() {
250 return Err(MyError::HTTP {
251 status: Status::BadRequest,
252 info: "Only Root can alter manager_id".into(),
253 });
254 }
255 debug!("Will update manager_id...")
256 } else if form.email.is_some() || form.password.is_some() {
257 if form.email.is_none() || form.password.is_none() {
259 return Err(MyError::HTTP {
260 status: Status::BadRequest,
261 info: "When updating either 'email' or 'password' both values must be provided"
262 .into(),
263 });
264 }
265 if user.is_root() || user.id != id {
267 return Err(MyError::HTTP {
268 status: Status::BadRequest,
269 info: "Only non-Root user can alter their 'email' + 'password' fields".into(),
270 });
271 }
272 debug!("Will update email + credentials...")
273 } else {
274 return Err(MyError::HTTP {
275 status: Status::BadRequest,
276 info: "You're wasting my time :(".into(),
277 });
278 }
279
280 if c.has_no_conditionals() {
282 Err(MyError::HTTP {
283 status: Status::Conflict,
284 info: "Update User w/ no pre-conditions is NOT allowed".into(),
285 })
286 } else {
287 let x = serde_json::to_string(&old_user).map_err(|x| MyError::Data(DataError::JSON(x)))?;
288 let etag = etag_from_str(&x);
289 debug!("etag (old) = {}", etag);
290 match eval_preconditions!(&etag, c) {
291 s if s != Status::Ok => Err(MyError::HTTP {
292 status: s,
293 info: "Failed pre-condition(s)".into(),
294 }),
295 _ => {
296 let x = update_user(db.pool(), id, form.into_inner()).await?;
297 emit_user_response(x, true).await
298 }
299 }
300 }
301}
302
303#[put("/", data = "<form>")]
314async fn update_many(
315 form: Form<BatchUpdateForm>,
316 db: &State<DB>,
317 user: User,
318) -> Result<Status, MyError> {
319 debug!("----- update_many ----- {user:?}");
320 user.can_manage_users()?;
321
322 debug!("form = {form:?}");
323
324 let ids = &form.ids;
326 if ids.is_empty() {
327 info!("Empty user IDs array. Do nothing");
328 return Ok(Status::Ok);
329 }
330
331 let conn = db.pool();
332 if user.is_admin() {
334 let x = find_group_member_ids(conn, user.id).await?;
335 let ok = ids.iter().all(|id| x.contains(id));
336 if !ok {
337 return Err(MyError::HTTP {
338 status: Status::BadRequest,
339 info: "Admins can only do batch updates for users they manage".into(),
340 });
341 }
342
343 if form
345 .role
346 .as_ref()
347 .is_some_and(|x| !matches!(Role::from(x.0), Role::User | Role::AuthUser))
348 {
349 return Err(MyError::HTTP {
350 status: Status::BadRequest,
351 info: "Admins can only toggle role between User and AuthUser".into(),
352 });
353 }
354
355 if form.manager_id.is_some() {
357 return Err(MyError::HTTP {
358 status: Status::Forbidden,
359 info: "Only Root can re-assign manager ID".into(),
360 });
361 }
362 }
363
364 batch_update_users(db.pool(), form.into_inner()).await?;
365 User::clear_cache().await;
368
369 Ok(Status::Ok)
370}
371
372async fn emit_user_response(u: User, uncache: bool) -> Result<WithResource<User>, MyError> {
375 let x = serde_json::to_string(&u).map_err(|x| MyError::Data(DataError::JSON(x)))?;
376 let etag = etag_from_str(&x);
377 debug!("etag (new) = {}", etag);
378 let last_modified = get_consistent_thru()
379 .await
380 .to_rfc3339_opts(SecondsFormat::Millis, true);
381
382 if uncache {
383 u.uncache().await
384 }
385
386 Ok(WithResource {
387 inner: rocket::serde::json::Json(u),
388 etag: Header::new(header::ETAG.as_str(), etag.to_string()),
389 last_modified: Header::new(header::LAST_MODIFIED.as_str(), last_modified),
390 })
391}