/* This file is part of jellything (https://codeberg.org/metamuffin/jellything) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2024 metamuffin */ use crate::{ database::DataAcid, routes::ui::{ account::session::AdminSession, error::MyResult, layout::{DynLayoutPage, FlashDisplay, LayoutPage}, }, uri, }; use anyhow::{anyhow, Context}; use jellybase::database::{redb::ReadableTable, Ser, TableExt, T_USER}; use jellycommon::user::{PermissionSet, UserPermission}; use rocket::{form::Form, get, post, FromForm, FromFormField, State}; #[get("/admin/users")] pub fn r_admin_users( _session: AdminSession, database: &State, ) -> MyResult> { user_management(database, None) } fn user_management<'a>( database: &DataAcid, flash: Option>, ) -> MyResult> { // TODO this doesnt scale, pagination! let users = { let txn = database.begin_read()?; let table = txn.open_table(T_USER)?; let i = table .iter()? .map(|a| { let (x, y) = a.unwrap(); (x.value().to_owned(), y.value().0) }) .collect::>(); drop(table); i }; let flash = flash.map(|f| f.map_err(|e| format!("{e:?}"))); Ok(LayoutPage { title: "User management".to_string(), content: markup::new! { h1 { "User Management" } @FlashDisplay { flash: flash.clone() } h2 { "All Users" } ul { @for (_, u) in &users { li { a[href=uri!(r_admin_user(&u.name))] { @format!("{:?}", u.display_name) " (" @u.name ")" } } }} }, ..Default::default() }) } #[get("/admin/user/")] pub fn r_admin_user<'a>( _session: AdminSession, database: &State, name: &'a str, ) -> MyResult> { manage_single_user(database, None, name.to_string()) } fn manage_single_user<'a>( database: &DataAcid, flash: Option>, name: String, ) -> MyResult> { let user = T_USER .get(&database, &*name)? .ok_or(anyhow!("user does not exist"))?; let flash = flash.map(|f| f.map_err(|e| format!("{e:?}"))); Ok(LayoutPage { title: "User management".to_string(), content: markup::new! { h1 { @format!("{:?}", user.display_name) " (" @user.name ")" } a[href=uri!(r_admin_users())] "Back to the User List" @FlashDisplay { flash: flash.clone() } form[method="POST", action=uri!(r_admin_remove_user())] { input[type="text", name="name", value=&user.name, hidden]; input[type="submit", value="Remove user(!)"]; } h2 { "Permissions" } @PermissionDisplay { perms: &user.permissions } form[method="POST", action=uri!(r_admin_user_permission())] { input[type="text", name="name", value=&user.name, hidden]; fieldset.perms { legend { "Permission" } @for p in UserPermission::ALL_ENUMERABLE { label { input[type="radio", name="permission", value=serde_json::to_string(p).unwrap()]; @format!("{p}") } br; } } fieldset.perms { legend { "Permission" } label { input[type="radio", name="action", value="unset"]; "Unset" } br; label { input[type="radio", name="action", value="grant"]; "Grant" } br; label { input[type="radio", name="action", value="revoke"]; "Revoke" } br; } input[type="submit", value="Update"]; } }, ..Default::default() }) } markup::define! { PermissionDisplay<'a>(perms: &'a PermissionSet) { ul { @for (perm,grant) in &perms.0 { @if *grant { li[class="perm-grant"] { @format!("Allow {}", perm) } } else { li[class="perm-revoke"] { @format!("Deny {}", perm) } } }} } } #[derive(FromForm)] pub struct DeleteUser { name: String, } #[derive(FromForm)] pub struct UserPermissionForm { name: String, permission: String, action: GrantState, } #[derive(FromFormField)] pub enum GrantState { Grant, Revoke, Unset, } #[post("/admin/update_user_permission", data = "
")] pub fn r_admin_user_permission( session: AdminSession, database: &State, form: Form, ) -> MyResult> { drop(session); let perm = serde_json::from_str::(&form.permission) .context("parsing provided permission")?; let txn = database.begin_write()?; let mut users = txn.open_table(T_USER)?; let mut user = users .get(&*form.name)? .ok_or(anyhow!("user missing"))? .value() .0; match form.action { GrantState::Grant => drop(user.permissions.0.insert(perm.clone(), true)), GrantState::Revoke => drop(user.permissions.0.insert(perm.clone(), false)), GrantState::Unset => drop(user.permissions.0.remove(&perm)), } users.insert(&*form.name, Ser(user))?; drop(users); txn.commit()?; manage_single_user( database, Some(Ok("Permissions update".into())), form.name.clone(), ) } #[post("/admin/remove_user", data = "")] pub fn r_admin_remove_user( session: AdminSession, database: &State, form: Form, ) -> MyResult> { drop(session); T_USER .remove(&database, form.name.as_str())? .ok_or(anyhow!("user did not exist"))?; user_management(database, Some(Ok("User removed".into()))) }