diff --git a/src/database/database.cpp b/src/database/database.cpp index d29da42..ea300db 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -88,7 +88,7 @@ set Database::getStrSet(const string& sql){ return vec; } -std::optional Database::insert(const char* sql) { +std::optional Database::insert(const std::string& sql) { sqlite3_stmt* stmt = prepareStmt(sql); if (stmt == nullptr) return {}; diff --git a/src/database/database.hpp b/src/database/database.hpp index 3a15dc7..6508740 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -21,7 +21,7 @@ public: bool exec(const char* sqlQuery); bool exec(const std::string& sqlQuery); - std::optional insert(const char* sql); + std::optional insert(const std::string& sql); std::set getStrSet(const std::string& sql); diff --git a/src/login/login.cpp b/src/login/login.cpp index 167d520..88510b0 100644 --- a/src/login/login.cpp +++ b/src/login/login.cpp @@ -1,25 +1,19 @@ #include "login.hpp" -#include "string.h" #include "sodium.h" -#include "unordered_map" #include "database.hpp" - +#include "utils.hpp" +#include namespace login { -struct Session { - std::string user_id; - std::string csrf_token; -}; std::unordered_map sessions; -bool verifyHashWithPassword(const std::string& hash, std::string const& password) -{ - if (crypto_pwhash_str_verify(hash.c_str(), password.c_str(), password.size()) == 0) { - return true; - } else { - return false; - } +bool is_logged_in(const crow::request& req) { + std::string session_id = get_session_id(req); + if (session_id.empty()) return false; + auto it = sessions.find(session_id); + if (it == sessions.end()) return false; + return !it->second.user_id.empty(); } std::string hashPassword(const std::string& password) @@ -41,6 +35,31 @@ std::string hashPassword(const std::string& password) return hash; } +std::optional createUser(const std::string& username, const std::string& password){ + auto db = Database(); + if (!db.open()) + return false; + + std::string password_hash = hashPassword(password); + if(password_hash.empty()) + return false; + + std::string insert_sql = + "INSERT INTO users (username, password_hash) VALUES ('" + + username + "', '" + password_hash + "');"; + + return db.insert(insert_sql); +} + +bool verifyHashWithPassword(const std::string& hash, std::string const& password) +{ + if (crypto_pwhash_str_verify(hash.c_str(), password.c_str(), password.size()) == 0) { + return true; + } else { + return false; + } +} + std::string get_session_id(const crow::request& req) { auto cookie_header = req.get_header_value("Cookie"); std::string prefix = "session_id="; @@ -61,45 +80,21 @@ std::string random_string(size_t length) { return result; } -bool is_logged_in(const crow::request& req) { - std::string session_id = get_session_id(req); - if (session_id.empty()) return false; - auto it = sessions.find(session_id); - if (it == sessions.end()) return false; - return !it->second.user_id.empty(); -} - -// lambda to be used by endpoint that requiere login -auto login_required = [](auto handler){ - return [handler](const crow::request& req){ - if (!is_logged_in(req)) { - crow::response res; - if (req.get_header_value("HX-Request") == "true") - res = crow::response(401, "Login required"); - else { - res = crow::response(302); - res.add_header("Location", "/login"); - } - return res; - } - return handler(req); - }; -}; - -bool loginUser(const std::string& username, const std::string& password) +std::optional loginUser(const std::string& username, const std::string& password) { auto sql = "SELECT id password_hash FROM users WHERE username = '?' LIMIT 1;"; auto db = Database(); - if (!db.open()) - return false; + return {}; auto opt_pair = db.get(sql, {username}); if (opt_pair.has_value()) { - return verifyHashWithPassword(opt_pair.value().second, password); - } else { - return false; - } + if (verifyHashWithPassword(opt_pair.value().second, password)) + { + return opt_pair.value().first; + } + } + return {}; } bool initDB() @@ -135,6 +130,8 @@ bool initLogin(crow::SimpleApp& app) { return false; } + + // createUser("lukas", "Trollar%4928"); CROW_ROUTE(app, "/login").methods("GET"_method) ([](const crow::request& req){ @@ -167,19 +164,22 @@ bool initLogin(crow::SimpleApp& app) auto session = it->second; // parse form - auto body = crow::query_string(req.body); - std::string csrf_token = body.get("csrf_token"); - std::string username = body.get("username"); - std::string password = body.get("password"); + auto body = utils::parseBody(req.body); + if (body.empty()) + return crow::response(400); + + std::string csrf_token = body.at("csrf_token"); + std::string username = body.at("username"); + std::string password = body.at("password"); if (csrf_token != session.csrf_token) return crow::response(403, "CSRF failed"); - bool ok = loginUser(username, password); + std::optional userId = loginUser(username, password); - if (!ok) return crow::response(401, "Invalid credentials"); + if (!userId.has_value()) return crow::response(401, "Invalid credentials"); - // regenerate session, mark as logged in - sessions[session_id].user_id = "123"; // user ID + // set user id + sessions[session_id].user_id = std::to_string(userId.value()); crow::response res; res.add_header("HX-Redirect", "/dashboard"); // htmx redirect diff --git a/src/login/login.hpp b/src/login/login.hpp index 33db34d..194fe4a 100644 --- a/src/login/login.hpp +++ b/src/login/login.hpp @@ -1,11 +1,41 @@ #ifndef __LOGIN_H__ #define __LOGIN_H__ +#pragma once + #include +#include "unordered_map" +#include "string.h" namespace login { +struct Session { + std::string user_id; + std::string csrf_token; +}; + bool initLogin(crow::SimpleApp& app); +bool is_logged_in(const crow::request& req); + +std::string get_session_id(const crow::request& req); + +// lambda to be used by endpoint that requiere login +inline auto login_required = [](auto handler){ + return [handler](const crow::request& req){ + if (!is_logged_in(req)) { + crow::response res; + if (req.get_header_value("HX-Request") == "true") + res = crow::response(401, "Login required"); + else { + res = crow::response(302); + res.add_header("Location", "/login"); + } + return res; + } + return handler(req); + }; +}; + } #endif // __LOGIN_H__ \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index fbfb3b6..3893982 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,18 +12,6 @@ using namespace std; -optional get_body_name(const string& body) { - const auto pos = body.find('='); - if (pos == std::string::npos) return {}; - - const string key = body.substr(0, pos); - string value = body.substr(pos + 1); - - if (key != "name") return {}; - - return value; -} - int main() { crow::SimpleApp app; @@ -41,7 +29,7 @@ int main() { // Static file redirector CROW_ROUTE(app, "/redirect") - ([](const crow::request& req) { + (login::login_required([](const crow::request& req) { auto file_param = req.url_params.get("file"); if (!file_param) { return crow::response(400, "Missing 'file' parameter"); @@ -54,15 +42,16 @@ int main() { res.code = 204; res.add_header("HX-Redirect", filepath); return res; - }); + })); - CROW_ROUTE(app, "/status")([] { + CROW_ROUTE(app, "/status")(login::login_required([](const crow::request& req) { auto table = create_service_table(); return crow::response{table.htmx()}; - }); + })); - CROW_ROUTE(app, "/toggle-service").methods(crow::HTTPMethod::Post)([](const crow::request& req) { - auto body = get_body_name(req.body); + CROW_ROUTE(app, "/toggle-service").methods(crow::HTTPMethod::Post) + (login::login_required([](const crow::request& req) { + auto body = utils::getBodyName(req.body); if (!body.has_value()) return crow::response(400); @@ -82,11 +71,10 @@ int main() { row = create_error_table_row(opt_settings.error()); } return crow::response{row.htmx()}; - }); + })); const uint16_t defaultPort = 3010; uint16_t httpPort = defaultPort; - { auto opt_settings = AppSettings::loadAppSettings(); if (opt_settings.has_value()){ diff --git a/src/shadowrun/ShadowrunApi.cpp b/src/shadowrun/ShadowrunApi.cpp index 42cdcce..2382578 100644 --- a/src/shadowrun/ShadowrunApi.cpp +++ b/src/shadowrun/ShadowrunApi.cpp @@ -3,6 +3,7 @@ #include "ShadowrunApi.hpp" #include "ShadowrunCharacterForm.hpp" #include "ShadowrunDb.hpp" +#include "login.hpp" #include #include @@ -35,8 +36,8 @@ static crow::response rsp(const std::string& msg){ void initApi(crow::SimpleApp& app) { - CROW_ROUTE(app, "/api/shadowrun/submit-character").methods("POST"_method)( - [](const crow::request& req) { + CROW_ROUTE(app, "/api/shadowrun/submit-character").methods("POST"_method) + (login::login_required([](const crow::request& req) { auto params = parse_query_string(req.body); auto name_data = params["Character-Info_Name"]; @@ -75,20 +76,20 @@ void initApi(crow::SimpleApp& app) return rsp("Failed to store character data"); }; return rsp(format("Character {} submitted successfully", name_data)); - }); + })); CROW_ROUTE(app, "/api/shadowrun/character-form") - ([](const crow::request& req) { + (login::login_required([](const crow::request& req) { auto query = crow::query_string(req.url_params); std::string name = query.get("name") ? query.get("name") : ""; auto data = getCharacterData(getKeyOfCharacter(name)); return crow::response{ShadowrunCharacterForm(data).htmx()}; - }); + })); CROW_ROUTE(app, "/api/shadowrun/character-list") - ([] { + (login::login_required([](const crow::request& req) { std::ostringstream html; // Simulated character database @@ -106,7 +107,7 @@ void initApi(crow::SimpleApp& app) << ""; return crow::response{html.str()}; - }); + })); if(!shadowrun::initDb()){ CROW_LOG_ERROR << "Failed to Init shadowrun database"; diff --git a/src/utils.cpp b/src/utils.cpp index 078dd99..a831426 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -14,6 +14,43 @@ using namespace std; namespace utils { +map parseBody(const string& body) +{ + size_t pos = 0; + size_t start = 0; + map data; + + while (true) + { + pos = body.find('=', start); + const string key = body.substr(start, pos - start); + + size_t nextPos = body.find('&', pos + 1); + string value = body.substr(pos + 1, nextPos - (pos + 1)); + data[key] = value; + + if (nextPos == std::string::npos) + { + break; + } + start = nextPos + 1; + } + + return data; +} + +optional getBodyName(const string& body) { + const auto pos = body.find('='); + if (pos == std::string::npos) return {}; + + const string key = body.substr(0, pos); + string value = body.substr(pos + 1); + + if (key != "name") return {}; + + return value; +} + expected isLocalPortOpen(uint16_t portno) { const char *hostname = "localhost"; diff --git a/src/utils.hpp b/src/utils.hpp index 55a51e1..da86fbb 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -5,8 +5,13 @@ #include #include #include +#include namespace utils { + std::map parseBody(const std::string& body); + + std::optional getBodyName(const std::string& body); + std::expected isLocalPortOpen(uint16_t portno); std::string to_id_format(const std::string& s); diff --git a/templates/dashboard.html b/templates/dashboard.html new file mode 100644 index 0000000..eefaabc --- /dev/null +++ b/templates/dashboard.html @@ -0,0 +1,75 @@ + + + + + Service Status + + + + +
+

Shadowrun

+
+ +
+

Service Status

+ +
+ Loading services... +
+
+ + diff --git a/templates/index.html b/templates/index.html index 069ce0d..3f9c775 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,7 +2,7 @@ - Service Status + Login -
- - - - -
+
+

Login

-
-

Shadowrun

-
- -
-

Service Status

- -
- Loading services... +
+