start working on admin panel
This commit is contained in:
parent
8e1d34595a
commit
2fb3edeb40
|
@ -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",
|
||||
|
|
|
@ -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"]}
|
||||
|
|
138
src/main.rs
138
src/main.rs
|
@ -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;
|
||||
|
@ -50,19 +51,19 @@ async fn createuser(
|
|||
match User::get_by_username(&mut db, &info.username).await {
|
||||
Some(_) => "Username already taken. Please try again.",
|
||||
None => {
|
||||
User::create(&mut db, &info.username, &info.password).await;
|
||||
match User::get_by_username(&mut db, &info.username).await {
|
||||
Some(user) => match user.set_new_token(&mut db).await {
|
||||
Ok(t) => {
|
||||
cookies.add_private(("token", t));
|
||||
"Your account has been created and you've been automatically logged in."
|
||||
User::create(&mut db, &info.username, &info.password).await;
|
||||
match User::get_by_username(&mut db, &info.username).await {
|
||||
Some(user) => match user.set_new_token(&mut db).await {
|
||||
Ok(t) => {
|
||||
cookies.add_private(("token", t));
|
||||
"Your account has been created and you've been automatically logged in."
|
||||
},
|
||||
Err(why) => {
|
||||
eprintln!("{why:?}");
|
||||
"Couldn't log you in. Your account has been created, though."
|
||||
},
|
||||
Err(why) => {
|
||||
eprintln!("{why:?}");
|
||||
"Couldn't log you in. Your account has been created, though."
|
||||
},
|
||||
},
|
||||
None => "Something went really wrong. I don't know what."
|
||||
None => "Something went really wrong. I don't know what."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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>
|
|
@ -1,2 +1,3 @@
|
|||
:root {
|
||||
.toggle-form {
|
||||
display: none
|
||||
}
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue