230 lines
6.5 KiB
Rust
230 lines
6.5 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use axum::{
|
|
Json, Router, extract,
|
|
http::StatusCode,
|
|
response::{IntoResponse, Redirect},
|
|
routing,
|
|
};
|
|
|
|
use non_empty_string::NonEmptyString;
|
|
use serde::{Deserialize, Serialize};
|
|
use sqlx::PgPool;
|
|
|
|
use crate::{
|
|
config::Config, models, routes::auth, state::State,
|
|
utils::crypto::generate_random_password_hash,
|
|
};
|
|
|
|
#[derive(Debug, Serialize)]
|
|
struct UserResponse {
|
|
display_name: String,
|
|
email: String,
|
|
disabled: bool,
|
|
image: Option<String>,
|
|
groups: Vec<String>,
|
|
}
|
|
|
|
impl From<models::users::UserWithGroups> for UserResponse {
|
|
fn from(user: models::users::UserWithGroups) -> Self {
|
|
Self {
|
|
display_name: user.display_name,
|
|
email: user.email,
|
|
disabled: user.disabled,
|
|
image: user.image,
|
|
groups: user.groups,
|
|
}
|
|
}
|
|
}
|
|
|
|
type UsersResponse = HashMap<String, UserResponse>;
|
|
|
|
pub async fn get_all(
|
|
_: auth::User,
|
|
extract::State(pg_pool): extract::State<PgPool>,
|
|
) -> Result<impl IntoResponse, StatusCode> {
|
|
let users_with_groups = models::users::UserWithGroups::select(&pg_pool)
|
|
.await
|
|
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
|
|
|
let users_response = users_with_groups
|
|
.into_iter()
|
|
.map(|user| (user.name.clone(), UserResponse::from(user)))
|
|
.collect::<UsersResponse>();
|
|
|
|
Ok(Json(users_response))
|
|
}
|
|
|
|
pub async fn get(
|
|
_: auth::User,
|
|
extract::Path(name): extract::Path<NonEmptyString>,
|
|
extract::State(pg_pool): extract::State<PgPool>,
|
|
) -> Result<impl IntoResponse, StatusCode> {
|
|
let user_with_groups = models::users::UserWithGroups::select_by_name(&pg_pool, name.as_str())
|
|
.await
|
|
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
|
.ok_or(StatusCode::NOT_FOUND)?;
|
|
|
|
Ok(Json(UserResponse::from(user_with_groups)))
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct UserCreate {
|
|
name: NonEmptyString,
|
|
displayname: NonEmptyString,
|
|
email: NonEmptyString,
|
|
disabled: bool,
|
|
image: Option<NonEmptyString>,
|
|
groups: Vec<NonEmptyString>,
|
|
}
|
|
|
|
pub async fn create(
|
|
_: auth::User,
|
|
extract::State(pg_pool): extract::State<PgPool>,
|
|
extract::Json(user_create): extract::Json<UserCreate>,
|
|
) -> Result<impl IntoResponse, StatusCode> {
|
|
if models::users::User::select_by_name(&pg_pool, user_create.name.as_str())
|
|
.await
|
|
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
|
.is_some()
|
|
{
|
|
return Err(StatusCode::CONFLICT);
|
|
}
|
|
|
|
let groups = user_create
|
|
.groups
|
|
.into_iter()
|
|
.map(|g| g.to_string())
|
|
.collect::<Vec<_>>();
|
|
|
|
if !models::groups::Group::all_exist_by_names(&pg_pool, &groups)
|
|
.await
|
|
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
|
{
|
|
return Err(StatusCode::NOT_FOUND);
|
|
}
|
|
|
|
let user_with_groups = models::users::UserWithGroups {
|
|
name: user_create.name.to_string(),
|
|
display_name: user_create.displayname.to_string(),
|
|
password: generate_random_password_hash(),
|
|
email: user_create.email.to_string(),
|
|
disabled: user_create.disabled,
|
|
image: user_create.image.map(|i| i.to_string()),
|
|
groups,
|
|
};
|
|
|
|
models::users::UserWithGroups::insert(&pg_pool, &user_with_groups)
|
|
.await
|
|
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct UserUpdate {
|
|
display_name: Option<NonEmptyString>,
|
|
email: Option<NonEmptyString>,
|
|
disabled: Option<bool>,
|
|
image: Option<NonEmptyString>,
|
|
groups: Option<Vec<NonEmptyString>>,
|
|
}
|
|
|
|
pub async fn update(
|
|
session_user: auth::User,
|
|
extract::Path(name): extract::Path<NonEmptyString>,
|
|
extract::State(pg_pool): extract::State<PgPool>,
|
|
extract::State(config): extract::State<Config>,
|
|
extract::Json(user_update): extract::Json<UserUpdate>,
|
|
) -> Result<impl IntoResponse, StatusCode> {
|
|
let user = models::users::User::select_by_name(&pg_pool, name.as_str())
|
|
.await
|
|
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
|
.ok_or(StatusCode::NOT_FOUND)?;
|
|
|
|
let mut logout = false;
|
|
|
|
if let Some(groups) = user_update.groups {
|
|
let groups = groups
|
|
.into_iter()
|
|
.map(|g| g.to_string())
|
|
.collect::<Vec<_>>();
|
|
|
|
if !models::groups::Group::all_exist_by_names(&pg_pool, &groups)
|
|
.await
|
|
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
|
{
|
|
return Err(StatusCode::NOT_FOUND);
|
|
}
|
|
|
|
models::intersections::UsersGroups::set_groups_for_user(
|
|
&pg_pool,
|
|
user.name.as_str(),
|
|
&groups,
|
|
)
|
|
.await
|
|
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
|
|
|
if name == session_user.username.to_string() && !groups.contains(&config.oauth.admin_group)
|
|
{
|
|
logout = true;
|
|
}
|
|
}
|
|
|
|
let user = models::users::User {
|
|
name: user.name,
|
|
display_name: user_update
|
|
.display_name
|
|
.map(|d| d.to_string())
|
|
.unwrap_or(user.display_name),
|
|
password: user.password,
|
|
email: user_update
|
|
.email
|
|
.map(|e| e.to_string())
|
|
.unwrap_or(user.email),
|
|
disabled: user_update.disabled.unwrap_or(user.disabled),
|
|
image: user_update.image.map(|i| i.to_string()).or(user.image),
|
|
};
|
|
|
|
models::users::User::upsert(&pg_pool, &user)
|
|
.await
|
|
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
|
|
|
if logout {
|
|
return Ok(Redirect::to("/api/auth/logout").into_response());
|
|
}
|
|
|
|
Ok(().into_response())
|
|
}
|
|
|
|
pub async fn delete(
|
|
session_user: auth::User,
|
|
extract::Path(name): extract::Path<String>,
|
|
extract::State(pg_pool): extract::State<PgPool>,
|
|
) -> Result<impl IntoResponse, StatusCode> {
|
|
if name == session_user.username.to_string() {
|
|
return Err(StatusCode::FORBIDDEN);
|
|
}
|
|
|
|
let user = models::users::User::select_by_name(&pg_pool, &name)
|
|
.await
|
|
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
|
.ok_or(StatusCode::NOT_FOUND)?;
|
|
|
|
models::users::User::delete_by_name(&pg_pool, &user.name)
|
|
.await
|
|
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn routes(state: State) -> Router {
|
|
Router::new()
|
|
.route("/users", routing::get(get_all))
|
|
.route("/users/{username}", routing::get(get))
|
|
.route("/users", routing::post(create))
|
|
.route("/users/{username}", routing::put(update))
|
|
.route("/users/{username}", routing::delete(delete))
|
|
.with_state(state)
|
|
}
|