start working on admin panel
This commit is contained in:
parent
8e1d34595a
commit
2fb3edeb40
|
@ -170,9 +170,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.5"
|
version = "0.21.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
|
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64ct"
|
name = "base64ct"
|
||||||
|
@ -517,6 +517,7 @@ dependencies = [
|
||||||
name = "fossil"
|
name = "fossil"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64",
|
||||||
"rand",
|
"rand",
|
||||||
"rand_hc",
|
"rand_hc",
|
||||||
"rocket",
|
"rocket",
|
||||||
|
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
base64 = "0.21.7"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rand_hc = "0.3.2"
|
rand_hc = "0.3.2"
|
||||||
rocket = {version="0.5.0",features=["secrets","json"]}
|
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::fs::FileServer;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::response::content::{self, RawHtml};
|
use rocket::response::content::{self, RawHtml};
|
||||||
|
use rocket::serde::Serialize;
|
||||||
use rocket::{Build, Request, Rocket};
|
use rocket::{Build, Request, Rocket};
|
||||||
use rocket_db_pools::sqlx::pool::PoolConnection;
|
use rocket_db_pools::sqlx::pool::PoolConnection;
|
||||||
use rocket_db_pools::sqlx::Postgres;
|
use rocket_db_pools::sqlx::Postgres;
|
||||||
|
@ -50,19 +51,19 @@ async fn createuser(
|
||||||
match User::get_by_username(&mut db, &info.username).await {
|
match User::get_by_username(&mut db, &info.username).await {
|
||||||
Some(_) => "Username already taken. Please try again.",
|
Some(_) => "Username already taken. Please try again.",
|
||||||
None => {
|
None => {
|
||||||
User::create(&mut db, &info.username, &info.password).await;
|
User::create(&mut db, &info.username, &info.password).await;
|
||||||
match User::get_by_username(&mut db, &info.username).await {
|
match User::get_by_username(&mut db, &info.username).await {
|
||||||
Some(user) => match user.set_new_token(&mut db).await {
|
Some(user) => match user.set_new_token(&mut db).await {
|
||||||
Ok(t) => {
|
Ok(t) => {
|
||||||
cookies.add_private(("token", t));
|
cookies.add_private(("token", t));
|
||||||
"Your account has been created and you've been automatically logged in."
|
"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");
|
let token = cookies.get_private("token");
|
||||||
match token {
|
match token {
|
||||||
Some(t) => {
|
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),
|
Some(user) => format!("Username: {}\nAdmin: {}\nMake Posts: {}\nComment: {}", user.username, user.admin, user.make_posts, user.comment),
|
||||||
None => "User doesn't exist.".to_string()
|
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)]
|
#[catch(default)]
|
||||||
fn default_catcher(status: Status, _: &Request) -> RawHtml<String> {
|
fn default_catcher(status: Status, _: &Request) -> RawHtml<String> {
|
||||||
content::RawHtml(
|
content::RawHtml(
|
||||||
|
@ -205,8 +316,9 @@ async fn main() {
|
||||||
.attach(AdHoc::on_ignite("DB Migrations", migrate))
|
.attach(AdHoc::on_ignite("DB Migrations", migrate))
|
||||||
.mount(
|
.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"))
|
.mount("/css", FileServer::from("/srv/web/css"))
|
||||||
.register("/", catchers![default_catcher])
|
.register("/", catchers![default_catcher])
|
||||||
.launch()
|
.launch()
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
|
use base64::{engine::general_purpose, Engine};
|
||||||
use rand::{RngCore, SeedableRng};
|
use rand::{RngCore, SeedableRng};
|
||||||
|
use rocket::http::Cookie;
|
||||||
use rocket_db_pools::sqlx::Executor;
|
use rocket_db_pools::sqlx::Executor;
|
||||||
use rocket_db_pools::Connection;
|
use rocket_db_pools::Connection;
|
||||||
use sqlx::FromRow;
|
|
||||||
|
|
||||||
use rocket_db_pools::{
|
use rocket_db_pools::{
|
||||||
sqlx::{self, PgPool, Row},
|
sqlx::{self, PgPool, Row},
|
||||||
Database,
|
Database,
|
||||||
};
|
};
|
||||||
|
use sqlx::FromRow;
|
||||||
|
|
||||||
#[derive(Database)]
|
#[derive(Database)]
|
||||||
#[database("fossil_postgres")]
|
#[database("fossil_postgres")]
|
||||||
|
@ -64,11 +65,7 @@ pub struct User {
|
||||||
pub make_posts: bool,
|
pub make_posts: bool,
|
||||||
pub comment: bool,
|
pub comment: bool,
|
||||||
}
|
}
|
||||||
pub enum UserRole {
|
|
||||||
Admin,
|
|
||||||
MakePosts,
|
|
||||||
Comment,
|
|
||||||
}
|
|
||||||
impl User {
|
impl User {
|
||||||
pub async fn create(db: &mut Connection<Db>, username: &String, password: &String) {
|
pub async fn create(db: &mut Connection<Db>, username: &String, password: &String) {
|
||||||
match db
|
match db
|
||||||
|
@ -107,9 +104,16 @@ impl User {
|
||||||
Err(_) => None,
|
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
|
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
|
.await
|
||||||
{
|
{
|
||||||
Ok(user) => Some(User {
|
Ok(user) => Some(User {
|
||||||
|
@ -142,10 +146,14 @@ impl User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn set_new_token(&self, db: &mut Connection<Db>) -> Result<String, String> {
|
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_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
|
match db
|
||||||
.execute(
|
.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
|
match db
|
||||||
.fetch_one(
|
.fetch_one(
|
||||||
sqlx::query("UPDATE users SET $1 = $2 WHERE id = $3")
|
sqlx::query("UPDATE users SET $1 = $2 WHERE id = $3")
|
||||||
.bind(match role {
|
.bind(role)
|
||||||
UserRole::Admin => "admin",
|
|
||||||
UserRole::MakePosts => "make_posts",
|
|
||||||
UserRole::Comment => "comment",
|
|
||||||
})
|
|
||||||
.bind(value)
|
.bind(value)
|
||||||
.bind(self.id),
|
.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