diff --git a/src/lib.rs b/src/lib.rs index 6add084..7783a90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,3 @@ pub mod tables; + +pub mod routes; diff --git a/src/main.rs b/src/main.rs index 84abd17..0eedbcf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,11 @@ -use image::io::Reader; use rocket::fairing::AdHoc; -use rocket::fs::{FileServer, NamedFile}; +use rocket::fs::FileServer; use rocket::http::Status; use rocket::response::content::{self, RawHtml}; -use rocket::response::status; -use rocket::serde::Serialize; use rocket::{Build, Request, Rocket}; use rocket_db_pools::sqlx::pool::PoolConnection; use rocket_db_pools::sqlx::Postgres; -use rocket_db_pools::Connection; use std::fs; -use std::path::Path; -use uuid::Uuid; #[macro_use] extern crate rocket; @@ -20,518 +14,8 @@ use rocket_db_pools::{ Database, }; -use rocket::fs::TempFile; -use rocket::http::CookieJar; -use rocket::serde::{json::Json, Deserialize}; - -use fossil::tables::{Db, Image, LoginStatus, Post, User}; - -#[get("/login")] -fn login_page() -> RawHtml { - RawHtml(fs::read_to_string("/srv/web/login.html").unwrap()) -} - -#[get("/createuser")] -fn createuser_page() -> RawHtml { - RawHtml(fs::read_to_string("/srv/web/createuser.html").unwrap()) -} - -#[get("/uploadimage")] -fn uploadimage() -> RawHtml { - RawHtml(fs::read_to_string("/srv/web/uploadimage.html").unwrap()) -} - -#[get("/myimages")] -fn myimages() -> RawHtml { - RawHtml(fs::read_to_string("/srv/web/myimages.html").unwrap()) -} - -#[derive(Deserialize)] -#[serde(crate = "rocket::serde")] -struct LoginInfo { - username: String, - password: String, -} - -#[post("/createuser", data = "")] -async fn createuser( - mut db: Connection, - info: Json, - cookies: &CookieJar<'_>, -) -> status::Custom { - let token = cookies.get_private("token"); - match token.is_some() { - true => status::Custom( - Status::Forbidden, - "You're already logged in. Log out before trying to create a new account.".to_string(), - ), - false => match User::get_by_username(&mut db, &info.username).await { - Some(_) => status::Custom( - Status::Forbidden, - "Username already taken. Please try again.".to_string(), - ), - None => match User::create(&mut db, &info.username, &info.password).await { - Ok(_) => 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)); - status::Custom(Status::Ok, "Your account has been created and you've been automatically logged in.".to_string()) - } - Err(why) => { - eprintln!("{why:?}"); - status::Custom( - Status::Created, - "Couldn't log you in. Your account has been created, though." - .to_string(), - ) - } - }, - None => status::Custom( - Status::InternalServerError, - "Something went really wrong. I don't know what.".to_string(), - ), - }, - Err(why) => { - format!("Couldn't create user: {}", why); - status::Custom(Status::InternalServerError, format!("{why}")) - } - }, - }, - } -} - -#[derive(Serialize)] -#[serde(crate = "rocket::serde")] -struct GetUser { - username: String, - admin: bool, - make_posts: bool, - comment: bool, -} - -#[get("/account")] -async fn account( - mut db: Connection, - cookies: &CookieJar<'_>, -) -> status::Custom, &'static str>> { - let token = cookies.get_private("token"); - match token { - Some(t) => match User::get_by_token(&mut db, t).await { - Some(user) => status::Custom( - Status::Ok, - Ok(Json(GetUser { - username: user.username, - admin: user.admin, - make_posts: user.make_posts, - comment: user.comment, - })), - ), - None => status::Custom(Status::NotFound, Err("User doesn't exist.")), - }, - None => status::Custom(Status::Unauthorized, Err("Not logged in")), - } -} - -#[post("/login", data = "")] -async fn login( - mut db: Connection, - info: Json, - cookies: &CookieJar<'_>, -) -> status::Custom<&'static str> { - let token = cookies.get_private("token"); - match token { - Some(_) => { - status::Custom(Status::Continue, "already logged in") - } - None => { - match User::get_by_username(&mut db, &info.username).await { - Some(user) => { - if user.password == sha256::digest(&info.password) { - match user.token { - Some(t) => {cookies.add_private(("token", t)); status::Custom(Status::Ok, "Logged in")}, - None => { - match user.set_new_token(&mut db).await { - Ok(t) => { - cookies.add_private(("token", t)); - status::Custom(Status::Ok, "Logged in") - }, - Err(why) => { - eprintln!("{why:?}"); - status::Custom(Status::InternalServerError, "Couldn't generate a token for you, therefore you weren't logged in.") - }, - } - } - } - } else { - status::Custom(Status::Forbidden, "Invalid username or password (to those whining about why it doesn't tell you if the username or password is incorrect, security)") - } - } - None => - status::Custom(Status::Forbidden, "Invalid username or password (to those whining about why it doesn't tell you if the username or password is incorrect, security)") - } - } - } -} - -#[post("/logout")] -async fn logout(cookies: &CookieJar<'_>) -> status::Custom<&'static str> { - match cookies.get_private("token") { - Some(_) => { - cookies.remove_private("token"); - status::Custom(Status::Ok, "Logged out.") - } - None => status::Custom(Status::Unauthorized, "Not logged in."), - } -} - -#[get("/adminpanel")] -async fn adminpanel( - mut db: Connection, - cookies: &CookieJar<'_>, -) -> status::Custom> { - 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 => status::Custom( - Status::Ok, - RawHtml( - fs::read_to_string("/srv/web/adminpanel.html") - .unwrap() - .replace("{{username}}", &user.username[..]), - ), - ), - false => status::Custom( - Status::Unauthorized, - RawHtml(fs::read_to_string("/srv/web/invalidperms.html").unwrap()), - ), - }, - None => status::Custom( - Status::Unauthorized, - RawHtml( - fs::read_to_string("/srv/web/error.html") - .unwrap() - .replace("{{errorcode}}", "401"), - ), - ), - }, - None => status::Custom( - Status::Unauthorized, - 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, - cookies: &CookieJar<'_>, -) -> status::Custom>> { - 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, &username).await { - Some(user) => status::Custom( - Status::Ok, - Json(Ok(Perms { - admin: user.admin, - make_posts: user.make_posts, - comment: user.comment, - })), - ), - None => status::Custom(Status::NotFound, Json(Err("User doesn't exist"))), - }, - false => status::Custom( - Status::Unauthorized, - Json(Err("You don't have the permission to do this")), - ), - }, - None => status::Custom(Status::Unauthorized, Json(Err("Invalid token"))), - }, - None => status::Custom(Status::Unauthorized, Json(Err("Not logged in"))), - } -} - -#[derive(Deserialize)] -#[serde(crate = "rocket::serde")] -struct TogglePerms { - perm: String, - value: bool, - username: String, -} - -#[post("/toggleperms", data = "")] -async fn toggleperms( - mut db: Connection, - info: Json, - cookies: &CookieJar<'_>, -) -> status::Custom { - 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) => { - match toggled_user.username == user.username && info.perm == "admin" - { - true => status::Custom( - Status::Forbidden, - "You can't change your own admin status".to_string(), - ), - false => { - let admin_username = std::env::var("ADMIN_USERNAME") - .expect("set ADMIN_USERNAME env var"); - match toggled_user.username == admin_username { - true => status::Custom( - Status::Forbidden, - "You can't change the system admin's perms." - .to_string(), - ), - false => { - match info.perm == "admin" - && user.username != admin_username - { - true => status::Custom( - Status::Forbidden, - "You can't change other people's admin status." - .to_string(), - ), - false => { - match toggled_user.set_role(&mut db,&info.perm,&info.value).await { - Ok(_) => status::Custom(Status::Ok, "Done".to_string()), - Err(why) => status::Custom(Status::InternalServerError, format!( - "Couldn't update the user's role: {why}" - )), - } - } - } - } - } - } - } - } - None => status::Custom( - Status::NotFound, - "The user you're trying to toggle perms for doesn't exist." - .to_string(), - ), - }, - false => { - status::Custom(Status::Unauthorized, "You aren't an admin.".to_string()) - } - } - } - None => status::Custom(Status::Unauthorized, "Invalid login token".to_string()), - } - } - None => status::Custom(Status::Unauthorized, "Not logged in".to_string()), - } -} - -#[get("/images/")] -async fn get_image( - image: String, - mut db: Connection, -) -> Result> { - let mut split = image.split('.').collect::>(); - let format = split.pop().unwrap(); - let image = split.join("."); - - match Image::get_by_uuid(&mut db, &image).await { - Ok(_) => match Reader::open(format!("/srv/images/{image}.png")) { - Ok(i) => match i.decode() { - Ok(img) => { - let encoder = match format { - "jpg" => image::ImageFormat::Jpeg, - "jpeg" => image::ImageFormat::Jpeg, - "png" => image::ImageFormat::Png, - _ => { - panic!("invalid format") - } - }; - - img.save_with_format(format!("/tmp/{image}.{format}"), encoder) - .unwrap(); - let file = NamedFile::open(Path::new(&format!("/tmp/{image}.{format}"))) - .await - .unwrap(); - fs::remove_file(format!("/tmp/{image}.{format}")).unwrap_or(()); - - Ok(file) - } - Err(why) => Err(match &why.to_string()[..] { - "Format error decoding Png: Invalid PNG signature." => { - status::Custom(Status::NotAcceptable, "That file isn't an image.") - } - _ => status::Custom(Status::InternalServerError, "Unknown decoding error"), - }), - }, - Err(_) => Err(status::Custom( - Status::InternalServerError, - "Unknown decoding error", - )), - }, - Err(_) => Err(status::Custom( - Status::InternalServerError, - "File not found", - )), - } -} - -#[post("/upload", format = "image/png", data = "")] -async fn upload( - mut file: TempFile<'_>, - cookies: &CookieJar<'_>, - mut db: Connection, -) -> status::Custom { - let uuid = Uuid::new_v4().to_string(); - - // login & perms check - match User::login_status(&mut db, cookies).await { - LoginStatus::LoggedIn(user) => { - match user.make_posts || user.admin { - // image validation check - true => { - match file.copy_to(format!("/srv/tmpimages/{uuid}.png")).await { - Ok(_) => { - // validate that it is, in fact, an image - match Reader::open(format!("/srv/tmpimages/{uuid}.png")) { - Ok(_) => { - match file.copy_to(format!("/srv/images/{uuid}.png")).await { - Ok(_) => match Image::create(&mut db, &uuid, user).await { - Ok(_) => { - match fs::remove_file(format!( - "/srv/tmpimages/{uuid}.png" - )) { - Ok(_) => status::Custom(Status::Created, uuid), - Err(why) => { - eprintln!("couldn't clear image from tmpimages: {why:?}"); - status::Custom(Status::Ok, uuid) - } - } - } - Err(why) => { - eprintln!("{why:?}"); - status::Custom( - Status::InternalServerError, - "Couldn't save to DB".to_string(), - ) - } - }, - Err(_) => status::Custom( - Status::InternalServerError, - "Couldn't copy file to final location".to_string(), - ), - } - } - Err(_) => status::Custom( - Status::Forbidden, - "File isn't an image".to_string(), - ), - } - } - Err(_) => status::Custom( - Status::InternalServerError, - "Couldn't save temporary file".to_string(), - ), - } - } - false => status::Custom( - Status::Unauthorized, - "You don't have the right permissions for that".to_string(), - ), - } - } - LoginStatus::InvalidToken => { - status::Custom(Status::Unauthorized, "Invalid login token".to_string()) - } - LoginStatus::NotLoggedIn => { - status::Custom(Status::Unauthorized, "Not logged in".to_string()) - } - } -} - -#[delete("/images/")] -pub async fn delete_image( - mut db: Connection, - cookies: &CookieJar<'_>, - uuid: String, -) -> status::Custom<&'static str> { - match User::login_status(&mut db, cookies).await { - LoginStatus::LoggedIn(user) => { - match Image::is_owned_by(&mut db, &uuid, &user.username).await { - Ok(b) => match b { - // yeah this is jank but fuck types i don't want to figure that out - true => delimg(&mut db, uuid).await, - false => match user.admin { - true => delimg(&mut db, uuid).await, - false => status::Custom(Status::Unauthorized, "You don't own that image"), - }, - }, - Err(_) => status::Custom(Status::NotFound, "Couldn't get image"), - } - } - LoginStatus::InvalidToken => status::Custom(Status::Unauthorized, "Invalid login token"), - LoginStatus::NotLoggedIn => status::Custom(Status::Unauthorized, "Not logged in"), - } -} - -async fn delimg(db: &mut Connection, uuid: String) -> status::Custom<&'static str> { - match Image::delete(db, &uuid).await { - Ok(_) => match fs::remove_file(format!("/srv/images/{uuid}.png")) { - Ok(_) => status::Custom(Status::Ok, "Image deleted"), - Err(why) => { - eprintln!("{why:?}"); - status::Custom( - Status::ImATeapot, - "Image deleted from database but not filesystem", - ) - } - }, - Err(_) => status::Custom(Status::InternalServerError, "Couldn't delete the image"), - } -} - -#[get("/images/by-user/")] -pub async fn get_images_by_username( - mut db: Connection, - cookies: &CookieJar<'_>, - username: String, -) -> Result>, 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 || user.username == username { - true => match Image::get_by_username(&mut db, &username).await { - Ok(images) => Ok(Json::from( - images.into_iter().map(|i| i.uuid).collect::>(), - )), - Err(why) => { - eprintln!("{why:?}"); - Err("Couldn't get that user's images".to_string()) - } - }, - false => Err("You don't have permission to do this".to_string()), - }, - None => Err("Invalid login token".to_string()), - }, - None => Err("Not logged in".to_string()), - } -} +use fossil::routes; +use fossil::tables::{Db, Post}; #[catch(default)] fn default_catcher(status: Status, _: &Request) -> RawHtml { @@ -657,23 +141,26 @@ async fn main() { .mount( "/", routes![ - login_page, - login, - logout, - createuser, - createuser_page, - account, - adminpanel, - toggleperms, - get_image, - upload, - uploadimage, - delete_image, - get_images_by_username, - myimages + // gets from /web + routes::web::login_page, + routes::web::uploadimage, + routes::web::createuser_page, + routes::web::myimages, + // user related stuff + routes::users::login, + routes::users::logout, + routes::users::createuser, + routes::users::account, + routes::users::adminpanel, + routes::users::toggleperms, + // image related stuff + routes::images::get_image, + routes::images::upload, + routes::images::delete_image, + routes::images::get_images_by_username, ], ) - .mount("/api", routes![api_perms]) + .mount("/api", routes![routes::users::api_perms]) .mount("/css", FileServer::from("/srv/web/css")) .register("/", catchers![default_catcher]) .launch() diff --git a/src/routes/images.rs b/src/routes/images.rs new file mode 100644 index 0000000..4e626e0 --- /dev/null +++ b/src/routes/images.rs @@ -0,0 +1,204 @@ +use crate::tables::{Db, Image, LoginStatus, User}; +use image::io::Reader; +use rocket::fs::NamedFile; +use rocket::fs::TempFile; +use rocket::http::CookieJar; +use rocket::http::Status; +use rocket::response::status; +use rocket::serde::json::Json; +use rocket::{delete, get, post}; +use rocket_db_pools::Connection; +use std::fs; +use std::path::Path; +use uuid::Uuid; + +#[get("/images/")] +pub async fn get_image( + image: String, + mut db: Connection, +) -> Result> { + let mut split = image.split('.').collect::>(); + let format = split.pop().unwrap(); + let image = split.join("."); + + match Image::get_by_uuid(&mut db, &image).await { + Ok(_) => match Reader::open(format!("/srv/images/{image}.png")) { + Ok(i) => match i.decode() { + Ok(img) => { + let encoder = match format { + "jpg" => image::ImageFormat::Jpeg, + "jpeg" => image::ImageFormat::Jpeg, + "png" => image::ImageFormat::Png, + _ => { + panic!("invalid format") + } + }; + + img.save_with_format(format!("/tmp/{image}.{format}"), encoder) + .unwrap(); + let file = NamedFile::open(Path::new(&format!("/tmp/{image}.{format}"))) + .await + .unwrap(); + fs::remove_file(format!("/tmp/{image}.{format}")).unwrap_or(()); + + Ok(file) + } + Err(why) => Err(match &why.to_string()[..] { + "Format error decoding Png: Invalid PNG signature." => { + status::Custom(Status::NotAcceptable, "That file isn't an image.") + } + _ => status::Custom(Status::InternalServerError, "Unknown decoding error"), + }), + }, + Err(_) => Err(status::Custom( + Status::InternalServerError, + "Unknown decoding error", + )), + }, + Err(_) => Err(status::Custom( + Status::InternalServerError, + "File not found", + )), + } +} + +#[post("/upload", format = "image/png", data = "")] +pub async fn upload( + mut file: TempFile<'_>, + cookies: &CookieJar<'_>, + mut db: Connection, +) -> status::Custom { + let uuid = Uuid::new_v4().to_string(); + + // login & perms check + match User::login_status(&mut db, cookies).await { + LoginStatus::LoggedIn(user) => { + match user.make_posts || user.admin { + // image validation check + true => { + match file.copy_to(format!("/srv/tmpimages/{uuid}.png")).await { + Ok(_) => { + // validate that it is, in fact, an image + match Reader::open(format!("/srv/tmpimages/{uuid}.png")) { + Ok(_) => { + match file.copy_to(format!("/srv/images/{uuid}.png")).await { + Ok(_) => match Image::create(&mut db, &uuid, user).await { + Ok(_) => { + match fs::remove_file(format!( + "/srv/tmpimages/{uuid}.png" + )) { + Ok(_) => status::Custom(Status::Created, uuid), + Err(why) => { + eprintln!("couldn't clear image from tmpimages: {why:?}"); + status::Custom(Status::Ok, uuid) + } + } + } + Err(why) => { + eprintln!("{why:?}"); + status::Custom( + Status::InternalServerError, + "Couldn't save to DB".to_string(), + ) + } + }, + Err(_) => status::Custom( + Status::InternalServerError, + "Couldn't copy file to final location".to_string(), + ), + } + } + Err(_) => status::Custom( + Status::Forbidden, + "File isn't an image".to_string(), + ), + } + } + Err(_) => status::Custom( + Status::InternalServerError, + "Couldn't save temporary file".to_string(), + ), + } + } + false => status::Custom( + Status::Unauthorized, + "You don't have the right permissions for that".to_string(), + ), + } + } + LoginStatus::InvalidToken => { + status::Custom(Status::Unauthorized, "Invalid login token".to_string()) + } + LoginStatus::NotLoggedIn => { + status::Custom(Status::Unauthorized, "Not logged in".to_string()) + } + } +} + +#[delete("/images/")] +pub async fn delete_image( + mut db: Connection, + cookies: &CookieJar<'_>, + uuid: String, +) -> status::Custom<&'static str> { + match User::login_status(&mut db, cookies).await { + LoginStatus::LoggedIn(user) => { + match Image::is_owned_by(&mut db, &uuid, &user.username).await { + Ok(b) => match b { + // yeah this is jank but fuck types i don't want to figure that out + true => delimg(&mut db, uuid).await, + false => match user.admin { + true => delimg(&mut db, uuid).await, + false => status::Custom(Status::Unauthorized, "You don't own that image"), + }, + }, + Err(_) => status::Custom(Status::NotFound, "Couldn't get image"), + } + } + LoginStatus::InvalidToken => status::Custom(Status::Unauthorized, "Invalid login token"), + LoginStatus::NotLoggedIn => status::Custom(Status::Unauthorized, "Not logged in"), + } +} + +pub async fn delimg(db: &mut Connection, uuid: String) -> status::Custom<&'static str> { + match Image::delete(db, &uuid).await { + Ok(_) => match fs::remove_file(format!("/srv/images/{uuid}.png")) { + Ok(_) => status::Custom(Status::Ok, "Image deleted"), + Err(why) => { + eprintln!("{why:?}"); + status::Custom( + Status::ImATeapot, + "Image deleted from database but not filesystem", + ) + } + }, + Err(_) => status::Custom(Status::InternalServerError, "Couldn't delete the image"), + } +} + +#[get("/images/by-user/")] +pub async fn get_images_by_username( + mut db: Connection, + cookies: &CookieJar<'_>, + username: String, +) -> Result>, 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 || user.username == username { + true => match Image::get_by_username(&mut db, &username).await { + Ok(images) => Ok(Json::from( + images.into_iter().map(|i| i.uuid).collect::>(), + )), + Err(why) => { + eprintln!("{why:?}"); + Err("Couldn't get that user's images".to_string()) + } + }, + false => Err("You don't have permission to do this".to_string()), + }, + None => Err("Invalid login token".to_string()), + }, + None => Err("Not logged in".to_string()), + } +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs new file mode 100644 index 0000000..6bf2a4e --- /dev/null +++ b/src/routes/mod.rs @@ -0,0 +1,3 @@ +pub mod images; +pub mod users; +pub mod web; diff --git a/src/routes/users.rs b/src/routes/users.rs new file mode 100644 index 0000000..827cf88 --- /dev/null +++ b/src/routes/users.rs @@ -0,0 +1,303 @@ +use rocket::http::Status; +use rocket::response::content::RawHtml; +use rocket::response::status; +use rocket::serde::Serialize; +use rocket_db_pools::Connection; +use std::fs; + +use rocket::http::CookieJar; +use rocket::serde::{json::Json, Deserialize}; + +use crate::tables::{Db, User}; +use rocket::{get, post}; + +#[derive(Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct LoginInfo { + pub username: String, + pub password: String, +} + +#[post("/createuser", data = "")] +pub async fn createuser( + mut db: Connection, + info: Json, + cookies: &CookieJar<'_>, +) -> status::Custom { + let token = cookies.get_private("token"); + match token.is_some() { + true => status::Custom( + Status::Forbidden, + "You're already logged in. Log out before trying to create a new account.".to_string(), + ), + false => match User::get_by_username(&mut db, &info.username).await { + Some(_) => status::Custom( + Status::Forbidden, + "Username already taken. Please try again.".to_string(), + ), + None => match User::create(&mut db, &info.username, &info.password).await { + Ok(_) => 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)); + status::Custom(Status::Ok, "Your account has been created and you've been automatically logged in.".to_string()) + } + Err(why) => { + eprintln!("{why:?}"); + status::Custom( + Status::Created, + "Couldn't log you in. Your account has been created, though." + .to_string(), + ) + } + }, + None => status::Custom( + Status::InternalServerError, + "Something went really wrong. I don't know what.".to_string(), + ), + }, + Err(why) => { + format!("Couldn't create user: {}", why); + status::Custom(Status::InternalServerError, format!("{why}")) + } + }, + }, + } +} + +#[derive(Serialize)] +#[serde(crate = "rocket::serde")] +pub struct GetUser { + username: String, + admin: bool, + make_posts: bool, + comment: bool, +} + +#[get("/account")] +pub async fn account( + mut db: Connection, + cookies: &CookieJar<'_>, +) -> status::Custom, &'static str>> { + let token = cookies.get_private("token"); + match token { + Some(t) => match User::get_by_token(&mut db, t).await { + Some(user) => status::Custom( + Status::Ok, + Ok(Json(GetUser { + username: user.username, + admin: user.admin, + make_posts: user.make_posts, + comment: user.comment, + })), + ), + None => status::Custom(Status::NotFound, Err("User doesn't exist.")), + }, + None => status::Custom(Status::Unauthorized, Err("Not logged in")), + } +} + +#[post("/login", data = "")] +pub async fn login( + mut db: Connection, + info: Json, + cookies: &CookieJar<'_>, +) -> status::Custom<&'static str> { + let token = cookies.get_private("token"); + match token { + Some(_) => { + status::Custom(Status::Continue, "already logged in") + } + None => { + match User::get_by_username(&mut db, &info.username).await { + Some(user) => { + if user.password == sha256::digest(&info.password) { + match user.token { + Some(t) => {cookies.add_private(("token", t)); status::Custom(Status::Ok, "Logged in")}, + None => { + match user.set_new_token(&mut db).await { + Ok(t) => { + cookies.add_private(("token", t)); + status::Custom(Status::Ok, "Logged in") + }, + Err(why) => { + eprintln!("{why:?}"); + status::Custom(Status::InternalServerError, "Couldn't generate a token for you, therefore you weren't logged in.") + }, + } + } + } + } else { + status::Custom(Status::Forbidden, "Invalid username or password (to those whining about why it doesn't tell you if the username or password is incorrect, security)") + } + } + None => + status::Custom(Status::Forbidden, "Invalid username or password (to those whining about why it doesn't tell you if the username or password is incorrect, security)") + } + } + } +} + +#[post("/logout")] +pub async fn logout(cookies: &CookieJar<'_>) -> status::Custom<&'static str> { + match cookies.get_private("token") { + Some(_) => { + cookies.remove_private("token"); + status::Custom(Status::Ok, "Logged out.") + } + None => status::Custom(Status::Unauthorized, "Not logged in."), + } +} + +#[get("/adminpanel")] +pub async fn adminpanel( + mut db: Connection, + cookies: &CookieJar<'_>, +) -> status::Custom> { + 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 => status::Custom( + Status::Ok, + RawHtml( + fs::read_to_string("/srv/web/adminpanel.html") + .unwrap() + .replace("{{username}}", &user.username[..]), + ), + ), + false => status::Custom( + Status::Unauthorized, + RawHtml(fs::read_to_string("/srv/web/invalidperms.html").unwrap()), + ), + }, + None => status::Custom( + Status::Unauthorized, + RawHtml( + fs::read_to_string("/srv/web/error.html") + .unwrap() + .replace("{{errorcode}}", "401"), + ), + ), + }, + None => status::Custom( + Status::Unauthorized, + RawHtml(fs::read_to_string("/srv/web/invalidperms.html").unwrap()), + ), + } +} + +#[derive(Deserialize, Serialize)] +#[serde(crate = "rocket::serde")] +pub struct Perms { + admin: bool, + make_posts: bool, + comment: bool, +} +#[get("/perms/")] +pub async fn api_perms( + mut db: Connection, + username: String, + cookies: &CookieJar<'_>, +) -> status::Custom>> { + 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, &username).await { + Some(user) => status::Custom( + Status::Ok, + Json(Ok(Perms { + admin: user.admin, + make_posts: user.make_posts, + comment: user.comment, + })), + ), + None => status::Custom(Status::NotFound, Json(Err("User doesn't exist"))), + }, + false => status::Custom( + Status::Unauthorized, + Json(Err("You don't have the permission to do this")), + ), + }, + None => status::Custom(Status::Unauthorized, Json(Err("Invalid token"))), + }, + None => status::Custom(Status::Unauthorized, Json(Err("Not logged in"))), + } +} + +#[derive(Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct TogglePerms { + perm: String, + value: bool, + username: String, +} + +#[post("/toggleperms", data = "")] +pub async fn toggleperms( + mut db: Connection, + info: Json, + cookies: &CookieJar<'_>, +) -> status::Custom { + 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) => { + match toggled_user.username == user.username && info.perm == "admin" + { + true => status::Custom( + Status::Forbidden, + "You can't change your own admin status".to_string(), + ), + false => { + let admin_username = std::env::var("ADMIN_USERNAME") + .expect("set ADMIN_USERNAME env var"); + match toggled_user.username == admin_username { + true => status::Custom( + Status::Forbidden, + "You can't change the system admin's perms." + .to_string(), + ), + false => { + match info.perm == "admin" + && user.username != admin_username + { + true => status::Custom( + Status::Forbidden, + "You can't change other people's admin status." + .to_string(), + ), + false => { + match toggled_user.set_role(&mut db,&info.perm,&info.value).await { + Ok(_) => status::Custom(Status::Ok, "Done".to_string()), + Err(why) => status::Custom(Status::InternalServerError, format!( + "Couldn't update the user's role: {why}" + )), + } + } + } + } + } + } + } + } + None => status::Custom( + Status::NotFound, + "The user you're trying to toggle perms for doesn't exist." + .to_string(), + ), + }, + false => { + status::Custom(Status::Unauthorized, "You aren't an admin.".to_string()) + } + } + } + None => status::Custom(Status::Unauthorized, "Invalid login token".to_string()), + } + } + None => status::Custom(Status::Unauthorized, "Not logged in".to_string()), + } +} diff --git a/src/routes/web.rs b/src/routes/web.rs new file mode 100644 index 0000000..f4985c0 --- /dev/null +++ b/src/routes/web.rs @@ -0,0 +1,22 @@ +use rocket::{get, response::content::RawHtml}; +use std::fs; + +#[get("/login")] +pub fn login_page() -> RawHtml { + RawHtml(fs::read_to_string("/srv/web/login.html").unwrap()) +} + +#[get("/createuser")] +pub fn createuser_page() -> RawHtml { + RawHtml(fs::read_to_string("/srv/web/createuser.html").unwrap()) +} + +#[get("/uploadimage")] +pub fn uploadimage() -> RawHtml { + RawHtml(fs::read_to_string("/srv/web/uploadimage.html").unwrap()) +} + +#[get("/myimages")] +pub fn myimages() -> RawHtml { + RawHtml(fs::read_to_string("/srv/web/myimages.html").unwrap()) +}