login now works and files are protected

This commit is contained in:
Lukas Forsberg 2026-02-02 22:06:37 +01:00
parent 79b5737bcb
commit fbb54b461e
9 changed files with 149 additions and 105 deletions

View File

@ -2,59 +2,57 @@
<script lang="ts"> <script lang="ts">
import { API_BASE } from '$lib/config'; import { API_BASE } from '$lib/config';
import { goto } from '$app/navigation';
let user : any = ''; let error = "";
let password : any = '';
let loading : any = false; let Credentials = {
let error : any = ''; username: "",
password: "",
}
async function handleLogin() { async function handleLogin() {
let error : any = '';
let loading : Boolean = true;
try { try {
const res = await fetch(`${API_BASE}/api/shadowrun/characters`, { const res = await fetch(`${API_BASE}/login`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credentials: 'include',
username: user, body: JSON.stringify(
password: password, Credentials )
})
}); });
if (res.ok) { if (!res.ok) {
characters = await res.json(); error = await res.text();
return;
} }
goto("/shadowrun")
alert('Logged in!');
} catch (e) { } catch (e) {
error = e.message; error = e.message;
} finally {
loading = false;
} }
} }
</script> </script>
<form class="login" on:submit|preventDefault={handleLogin}> <form class="login">
<h2>Login</h2> <h2>Login</h2>
<label> <label>
User User
<input type="text" bind:value={user} required /> <input type="text" bind:value={Credentials.username} required />
</label> </label>
<label> <label>
Password Password
<input type="password" bind:value={password} required /> <input type="password" bind:value={Credentials.password} required />
</label> </label>
<button on:click={handleLogin}>
Login
</button>
{#if error} {#if error}
<p class="error">{error}</p> <p class="error">{error}</p>
{/if} {/if}
<button disabled={loading}>
{loading ? 'Logging in…' : 'Login'}
</button>
</form> </form>

View File

@ -29,20 +29,21 @@ bool isLoggedIn(const crow::request& req) {
std::optional<std::string> loginUser(const std::string& username, const std::string& password) std::optional<std::string> loginUser(const std::string& username, const std::string& password)
{ {
auto user = getVerifiedUser(username, password); auto user = getVerifiedUser(username, password);
if (user.has_value()) { if (user) {
return sessionHandler.createSession(user.value().id); return sessionHandler.createSession(user->id);
} }
return {}; return {};
} }
void initLogin(crow::SimpleApp& app) void initLogin(crow::SimpleApp& app)
{ {
// createUser("lukas", "Trollar4928");
//createUser("lukas", "Trollar4928");
CROW_ROUTE(app, "/login").methods("POST"_method) CROW_ROUTE(app, "/login").methods("POST"_method)
([](const crow::request& req) { ([](const crow::request& req) {
nlohmann::json body = nlohmann::json::parse(req.body); // parse JSON from HTTP body nlohmann::json body = nlohmann::json::parse(req.body); // parse JSON from HTTP body
if (!body.empty()) if (body.empty())
return crow::response(400, "Invalid JSON"); return crow::response(400, "Invalid JSON");
auto usenameIt = body.find("username"); auto usenameIt = body.find("username");

View File

@ -11,15 +11,27 @@ void initLogin(crow::SimpleApp& app);
bool isLoggedIn(const crow::request& req); bool isLoggedIn(const crow::request& req);
// lambda to be used by endpoint that requiere login // login_required lambda that works for any handler with arbitrary args
inline auto login_required = [](auto handler){ inline auto login_required = [](auto handler){
return [handler](const crow::request& req){ return [handler](auto&&... args) -> crow::response {
// the first argument is always crow::request
const crow::request& req = std::get<0>(std::forward_as_tuple(args...));
if (!isLoggedIn(req)) { if (!isLoggedIn(req)) {
return crow::response(401, "Login required"); return crow::response(401, "Login required");
}
// call original handler with all arguments
auto result = handler(std::forward<decltype(args)>(args)...);
// ensure crow::response return type
if constexpr (std::is_same_v<decltype(result), crow::response>) {
return result;
} else {
return crow::response(result);
} }
return handler(req);
}; };
}; };
} }
#endif // __LOGIN_H__ #endif // __LOGIN_H__

View File

@ -1,8 +1,11 @@
#include "loginDb.hpp" #include "loginDb.hpp"
#include "databasepool.h" #include "databasepool.h"
#include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
#include <iostream>
extern "C" { extern "C" {
#include "monocypher.h" #include "monocypher.h"
} }
@ -59,8 +62,14 @@ int createUser(const std::string& username, const std::string& password){
int64_t id; int64_t id;
auto db = dbpool.acquire(); auto db = dbpool.acquire();
auto user = db->get_optional<User>( for (auto &u : db->get_all<login::User>()) {
where(c(&User::username) == username) if (u.username == username){
std::cout << "WTF" << std::endl;
};
}
auto user = db->get_optional<login::User>(
where(c(&login::User::username) == username)
); );
if (user.has_value()) { if (user.has_value()) {
@ -76,11 +85,16 @@ int createUser(const std::string& username, const std::string& password){
std::optional<User> getUser(const std::string& username){ std::optional<User> getUser(const std::string& username){
auto db = dbpool.acquire(); auto db = dbpool.acquire();
auto user = db->get_optional<User>( auto user = db->get_all<login::User>(
where(c(&User::username) == username) where(c(&login::User::username) == username)
); );
dbpool.release(db); dbpool.release(db);
return user;
if(user.size() > 0){
return user[0];
}
return {};
} }
std::optional<User> getVerifiedUser(const std::string& username, const std::string& password){ std::optional<User> getVerifiedUser(const std::string& username, const std::string& password){
@ -88,7 +102,7 @@ std::optional<User> getVerifiedUser(const std::string& username, const std::stri
if (!user.has_value()) if (!user.has_value())
return {}; return {};
if (verifyUser(user.value(), password)){ if (verifyUser(*user, password)){
return user; return user;
} }
return {}; return {};

View File

@ -3,7 +3,6 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <optional>
#include "json.hpp" #include "json.hpp"
#include "utils.hpp" #include "utils.hpp"
namespace login namespace login

View File

@ -7,38 +7,12 @@
using namespace std; using namespace std;
std::string read_file(const std::string& path) {
std::ifstream file(path, std::ios::binary);
if (!file) {
throw std::runtime_error("Cannot open file: " + path);
}
std::ostringstream ss;
ss << file.rdbuf();
return ss.str();
}
// Simple MIME type detection
std::string get_mime_type(const std::string& path) {
if (path.ends_with(".html")) return "text/html";
if (path.ends_with(".js")) return "text/javascript";
if (path.ends_with(".css")) return "text/css";
if (path.ends_with(".json")) return "application/json";
if (path.ends_with(".svg")) return "image/svg+xml";
if (path.ends_with(".png")) return "image/png";
if (path.ends_with(".jpg") || path.ends_with(".jpeg")) return "image/jpeg";
if (path.ends_with(".woff")) return "font/woff";
if (path.ends_with(".woff2")) return "font/woff2";
if (path.ends_with(".pdf")) return "application/pdf";
return "application/octet-stream";
}
int main() { int main() {
crow::SimpleApp app; crow::SimpleApp app;
const filesystem::path build_dir = "frontend/build/"; // <-- set your build folder
// Root route // Root route
CROW_ROUTE(app, "/")([&]() { CROW_ROUTE(app, "/")([&]() {
auto data = read_file(build_dir / "index.html"); auto data = utils::read_file(utils::build_dir / "index.html");
return crow::response(200, "text/html", data); return crow::response(200, "text/html", data);
}); });
@ -63,40 +37,19 @@ int main() {
([&](const std::string& p) { ([&](const std::string& p) {
const filesystem::path assets_dir = "assets/"; // <-- set your build folder const filesystem::path assets_dir = "assets/"; // <-- set your build folder
filesystem::path file_path = assets_dir / p; filesystem::path file_path = assets_dir / p;
return utils::getFile(file_path);
// If file exists, serve it
if (filesystem::exists(file_path)) {
auto data = read_file(file_path);
auto mime = get_mime_type(file_path.string());
return crow::response(200, mime.c_str(), data);
}
// SPA fallback: serve root index.html for unknown routes
filesystem::path fallback = build_dir / "index.html";
auto data = read_file(fallback);
return crow::response(404, "text/html", data);
}); });
// Catch-all route for static files and SPA fallback // Catch-all route for static files and SPA fallback
CROW_ROUTE(app, "/<path>") CROW_ROUTE(app, "/<path>")
([&](const std::string& p) { ([&](const std::string& p) {
filesystem::path file_path = build_dir / p; filesystem::path file_path = utils::build_dir / p;
// If path is a directory, serve index.html inside it // If path is a directory, serve index.html inside it
if (filesystem::is_directory(file_path)) if (filesystem::is_directory(file_path))
file_path /= "index.html"; file_path /= "index.html";
// If file exists, serve it return utils::getFile(file_path);
if (filesystem::exists(file_path)) {
auto data = read_file(file_path);
auto mime = get_mime_type(file_path.string());
return crow::response(200, mime.c_str(), data);
}
// SPA fallback: serve root index.html for unknown routes
filesystem::path fallback = build_dir / "index.html";
auto data = read_file(fallback);
return crow::response(404, "text/html", data);
}); });
app.loglevel(crow::LogLevel::INFO); app.loglevel(crow::LogLevel::INFO);

View File

@ -26,16 +26,26 @@ static std::unordered_map<std::string, std::string> parse_query_string(const std
return params; return params;
} }
static crow::response rsp(const std::string& msg){ void initApi(crow::SimpleApp& app){
auto str = format("<div class='alert alert-success'>"
"{} </div>", msg); CROW_ROUTE(app, "/assets/shadowrun/<path>")
return crow::response{str}; ([&](const crow::request& req, const std::string& p) {
} if (!login::isLoggedIn(req)) {
return crow::response(401, "Login required");
}
const filesystem::path assets_dir = "assets/shadowrun/";
filesystem::path file_path = assets_dir / p;
return utils::getFile(file_path);
});
void initApi(crow::SimpleApp& app)
{
CROW_ROUTE(app, "/api/shadowrun/characters") CROW_ROUTE(app, "/api/shadowrun/characters")
([&]() { ([&](const crow::request& req) {
if (!login::isLoggedIn(req)) {
return crow::response(401, "Login required");
}
auto characters = getCharacters(); auto characters = getCharacters();
auto res = auto res =
crow::response(200, utils::toJsonArray(characters)); crow::response(200, utils::toJsonArray(characters));
@ -45,6 +55,10 @@ void initApi(crow::SimpleApp& app)
CROW_ROUTE(app, "/api/shadowrun/characters").methods("POST"_method) CROW_ROUTE(app, "/api/shadowrun/characters").methods("POST"_method)
([](const crow::request& req) { ([](const crow::request& req) {
if (!login::isLoggedIn(req)) {
return crow::response(401, "Login required");
}
nlohmann::json data = nlohmann::json::parse(req.body); // parse JSON from HTTP body nlohmann::json data = nlohmann::json::parse(req.body); // parse JSON from HTTP body
auto name = data["name"]; auto name = data["name"];
int id = createCharacter(name); int id = createCharacter(name);
@ -60,7 +74,10 @@ void initApi(crow::SimpleApp& app)
}); });
CROW_ROUTE(app, "/api/shadowrun/characters/<int>") CROW_ROUTE(app, "/api/shadowrun/characters/<int>")
([&](int id) { ([&](const crow::request& req, int id) {
if (!login::isLoggedIn(req)) {
return crow::response(401, "Login required");
}
auto optCharacter = getChracter(id); auto optCharacter = getChracter(id);
if (!optCharacter.has_value()) if (!optCharacter.has_value())
return crow::response(404, "Character not found"); return crow::response(404, "Character not found");
@ -71,7 +88,10 @@ void initApi(crow::SimpleApp& app)
}); });
CROW_ROUTE(app, "/api/shadowrun/characters_data/<int>") CROW_ROUTE(app, "/api/shadowrun/characters_data/<int>")
([&](int id) { ([&](const crow::request& req, int id) {
if (!login::isLoggedIn(req)) {
return crow::response(401, "Login required");
}
nlohmann::json j; nlohmann::json j;
const auto characterData = getChracterData(id); const auto characterData = getChracterData(id);
@ -99,6 +119,9 @@ void initApi(crow::SimpleApp& app)
CROW_ROUTE(app, "/api/shadowrun/characters_data/<int>").methods("POST"_method) CROW_ROUTE(app, "/api/shadowrun/characters_data/<int>").methods("POST"_method)
([&](const crow::request& req, int id) { ([&](const crow::request& req, int id) {
if (!login::isLoggedIn(req)) {
return crow::response(401, "Login required");
}
nlohmann::json j = nlohmann::json::parse(req.body); nlohmann::json j = nlohmann::json::parse(req.body);
for (auto type : magic_enum::enum_values<Type>()) { for (auto type : magic_enum::enum_values<Type>()) {
@ -110,7 +133,6 @@ void initApi(crow::SimpleApp& app)
auto res = crow::response(200, "Saved Character data"); auto res = crow::response(200, "Saved Character data");
return res; return res;
//return crow::response(405, ret.error());
}); });
} }

View File

@ -6,6 +6,7 @@
#include <netinet/in.h> #include <netinet/in.h>
#include <netdb.h> #include <netdb.h>
#include "utils.hpp" #include "utils.hpp"
#include "crow/http_response.h"
#include <algorithm> #include <algorithm>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
@ -141,6 +142,45 @@ string currentTime(){
return ss.str(); return ss.str();
} }
std::string read_file(const std::string& path) {
std::ifstream file(path, std::ios::binary);
if (!file) {
throw std::runtime_error("Cannot open file: " + path);
}
std::ostringstream ss;
ss << file.rdbuf();
return ss.str();
}
// Simple MIME type detection
std::string get_mime_type(const std::string& path) {
if (path.ends_with(".html")) return "text/html";
if (path.ends_with(".js")) return "text/javascript";
if (path.ends_with(".css")) return "text/css";
if (path.ends_with(".json")) return "application/json";
if (path.ends_with(".svg")) return "image/svg+xml";
if (path.ends_with(".png")) return "image/png";
if (path.ends_with(".jpg") || path.ends_with(".jpeg")) return "image/jpeg";
if (path.ends_with(".woff")) return "font/woff";
if (path.ends_with(".woff2")) return "font/woff2";
if (path.ends_with(".pdf")) return "application/pdf";
return "application/octet-stream";
}
crow::response getFile(const filesystem::path& file_path){
// If file exists, serve it
if (filesystem::exists(file_path)) {
auto data = read_file(file_path);
auto mime = get_mime_type(file_path.string());
return crow::response(200, mime.c_str(), data);
}
// SPA fallback: serve root index.html for unknown routes
filesystem::path fallback = build_dir / "index.html";
auto data = read_file(fallback);
return crow::response(404, "text/html", data);
}
std::expected<nlohmann::json, std::string> parseJson(const std::string& input) { std::expected<nlohmann::json, std::string> parseJson(const std::string& input) {
try { try {
return nlohmann::json::parse(input); return nlohmann::json::parse(input);

View File

@ -5,10 +5,13 @@
#include <string> #include <string>
#include <cstdint> #include <cstdint>
#include <filesystem> #include <filesystem>
#include <map>
#include <expected> #include <expected>
#include "json.hpp" #include "json.hpp"
#include "crow.h"
namespace utils { namespace utils {
const std::filesystem::path build_dir = "frontend/build/"; // <-- set your build folder
std::map<std::string, std::string> parseBody(const std::string& body); std::map<std::string, std::string> parseBody(const std::string& body);
std::optional<std::string> getBodyName(const std::string& body); std::optional<std::string> getBodyName(const std::string& body);
@ -45,8 +48,10 @@ namespace utils {
catch (const nlohmann::json::exception& e) { catch (const nlohmann::json::exception& e) {
return std::unexpected(e.what()); return std::unexpected(e.what());
} }
} }
std::string read_file(const std::string& path);
crow::response getFile(const std::filesystem::path& file_path);
} }
#endif #endif