start working on admin panel

This commit is contained in:
SadlyNotSappho 2024-02-13 12:18:04 -08:00
parent 8e1d34595a
commit 2fb3edeb40
7 changed files with 257 additions and 33 deletions

5
Cargo.lock generated
View File

@ -170,9 +170,9 @@ dependencies = [
[[package]]
name = "base64"
version = "0.21.5"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64ct"
@ -517,6 +517,7 @@ dependencies = [
name = "fossil"
version = "0.1.0"
dependencies = [
"base64",
"rand",
"rand_hc",
"rocket",

View File

@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
base64 = "0.21.7"
rand = "0.8.5"
rand_hc = "0.3.2"
rocket = {version="0.5.0",features=["secrets","json"]}

View File

@ -2,6 +2,7 @@ use rocket::fairing::AdHoc;
use rocket::fs::FileServer;
use rocket::http::Status;
use rocket::response::content::{self, RawHtml};
use rocket::serde::Serialize;
use rocket::{Build, Request, Rocket};
use rocket_db_pools::sqlx::pool::PoolConnection;
use rocket_db_pools::sqlx::Postgres;
@ -75,7 +76,7 @@ async fn account(mut db: Connection<Db>, cookies: &CookieJar<'_>) -> String {
let token = cookies.get_private("token");
match token {
Some(t) => {
match User::get_by_token(&mut db, t.to_string().split('=').collect::<Vec<&str>>()[1].to_string() /*GOD I LOVE RUST*/).await {
match User::get_by_token(&mut db, t).await {
Some(user) => format!("Username: {}\nAdmin: {}\nMake Posts: {}\nComment: {}", user.username, user.admin, user.make_posts, user.comment),
None => "User doesn't exist.".to_string()
}
@ -130,6 +131,116 @@ async fn logout(cookies: &CookieJar<'_>) -> &'static str {
}
}
#[get("/adminpanel")]
async fn adminpanel(mut db: Connection<Db>, cookies: &CookieJar<'_>) -> RawHtml<String> {
let token = cookies.get_private("token");
match token {
Some(t) => {
match User::get_by_token(&mut db, t).await {
Some(user) => match user.admin {
true => {
RawHtml(
fs::read_to_string("/srv/web/adminpanel.html").unwrap()
)
},
false => RawHtml(fs::read_to_string("/srv/web/invalidperms.html").unwrap())
}
None => RawHtml(fs::read_to_string("/srv/web/error.html").unwrap().replace("{{errorcode}}", "498"))
}
},
None => {
RawHtml(fs::read_to_string("/srv/web/invalidperms.html").unwrap())
}
}
}
#[derive(Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
struct ApiPermsResult {
perms: Result<Perms, bool>
}
#[derive(Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
struct Perms{
admin: bool,
make_posts: bool,
comment: bool
}
#[get("/perms/<username>")]
async fn api_perms(mut db: Connection<Db>, username: String) -> Json<ApiPermsResult> {
match User::get_by_username(&mut db, &username).await {
Some(user) => {
Json(ApiPermsResult {
perms: Ok(Perms {
admin: user.admin,
make_posts: user.make_posts,
comment: user.comment
})})
},
None => {
Json(ApiPermsResult { perms: Err(false) })
}
}
}
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct ToggleRole {
perm: String,
value: bool,
username: String
}
#[post("/togglerole", data = "<info>")]
async fn togglerole(
mut db: Connection<Db>,
info: Json<ToggleRole>,
cookies: &CookieJar<'_>,
) -> &'static str {
match cookies.get_private("token") {
Some(t) => {
match User::get_by_token(&mut db, t).await {
Some(user) => {
match user.admin {
true => {
match User::get_by_username(&mut db, &info.username).await {
Some(toggled_user) => {
if toggled_user.username == user.username && info.perm == "admin".to_string() {
"You can't toggle your own admin status"
} else {
let admin_username = std::env::var("ADMIN_USERNAME").expect("set ADMIN_USERNAME env var");
if toggled_user.username == admin_username {
"You can't change the system admin's perms."
} else {
if info.perm == "admin" && user.username != admin_username {
"You can't toggle other people's admin status."
} else {
// how deep is this shit
// i counted. 12.
// NOW we can actually do the thing :D
match toggled_user.set_role(db, &info.perm, &info.value).await {
Ok(_) => "Done",
Err(_) => "Couldn't update the user's role."
}
}
}
}
},
None => "The user you're trying to toggle perms for doesn't exist."
}
},
false => {
"You aren't an admin."
}
}
},
None => "Invalid user"
}
},
None => "Not logged in"
}
}
#[catch(default)]
fn default_catcher(status: Status, _: &Request) -> RawHtml<String> {
content::RawHtml(
@ -205,8 +316,9 @@ async fn main() {
.attach(AdHoc::on_ignite("DB Migrations", migrate))
.mount(
"/",
routes![login_page, login, logout, createuser, createuser_page, account],
routes![login_page, login, logout, createuser, createuser_page, account, adminpanel, togglerole],
)
.mount("/api", routes![api_perms])
.mount("/css", FileServer::from("/srv/web/css"))
.register("/", catchers![default_catcher])
.launch()

View File

@ -1,12 +1,13 @@
use base64::{engine::general_purpose, Engine};
use rand::{RngCore, SeedableRng};
use rocket::http::Cookie;
use rocket_db_pools::sqlx::Executor;
use rocket_db_pools::Connection;
use sqlx::FromRow;
use rocket_db_pools::{
sqlx::{self, PgPool, Row},
Database,
};
use sqlx::FromRow;
#[derive(Database)]
#[database("fossil_postgres")]
@ -64,11 +65,7 @@ pub struct User {
pub make_posts: bool,
pub comment: bool,
}
pub enum UserRole {
Admin,
MakePosts,
Comment,
}
impl User {
pub async fn create(db: &mut Connection<Db>, username: &String, password: &String) {
match db
@ -107,9 +104,16 @@ impl User {
Err(_) => None,
}
}
pub async fn get_by_token(db: &mut Connection<Db>, token: String) -> Option<User> {
pub async fn get_by_token(db: &mut Connection<Db>, token: Cookie<'static>) -> Option<User> {
let to = token.to_string();
let mut fixed_token = to.split('=').collect::<Vec<&str>>();
fixed_token.reverse();
fixed_token.pop();
fixed_token.reverse();
let token_string = fixed_token.join("=");
match db
.fetch_one(sqlx::query("SELECT * FROM users WHERE token = $1;").bind(token))
.fetch_one(sqlx::query("SELECT * FROM users WHERE token = $1;").bind(token_string))
.await
{
Ok(user) => Some(User {
@ -142,10 +146,14 @@ impl User {
}
}
pub async fn set_new_token(&self, db: &mut Connection<Db>) -> Result<String, String> {
let token_end = rand_hc::Hc128Rng::from_entropy().next_u64();
let token_end = format!("{}", rand_hc::Hc128Rng::from_entropy().next_u64());
let token_start = sha256::digest(&self.username);
let token = sha256::digest(format!("{token_start}-{token_end}"));
let token = format!(
"{}-{}",
general_purpose::STANDARD.encode(token_start),
general_purpose::STANDARD.encode(token_end)
);
match db
.execute(
@ -160,15 +168,16 @@ impl User {
}
}
pub async fn set_role(&self, mut db: Connection<Db>, role: UserRole, value: bool) -> Result<String, String> {
pub async fn set_role(
&self,
mut db: Connection<Db>,
role: &String,
value: &bool,
) -> Result<String, String> {
match db
.fetch_one(
sqlx::query("UPDATE users SET $1 = $2 WHERE id = $3")
.bind(match role {
UserRole::Admin => "admin",
UserRole::MakePosts => "make_posts",
UserRole::Comment => "comment",
})
.bind(role)
.bind(value)
.bind(self.id),
)

88
web/adminpanel.html Normal file
View File

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en">
<!--
struct ToggleRole {
perm: String,
value: bool,
username: String
}
-->
<head>
<title>perms toggle</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/css/style.css" rel="stylesheet">
</head>
<body>
<div id="data">
perms toggle
</div>
<form action="/toggleperms" method="post" class="permtoggle-form" id="permtoggle-form">
<div id="username-form">
<label for="name">Username: </label>
<input type="text" name="username" id="username" required />
</div>
<div id="load-button">
<input type="button" value="Load user" id="load-user" />
</div>
<div class="toggle-form">
<label for="admin-toggle">Admin: </label>
<input type="checkbox" value="admin-toggle" id="admin-toggle" />
<br>
<label for="make-post-toggle">Make posts: </label>
<input type="checkbox" value="make-post-toggle" id="make-post-toggle" />
<br>
<label for="comment-toggle">Comment: </label>
<input type="checkbox" value="comment-toggle" id="comment-toggle" />
</div>
</form>
</body>
<script defer>
document.getElementById("permtoggle-form").addEventListener("click", async (event) => {
event.preventDefault();
switch (event.srcElement.id) {
case "load-user": {
let username = document.getElementById("username").value;
const user = JSON.parse(await (await fetch(`/api/perms/${username}`)).text()).perms
if (user.Ok) {
document.querySelector(".toggle-form").style.display = "block"
document.getElementById("username-form").style.display = "none"
document.getElementById("load-button").style.display = "none"
document.getElementById("data").innerHTML = `Username: ${username}`
document.getElementById("admin-toggle").checked = user.Ok.admin
} else {
alert("User doesn't exist.")
}
break
}
// TODO: imlpement these, server handles perm checks
case "admin-toggle": {
// admin toggle
break
}
case "make-post-toggle": {
// make post toggle
break
}
case "comment-toggle": {
// comment toggle
break
}
}
// const user = await fetch("/api/perms/ ")
// const token = await fetch("/login", {
// method: "POST",
// header: {"Content-Type": "application/json"},
// body: JSON.stringify({username, password})
// })
// alert(await token.text());
});
</script>
</html>

View File

@ -1,2 +1,3 @@
:root {
.toggle-form {
display: none
}

12
web/invalidperms.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Invalid Permissions</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="css/style.css" rel="stylesheet">
</head>
<body>
<h1>You don't have permission to view this page.</h1>
</body>
</html>