fix crash on first startup due to making admin account, and allow creation of new users through a ui

This commit is contained in:
SadlyNotSappho 2024-02-06 14:41:29 -08:00
parent d6c418a899
commit 8e1d34595a
4 changed files with 94 additions and 66 deletions

View File

@ -1,4 +1,5 @@
use rocket::fairing::AdHoc; use rocket::fairing::AdHoc;
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::{Build, Request, Rocket}; use rocket::{Build, Request, Rocket};
@ -15,29 +16,18 @@ use rocket_db_pools::{
}; };
use rocket::serde::{json::Json, Deserialize}; use rocket::serde::{json::Json, Deserialize};
use rocket::{ use rocket::http::CookieJar;
fs::FileServer,
http::CookieJar,
tokio::time::{sleep, Duration},
};
use fossil::tables::{Db, Post, User}; use fossil::tables::{Db, Post, User};
#[get("/")] #[get("/login")]
fn hello() -> RawHtml<String> { fn login_page() -> RawHtml<String> {
content::RawHtml(fs::read_to_string("/srv/web/index.html").unwrap()) content::RawHtml(fs::read_to_string("/srv/web/login.html").unwrap())
// format!("hi!!!")
} }
#[get("/book/<card>/<isbn>")] #[get("/createuser")]
fn get_book(card: String, isbn: String) -> String { fn createuser_page() -> RawHtml<String> {
format!("You're checking out the book {isbn}, with the account {card}.") content::RawHtml(fs::read_to_string("/srv/web/createuser.html").unwrap())
}
#[get("/delay/<time>")]
async fn delay(time: u64) -> String {
sleep(Duration::from_secs(time)).await;
format!("waited for {time} seconds")
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -49,9 +39,7 @@ struct LoginInfo {
#[post("/createuser", data = "<info>")] #[post("/createuser", data = "<info>")]
async fn createuser( async fn createuser(
// this is so fucking jank but it works !!! mut db: Connection<Db>,
db: Connection<Db>,
db2: Connection<Db>,
info: Json<LoginInfo>, info: Json<LoginInfo>,
cookies: &CookieJar<'_>, cookies: &CookieJar<'_>,
) -> &'static str { ) -> &'static str {
@ -59,22 +47,35 @@ async fn createuser(
match token.is_some() { match token.is_some() {
true => "You're already logged in. Log out before trying to create a new account.", true => "You're already logged in. Log out before trying to create a new account.",
false => { false => {
if User::get_by_username(db, &info.username).await.is_some() { match User::get_by_username(&mut db, &info.username).await {
"Username already taken. Please try again." Some(_) => "Username already taken. Please try again.",
} else { None => {
User::create(db2, &info.username, &info.password).await; User::create(&mut db, &info.username, &info.password).await;
"Account created." 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."
},
},
None => "Something went really wrong. I don't know what."
}
}
} }
} }
} }
} }
#[get("/account")] #[get("/account")]
async fn account(db: Connection<Db>, cookies: &CookieJar<'_>) -> String { 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(db, t.to_string().split('=').collect::<Vec<&str>>()[1].to_string() /*GOD I LOVE RUST*/).await { match User::get_by_token(&mut db, t.to_string().split('=').collect::<Vec<&str>>()[1].to_string() /*GOD I LOVE RUST*/).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()
} }
@ -86,20 +87,20 @@ async fn account(db: Connection<Db>, cookies: &CookieJar<'_>) -> String {
} }
#[post("/login", data = "<info>")] #[post("/login", data = "<info>")]
async fn login(db: Connection<Db>, db2: Connection<Db>, info: Json<LoginInfo>, cookies: &CookieJar<'_>) -> String { async fn login(mut db: Connection<Db>, info: Json<LoginInfo>, cookies: &CookieJar<'_>) -> String {
let token = cookies.get_private("token"); let token = cookies.get_private("token");
match token { match token {
Some(_) => { Some(_) => {
"already logged in".to_string() "already logged in".to_string()
} }
None => { None => {
match User::get_by_username(db, &info.username).await { match User::get_by_username(&mut db, &info.username).await {
Some(user) => { Some(user) => {
if user.password == sha256::digest(&info.password) { if user.password == sha256::digest(&info.password) {
match user.token { match user.token {
Some(t) => {cookies.add_private(("token", t)); "Logged in".to_string()}, Some(t) => {cookies.add_private(("token", t)); "Logged in".to_string()},
None => { None => {
match user.set_new_token(db2).await { match user.set_new_token(&mut db).await {
Ok(t) => { Ok(t) => {
cookies.add_private(("token", t)); cookies.add_private(("token", t));
"logged in".to_string() "logged in".to_string()
@ -129,26 +130,6 @@ async fn logout(cookies: &CookieJar<'_>) -> &'static str {
} }
} }
#[get("/dbtest/<id>")]
async fn dbtest(db: Connection<Db>, id: i32) -> Option<String> {
let post = Post::get(db, id).await;
Some(format!(
"ID: {}\nTitle: {}\nBody: {}\nPublished: {}",
post.id, post.title, post.body, post.published
))
}
#[get("/dbcreate/<title>/<body>/<published>")]
async fn dbcreate(
db: Connection<Db>,
title: String,
body: String,
published: bool,
) -> &'static str {
Post::create(db, title, body, published).await;
"created!"
}
#[catch(default)] #[catch(default)]
fn default_catcher(status: Status, _: &Request) -> RawHtml<String> { fn default_catcher(status: Status, _: &Request) -> RawHtml<String> {
content::RawHtml( content::RawHtml(
@ -199,9 +180,7 @@ async fn migrate(rocket: Rocket<Build>) -> Rocket<Build> {
let username = std::env::var("ADMIN_USERNAME").expect("make ADMIN_USERNAME env var"); let username = std::env::var("ADMIN_USERNAME").expect("make ADMIN_USERNAME env var");
let password = sha256::digest(std::env::var("ADMIN_PASSWORD").expect("make ADMIN_PASSWORD env var")); let password = sha256::digest(std::env::var("ADMIN_PASSWORD").expect("make ADMIN_PASSWORD env var"));
eprintln!("{username}\n{password}"); conn.execute(sqlx::query(
conn.fetch_one(sqlx::query(
r#" r#"
INSERT INTO users (username, password, admin) INSERT INTO users (username, password, admin)
VALUES ($1, $2, true); VALUES ($1, $2, true);
@ -226,10 +205,10 @@ async fn main() {
.attach(AdHoc::on_ignite("DB Migrations", migrate)) .attach(AdHoc::on_ignite("DB Migrations", migrate))
.mount( .mount(
"/", "/",
routes![hello, get_book, delay, login, logout, dbtest, dbcreate, createuser, account], routes![login_page, login, logout, createuser, createuser_page, account],
) )
.mount("/css", FileServer::from("/srv/web/css"))
.register("/", catchers![default_catcher]) .register("/", catchers![default_catcher])
.mount("/login", FileServer::from("/srv/web"))
.launch() .launch()
.await .await
.unwrap(); .unwrap();

View File

@ -70,9 +70,9 @@ pub enum UserRole {
Comment, Comment,
} }
impl User { impl User {
pub async fn create(mut db: Connection<Db>, username: &String, password: &String) { pub async fn create(db: &mut Connection<Db>, username: &String, password: &String) {
match db match db
.fetch_all( .execute(
sqlx::query( sqlx::query(
r#" r#"
INSERT INTO users (username, password) INSERT INTO users (username, password)
@ -90,7 +90,7 @@ impl User {
} }
} }
} }
pub async fn get_by_id(mut db: Connection<Db>, id: i32) -> Option<User> { pub async fn get_by_id(db: &mut Connection<Db>, id: i32) -> Option<User> {
match db match db
.fetch_one(sqlx::query("SELECT * FROM users WHERE id = $1;").bind(id)) .fetch_one(sqlx::query("SELECT * FROM users WHERE id = $1;").bind(id))
.await .await
@ -107,7 +107,7 @@ impl User {
Err(_) => None, Err(_) => None,
} }
} }
pub async fn get_by_token(mut db: Connection<Db>, token: String) -> Option<User> { pub async fn get_by_token(db: &mut Connection<Db>, token: String) -> Option<User> {
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))
.await .await
@ -124,7 +124,7 @@ impl User {
Err(_) => None, Err(_) => None,
} }
} }
pub async fn get_by_username(mut db: Connection<Db>, username: &String) -> Option<User> { pub async fn get_by_username(db: &mut Connection<Db>, username: &String) -> Option<User> {
match db match db
.fetch_one(sqlx::query("SELECT * FROM users WHERE username = $1;").bind(username)) .fetch_one(sqlx::query("SELECT * FROM users WHERE username = $1;").bind(username))
.await .await
@ -141,14 +141,14 @@ impl User {
Err(_) => None, Err(_) => None,
} }
} }
pub async fn set_new_token(&self, mut db: 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 = 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 = sha256::digest(format!("{token_start}-{token_end}"));
match db match db
.fetch_one( .execute(
sqlx::query("UPDATE users SET token = $1 WHERE id = $2") sqlx::query("UPDATE users SET token = $1 WHERE id = $2")
.bind(&token) .bind(&token)
.bind(self.id), .bind(self.id),

50
web/createuser.html Normal file
View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>create user</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/createuser/css/style.css" rel="stylesheet">
</head>
<body>
create user
<form action="/createuser" method="post" class="login-form" id="login-form">
<div class="form-test">
<label for="name">Username: </label>
<input type="text" name="username" id="name" required />
</div>
<div class="form-test">
<label for="pass">Password: </label>
<input type="text" name="password" id="password" required />
</div>
<div class="form-test">
<input type="submit" value="Create user" />
</div>
</form>
<button id="logout-button">Log Out</button>
</body>
<script defer>
document.getElementById("login-form").addEventListener("submit", async (event) => {
event.preventDefault();
const username = event.target.username.value;
const password = event.target.password.value;
const token = await fetch("/createuser", {
method: "POST",
header: {"Content-Type": "application/json"},
body: JSON.stringify({username, password})
})
alert(await token.text());
});
document.getElementById("logout-button").addEventListener("click", async (event) => {
event.preventDefault();
const loggedout = await fetch("/logout", {
method: "POST"
});
alert(await loggedout.text());
});
</script>
</html>

View File

@ -26,7 +26,6 @@
<button id="logout-button">Log Out</button> <button id="logout-button">Log Out</button>
</body> </body>
<script defer> <script defer>
console.log("FUCK YEAH JAVASCRIPT TIME BABYYYYYYY");
document.getElementById("login-form").addEventListener("submit", async (event) => { document.getElementById("login-form").addEventListener("submit", async (event) => {
event.preventDefault(); event.preventDefault();
const username = event.target.username.value; const username = event.target.username.value;
@ -37,14 +36,14 @@
header: {"Content-Type": "application/json"}, header: {"Content-Type": "application/json"},
body: JSON.stringify({username, password}) body: JSON.stringify({username, password})
}) })
console.log(await token.text()); alert(await token.text());
}); });
document.getElementById("logout-button").addEventListener("click", async (event) => { document.getElementById("logout-button").addEventListener("click", async (event) => {
event.preventDefault(); event.preventDefault();
const loggedout = await fetch("/logout", { const loggedout = await fetch("/logout", {
method: "POST" method: "POST"
}); });
console.log(await loggedout.text()); alert(await loggedout.text());
}); });
</script> </script>