diff --git a/Cargo.lock b/Cargo.lock index bca33e5..2f4398b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 4183880..9ccac39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"]} diff --git a/src/main.rs b/src/main.rs index c4ba40e..a3b193c 100644 --- a/src/main.rs +++ b/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, 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::>()[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, cookies: &CookieJar<'_>) -> RawHtml { + 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 +} +#[derive(Deserialize, Serialize)] +#[serde(crate = "rocket::serde")] +struct Perms{ + admin: bool, + make_posts: bool, + comment: bool +} +#[get("/perms/")] +async fn api_perms(mut db: Connection, username: String) -> Json { + 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 = "")] +async fn togglerole( + mut db: Connection, + info: Json, + 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 { 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() diff --git a/src/tables.rs b/src/tables.rs index ea097e1..98b01cf 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -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, username: &String, password: &String) { match db @@ -107,9 +104,16 @@ impl User { Err(_) => None, } } - pub async fn get_by_token(db: &mut Connection, token: String) -> Option { + pub async fn get_by_token(db: &mut Connection, token: Cookie<'static>) -> Option { + let to = token.to_string(); + let mut fixed_token = to.split('=').collect::>(); + 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) -> Result { - 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, role: UserRole, value: bool) -> Result { + pub async fn set_role( + &self, + mut db: Connection, + role: &String, + value: &bool, + ) -> Result { 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), ) diff --git a/web/adminpanel.html b/web/adminpanel.html new file mode 100644 index 0000000..9dfd52f --- /dev/null +++ b/web/adminpanel.html @@ -0,0 +1,88 @@ + + + + + + perms toggle + + + + + + +
+ perms toggle +
+
+
+ + +
+
+ +
+
+ + +
+ + +
+ + +
+
+ + + + diff --git a/web/css/style.css b/web/css/style.css index 5a4260f..edaa0c6 100644 --- a/web/css/style.css +++ b/web/css/style.css @@ -1,2 +1,3 @@ -:root { +.toggle-form { + display: none } diff --git a/web/invalidperms.html b/web/invalidperms.html new file mode 100644 index 0000000..71c6836 --- /dev/null +++ b/web/invalidperms.html @@ -0,0 +1,12 @@ + + + + Invalid Permissions + + + + + +

You don't have permission to view this page.

+ +