added login suppport
This commit is contained in:
parent
4916b4f1c1
commit
5a1297dc80
@ -88,7 +88,7 @@ set<string> Database::getStrSet(const string& sql){
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::optional<int64_t> Database::insert(const char* sql) {
|
||||
std::optional<int64_t> Database::insert(const std::string& sql) {
|
||||
sqlite3_stmt* stmt = prepareStmt(sql);
|
||||
if (stmt == nullptr)
|
||||
return {};
|
||||
|
||||
@ -21,7 +21,7 @@ public:
|
||||
bool exec(const char* sqlQuery);
|
||||
bool exec(const std::string& sqlQuery);
|
||||
|
||||
std::optional<int64_t> insert(const char* sql);
|
||||
std::optional<int64_t> insert(const std::string& sql);
|
||||
|
||||
std::set<std::string> getStrSet(const std::string& sql);
|
||||
|
||||
|
||||
@ -1,25 +1,19 @@
|
||||
#include "login.hpp"
|
||||
#include "string.h"
|
||||
#include "sodium.h"
|
||||
#include "unordered_map"
|
||||
#include "database.hpp"
|
||||
|
||||
#include "utils.hpp"
|
||||
#include <iostream>
|
||||
namespace login
|
||||
{
|
||||
struct Session {
|
||||
std::string user_id;
|
||||
std::string csrf_token;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, Session> 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<int> 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<int> 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<int, std::string>(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<int> 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
|
||||
|
||||
@ -1,11 +1,41 @@
|
||||
#ifndef __LOGIN_H__
|
||||
#define __LOGIN_H__
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <crow.h>
|
||||
#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__
|
||||
28
src/main.cpp
28
src/main.cpp
@ -12,18 +12,6 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
optional<string> 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()){
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include "ShadowrunApi.hpp"
|
||||
#include "ShadowrunCharacterForm.hpp"
|
||||
#include "ShadowrunDb.hpp"
|
||||
#include "login.hpp"
|
||||
#include <format>
|
||||
#include <vector>
|
||||
|
||||
@ -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)
|
||||
<< "</form>";
|
||||
|
||||
return crow::response{html.str()};
|
||||
});
|
||||
}));
|
||||
|
||||
if(!shadowrun::initDb()){
|
||||
CROW_LOG_ERROR << "Failed to Init shadowrun database";
|
||||
|
||||
@ -14,6 +14,43 @@ using namespace std;
|
||||
|
||||
namespace utils {
|
||||
|
||||
map<string, string> parseBody(const string& body)
|
||||
{
|
||||
size_t pos = 0;
|
||||
size_t start = 0;
|
||||
map<string, string> 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<string> 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<bool, string> isLocalPortOpen(uint16_t portno) {
|
||||
const char *hostname = "localhost";
|
||||
|
||||
|
||||
@ -5,8 +5,13 @@
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
|
||||
namespace utils {
|
||||
std::map<std::string, std::string> parseBody(const std::string& body);
|
||||
|
||||
std::optional<std::string> getBodyName(const std::string& body);
|
||||
|
||||
std::expected<bool, std::string> isLocalPortOpen(uint16_t portno);
|
||||
|
||||
std::string to_id_format(const std::string& s);
|
||||
|
||||
75
templates/dashboard.html
Normal file
75
templates/dashboard.html
Normal file
@ -0,0 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="/static/htmx.min.js"></script>
|
||||
<title>Service Status</title>
|
||||
<style>
|
||||
.active-button {
|
||||
background-color: #4CAF50; /* Green */
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.inactive-button {
|
||||
background-color: #f44336; /* Red */
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100vh; /* Full screen height */
|
||||
display: flex;
|
||||
justify-content: center; /* Horizontal centering */
|
||||
align-items: center; /* Vertical centering */
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center; /* Optional: center items within the column */
|
||||
gap: 1rem; /* Space between elements */
|
||||
}
|
||||
|
||||
.app-panel {
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
height: 120px;
|
||||
margin: 10px;
|
||||
padding: 20px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
.app-panel:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="app-panel"
|
||||
hx-get="/redirect?file=shadowrun.html"
|
||||
hx-trigger="click"
|
||||
hx-target="this"
|
||||
hx-swap="none">
|
||||
<h3>Shadowrun</h3>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<h1>Service Status</h1>
|
||||
|
||||
<div id="services" hx-get="/status" hx-trigger="load, every 5s" hx-swap="innerHTML">
|
||||
Loading services...
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="/static/htmx.min.js"></script>
|
||||
<title>Service Status</title>
|
||||
<title>Login</title>
|
||||
<style>
|
||||
.active-button {
|
||||
background-color: #4CAF50; /* Green */
|
||||
@ -37,46 +37,19 @@
|
||||
gap: 1rem; /* Space between elements */
|
||||
}
|
||||
|
||||
.app-panel {
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
height: 120px;
|
||||
margin: 10px;
|
||||
padding: 20px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
.app-panel:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<form hx-post="/login" hx-swap="none">
|
||||
<input type="hidden" name="csrf_token" value="{{csrf_token}}">
|
||||
<input name="email">
|
||||
<input name="password" type="password">
|
||||
<button>Login</button>
|
||||
</form>
|
||||
<div class="column">
|
||||
<h1>Login</h1>
|
||||
|
||||
<div class="app-panel"
|
||||
hx-get="/redirect?file=shadowrun.html"
|
||||
hx-trigger="click"
|
||||
hx-target="this"
|
||||
hx-swap="none">
|
||||
<h3>Shadowrun</h3>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<h1>Service Status</h1>
|
||||
|
||||
<div id="services" hx-get="/status" hx-trigger="load, every 5s" hx-swap="innerHTML">
|
||||
Loading services...
|
||||
<div id="login" hx-get="/login"
|
||||
hx-trigger="load"
|
||||
hx-target="this"
|
||||
hx-swap="outerHTML">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user