From 7e9271db726fbd53822695857f6ba2cccc86e944 Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Tue, 21 Jul 2026 16:05:08 +1000 Subject: [PATCH] Logins + sessions working --- client/header_bottom.html | 1 - client/script.js | 42 +++++++++++++++++++--- server/src/main.cpp | 75 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 110 insertions(+), 8 deletions(-) diff --git a/client/header_bottom.html b/client/header_bottom.html index 76669a9..ef8b183 100644 --- a/client/header_bottom.html +++ b/client/header_bottom.html @@ -6,7 +6,6 @@ diff --git a/client/script.js b/client/script.js index 3faf8c4..763c74f 100644 --- a/client/script.js +++ b/client/script.js @@ -18,6 +18,25 @@ async function like(id) { } } +async function post() { + const postbox = document.getElementById('newpost'); + + const formData = new FormData(); + formData.append('post', postbox.value); + + const result = await fetch('/post', { + method: 'POST', + body: formData + }); + + if (result.status < 200 || result.status >= 400) { + alert("you have been silenced by the server and your post did not go through"); + return; + } + + window.location.href = "/"; +} + async function register() { const usernameBox = document.getElementById('username'); const passwordBox = document.getElementById('password'); @@ -48,7 +67,6 @@ async function login() { formData.append('username', usernameBox.value); formData.append('password', passwordBox.value); - console.log(formData); const result = await fetch('/login', { method: 'POST', @@ -57,8 +75,6 @@ async function login() { if (result.status < 200 || result.status >= 400) { alert("for some reason the server hates you and didn't let you log into your account"); - console.log(result.status); - console.log(result.content); return; } @@ -78,13 +94,19 @@ async function checkLoginStatus() { document.addEventListener('DOMContentLoaded', async function() { - if (window.location.pathname == "/" && window.fetch) { + if (window.fetch) { // We know the fetch API exists, so hide the stuff that doesn't rely on it const form = document.getElementById("postbox-form"); + if (!form) { + return; + } form.style.display = 'none'; const postbox = document.getElementById("postbox"); + if (!postbox) { + return; + } const userStatus = await checkLoginStatus(); @@ -98,8 +120,20 @@ document.addEventListener('DOMContentLoaded', async function() { const helperText = document.createElement("p"); helperText.textContent = "You're posting as " + userStatus; postbox.appendChild(helperText); + const textArea = document.createElement("textarea"); + textArea.id = "newpost"; postbox.appendChild(textArea); + + const postButton = document.createElement("button"); + postButton.textContent = "Post"; + postButton.id = "postbutton" + postButton.onclick = post; + postbox.appendChild(postButton); + + const loginLink = document.getElementById("loginLink"); + loginLink.textContent = "hello, " + userStatus + "!"; + loginLink.href = "/profile/" + userStatus; } } diff --git a/server/src/main.cpp b/server/src/main.cpp index 1ea4424..6634cfc 100644 --- a/server/src/main.cpp +++ b/server/src/main.cpp @@ -67,7 +67,7 @@ int main() { }); svr.Get("/script.js", [&script](const httplib::Request& request, httplib::Response& response) { - response.set_content(script, "text/css"); + response.set_content(script, "text/javascript"); }); svr.Get("/login", [&login](const httplib::Request& request, httplib::Response& response) { @@ -93,13 +93,52 @@ int main() { response.set_content("

wrong password lmao

your stupid lol", "text/html"); return; } + } else { + response.status = 400; + response.set_content("

that username doesn't exist

", "text/html"); + return; + } + + std::optional token = database.createNewToken(user->id); + if (!token.has_value()) { + response.status = 500; + response.set_content("

couldn't create a session, sorry

", "text/html"); + return; + } + + // HttpOnly so script.js can't read/leak it, SameSite=Lax so it + // isn't sent on cross-site POSTs (basic CSRF mitigation), + // Max-Age matches the 30 day expiry stored in the DB + response.set_header( + "Set-Cookie", + "session=" + *token + "; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000" + ); + response.set_redirect("/"); + } catch (const std::runtime_error& e) { + response.status = 500; + response.set_content("

there was an error :( it is: " + std::string(e.what()) + "

", "text/html"); + } + }); + + svr.Post("/register", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) { + std::string username = request.form.get_field("username"); + std::string password = request.form.get_field("password"); + + std::lock_guard lock(data_mutex); + + try { + std::optional user = database.getUserByName(username); + + if (user.has_value()) { + response.status = 400; + response.set_content("

that username already exists

", "text/html"); + return; } else { if (username.empty()) { response.status = 400; response.set_content("

hey you can't have an empty username!!!!1!!!1! >:(

", "text/html"); return; } - // register on the fly, same as make_post currently does User newUser{0, username, bcrypt::generateHash(password)}; database.addUser(newUser); user = newUser; @@ -152,6 +191,7 @@ int main() { svr.Get("/posts/:id", [&headerTop, &headerBottom, &footer, &e404, &database, &data_mutex](const httplib::Request& request, httplib::Response& response) { std::string postId = request.path_params.at("id"); + std::lock_guard lock(data_mutex); try { uint64_t postIdNum = std::stoll(postId); std::optional post = database.getPost(postIdNum); @@ -174,6 +214,7 @@ int main() { } }); + // endpoint to be used by HTML forms svr.Post("/make_post", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) { std::string username = request.get_param_value("username"); @@ -213,6 +254,32 @@ int main() { }); + // endpoint to be used in Javascript + svr.Post("/post", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) { + try { + std::lock_guard lock(data_mutex); + std::string post = request.form.get_field("post"); + + std::optional user = getLoggedInUser(request, database); + if (!user.has_value()) { + response.status = 401; + response.set_content("

you're not logged in, so you can't post

", "text/html"); + return; + } + + database.addPost(Post(post, user->id)); + response.status = 200; + response.set_content("OK", "text/text"); + + } catch (const std::runtime_error& e) { + response.status = 500; + response.set_content("

there was an error :( it is: " + std::string(e.what()) + "

", "text/html"); + } catch (const std::exception& e) { + response.status = 500; + response.set_content("

there was an error :( it is: " + std::string(e.what()) + "

", "text/html"); + } + }); + svr.Post("/like", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) { try { std::string username = request.form.get_field("username"); @@ -233,14 +300,16 @@ int main() { }); svr.Get("/me", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) { + std::lock_guard lock(data_mutex); std::optional user = getLoggedInUser(request, database); + response.set_header("Cache-Control", "no-store"); if (!user.has_value()) { response.set_header("X-Logged-In", "false"); return; } - response.set_header("X-Logged-In", "false"); + response.set_header("X-Logged-In", "true"); response.set_header("X-Username", user->name); });