monorepo/visions/server/src/handlers/user_management.rs

157 lines
3.9 KiB
Rust

use axum::{http::HeaderMap, Json};
use futures::Future;
use result_extended::{error, ok, return_error, ResultExt};
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::{
core::{AuthResponse, Core},
database::{SessionId, UserId},
types::{AppError, FatalError, User},
};
use super::UserOverview;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[typeshare]
pub struct AuthRequest {
pub username: String,
pub password: String,
}
#[derive(Deserialize, Serialize)]
#[typeshare]
pub struct CreateUserRequest {
pub username: String,
}
#[derive(Deserialize, Serialize)]
#[typeshare]
pub struct SetPasswordRequest {
pub password_1: String,
pub password_2: String,
}
#[derive(Deserialize, Serialize)]
#[typeshare]
pub struct SetAdminPasswordRequest {
pub password: String,
}
async fn check_session(
core: &Core,
headers: HeaderMap,
) -> ResultExt<Option<User>, AppError, FatalError> {
match headers.get("Authorization") {
Some(token) => {
println!("check_session: {:?}", token);
match token
.to_str()
.unwrap()
.split(" ")
.collect::<Vec<&str>>()
.as_slice()
{
[_schema, token] => core.session(&SessionId::from(token.to_owned())).await,
_ => error(AppError::BadRequest),
}
}
None => ok(None),
}
}
pub async fn auth_required<F, A, Fut>(
core: Core,
headers: HeaderMap,
f: F,
) -> ResultExt<A, AppError, FatalError>
where
F: FnOnce(User) -> Fut,
Fut: Future<Output = ResultExt<A, AppError, FatalError>>,
{
match return_error!(check_session(&core, headers).await) {
Some(user) => f(user).await,
None => error(AppError::AuthFailed),
}
}
pub async fn admin_required<F, A, Fut>(
core: Core,
headers: HeaderMap,
f: F,
) -> ResultExt<A, AppError, FatalError>
where
F: FnOnce(User) -> Fut,
Fut: Future<Output = ResultExt<A, AppError, FatalError>>,
{
match return_error!(check_session(&core, headers).await) {
Some(user) => {
if user.admin {
f(user).await
} else {
error(AppError::PermissionDenied)
}
}
None => error(AppError::AuthFailed),
}
}
pub async fn check_password(
core: Core,
req: Json<AuthRequest>,
) -> ResultExt<AuthResponse, AppError, FatalError> {
let Json(AuthRequest { username, password }) = req;
core.auth(&username, &password).await
}
pub async fn get_user(
core: Core,
headers: HeaderMap,
user_id: Option<UserId>,
) -> ResultExt<Option<UserOverview>, AppError, FatalError> {
auth_required(core.clone(), headers, |user| async move {
match user_id {
Some(user_id) => core.user(user_id).await,
None => core.user(user.id).await,
}
.map(|maybe_user| maybe_user.map(|user| UserOverview::from(user)))
})
.await
}
pub async fn get_users(
core: Core,
headers: HeaderMap,
) -> ResultExt<Vec<UserOverview>, AppError, FatalError> {
auth_required(core.clone(), headers, |_user| async move {
core.list_users().await.map(|users| users.into_iter().map(|user| UserOverview::from(user)).collect::<Vec<UserOverview>>())
})
.await
}
pub async fn create_user(
core: Core,
headers: HeaderMap,
req: CreateUserRequest,
) -> ResultExt<UserId, AppError, FatalError> {
admin_required(core.clone(), headers, |_admin| async {
core.create_user(&req.username).await
})
.await
}
pub async fn set_password(
core: Core,
headers: HeaderMap,
req: SetPasswordRequest,
) -> ResultExt<(), AppError, FatalError> {
auth_required(core.clone(), headers, |user| async {
if req.password_1 == req.password_2 {
core.set_password(user.id, req.password_1).await
} else {
error(AppError::BadRequest)
}
})
.await
}