first what so ever registration
This commit is contained in:
parent
0c50cfa639
commit
99a9fd507e
@ -39,6 +39,7 @@ message("Compile options: " ${COMPILE_OPTIONS_STRING})
|
||||
|
||||
find_package(nlohmann_json REQUIRED)
|
||||
find_package(FCGI REQUIRED)
|
||||
find_package(Argon2 REQUIRED)
|
||||
|
||||
add_executable(${PROJECT_NAME} main.cpp)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
@ -59,6 +60,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
FCGI::FCGI
|
||||
FCGI::FCGI++
|
||||
nlohmann_json::nlohmann_json
|
||||
Argon2::Argon2
|
||||
)
|
||||
|
||||
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
26
cmake/FindArgon2.cmake
Normal file
26
cmake/FindArgon2.cmake
Normal file
@ -0,0 +1,26 @@
|
||||
find_library(Argon2_LIBRARIES argon2)
|
||||
find_path(Argon2_INCLUDE_DIR argon2.h)
|
||||
|
||||
if (Argon2_LIBRARIES AND Argon2_INCLUDE_DIR)
|
||||
set(Argon2_FOUND TRUE)
|
||||
endif()
|
||||
|
||||
if (Argon2_FOUND)
|
||||
add_library(Argon2::Argon2 SHARED IMPORTED)
|
||||
set_target_properties(Argon2::Argon2 PROPERTIES
|
||||
IMPORTED_LOCATION "${Argon2_LIBRARIES}"
|
||||
INTERFACE_LINK_LIBRARIES "${Argon2_LIBRARIES}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${Argon2_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
if (NOT Argon2_FIND_QUIETLY)
|
||||
message(STATUS "Found Argon2 includes: ${Argon2_INCLUDE_DIR}")
|
||||
message(STATUS "Found Argon2 library: ${Argon2_LIBRARIES}")
|
||||
endif ()
|
||||
else ()
|
||||
if (Argon2_FIND_REQUIRED)
|
||||
message(FATAL_ERROR "Could NOT find Argon2 development files")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
|
@ -36,6 +36,8 @@ public:
|
||||
virtual uint8_t getVersion() = 0;
|
||||
virtual void setVersion(uint8_t version) = 0;
|
||||
|
||||
virtual unsigned int registerAccount(const std::string& login, const std::string& hash) = 0;
|
||||
|
||||
protected:
|
||||
DBInterface(Type type);
|
||||
|
||||
|
@ -17,17 +17,36 @@ CREATE TABLE IF NOT EXISTS accounts (
|
||||
`login` VARCHAR(256) UNIQUE NOT NULL,
|
||||
`nick` VARCHAR(256),
|
||||
`type` INTEGER UNSIGNED NOT NULL,
|
||||
`password` VARCHAR(64),
|
||||
`salt` VARCHAR(32),
|
||||
`role` INTEGER UNSIGNED NOT NULL,
|
||||
`created` TIMESTAMP DEFAULT UTC_TIMESTAMP(),
|
||||
`password` VARCHAR(128),
|
||||
`created` TIMESTAMP DEFAULT UTC_TIMESTAMP()
|
||||
);
|
||||
|
||||
--creating role bindings table
|
||||
CREATE TABLE IF NOT EXISTS roleBindings (
|
||||
`account` INTEGER UNSIGNED NOT NULL,
|
||||
`role` INTEGER UNSIGNED NOT NULL,
|
||||
|
||||
PRIMARY KEY (account, role),
|
||||
FOREIGN KEY (account) REFERENCES accounts(id),
|
||||
FOREIGN KEY (role) REFERENCES roles(id)
|
||||
);
|
||||
|
||||
--creating sessings table
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
`id` INTEGER AUTO_INCREMENT PRIMARY KEY,
|
||||
`owner` INTEGER UNSIGNED NOT NULL,
|
||||
`started` TIMESTAMP DEFAULT UTC_TIMESTAMP(),
|
||||
`latest` TIMESTAMP DEFAULT UTC_TIMESTAMP(),
|
||||
`salt` CHAR(16),
|
||||
`persist` BOOLEAN NOT NULL,
|
||||
|
||||
FOREIGN KEY (owner) REFERENCES accounts(id)
|
||||
);
|
||||
|
||||
--creating defailt roles
|
||||
INSERT IGNORE INTO roles (`name`)
|
||||
VALUES ('root');
|
||||
VALUES ('root'),
|
||||
('default');
|
||||
|
||||
--inserting initial version
|
||||
INSERT INTO system (`key`, `value`) VALUES ('version', '0');
|
||||
|
@ -1,11 +1,13 @@
|
||||
set(HEADERS
|
||||
mysql.h
|
||||
statement.h
|
||||
transaction.h
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
mysql.cpp
|
||||
statement.cpp
|
||||
transaction.cpp
|
||||
)
|
||||
|
||||
find_package(MariaDB REQUIRED)
|
||||
|
@ -9,8 +9,13 @@
|
||||
#include "mysqld_error.h"
|
||||
|
||||
#include "statement.h"
|
||||
#include "transaction.h"
|
||||
|
||||
constexpr const char* versionQuery = "SELECT value FROM system WHERE `key` = 'version'";
|
||||
constexpr const char* updateQuery = "UPDATE system SET `value` = ? WHERE `key` = 'version'";
|
||||
constexpr const char* registerQuery = "INSERT INTO accounts (`login`, `type`, `password`) VALUES (?, 1, ?)";
|
||||
constexpr const char* lastIdQuery = "SELECT LAST_INSERT_ID() AS id";
|
||||
constexpr const char* assignRoleQuery = "INSERT INTO roleBindings (`account`, `role`) SELECT ?, roles.id FROM roles WHERE roles.name = ?";
|
||||
|
||||
static const std::filesystem::path buildSQLPath = "database";
|
||||
|
||||
@ -34,7 +39,6 @@ MySQL::~MySQL() {
|
||||
mysql_close(&connection);
|
||||
}
|
||||
|
||||
|
||||
void MySQL::connect(const std::string& path) {
|
||||
if (state != State::disconnected)
|
||||
return;
|
||||
@ -138,7 +142,7 @@ void MySQL::executeFile(const std::filesystem::path& relativePath) {
|
||||
|
||||
uint8_t MySQL::getVersion() {
|
||||
MYSQL* con = &connection;
|
||||
int result = mysql_query(con, "SELECT value FROM system WHERE `key` = 'version'");
|
||||
int result = mysql_query(con, versionQuery);
|
||||
|
||||
if (result != 0) {
|
||||
unsigned int errcode = mysql_errno(con);
|
||||
@ -216,3 +220,48 @@ std::optional<std::string> MySQL::getComment(std::string& string) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
unsigned int MySQL::registerAccount(const std::string& login, const std::string& hash) {
|
||||
//TODO validate filed lengths!
|
||||
MYSQL* con = &connection;
|
||||
MySQL::Transaction txn(con);
|
||||
|
||||
Statement addAcc(con, registerQuery);
|
||||
|
||||
std::string l = login; //I hate copying just to please this horible API
|
||||
std::string h = hash;
|
||||
addAcc.bind(l.data(), MYSQL_TYPE_STRING);
|
||||
addAcc.bind(h.data(), MYSQL_TYPE_STRING);
|
||||
addAcc.execute();
|
||||
|
||||
unsigned int id = lastInsertedId();
|
||||
static std::string defaultRole("default");
|
||||
|
||||
Statement addRole(con, assignRoleQuery);
|
||||
addRole.bind(&id, MYSQL_TYPE_LONG, true);
|
||||
addRole.bind(defaultRole.data(), MYSQL_TYPE_STRING);
|
||||
addRole.execute();
|
||||
|
||||
txn.commit();
|
||||
return id;
|
||||
}
|
||||
|
||||
unsigned int MySQL::lastInsertedId() {
|
||||
MYSQL* con = &connection;
|
||||
int result = mysql_query(con, lastIdQuery);
|
||||
|
||||
if (result != 0)
|
||||
throw std::runtime_error(std::string("Error executing last inserted id: ") + mysql_error(con));
|
||||
|
||||
std::unique_ptr<MYSQL_RES, ResDeleter> res(mysql_store_result(con));
|
||||
if (!res)
|
||||
throw std::runtime_error(std::string("Querying last inserted id returned no result: ") + mysql_error(con));
|
||||
|
||||
MYSQL_ROW row = mysql_fetch_row(res.get());
|
||||
if (row)
|
||||
return std::stoi(row[0]);
|
||||
else
|
||||
throw std::runtime_error(std::string("Querying last inserted id returned no rows"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
class MySQL : public DBInterface {
|
||||
class Statement;
|
||||
class Transaction;
|
||||
public:
|
||||
MySQL();
|
||||
~MySQL() override;
|
||||
@ -27,9 +28,12 @@ public:
|
||||
uint8_t getVersion() override;
|
||||
void setVersion(uint8_t version) override;
|
||||
|
||||
unsigned int registerAccount(const std::string& login, const std::string& hash) override;
|
||||
|
||||
private:
|
||||
void executeFile(const std::filesystem::path& relativePath);
|
||||
static std::optional<std::string> getComment(std::string& string);
|
||||
unsigned int lastInsertedId();
|
||||
|
||||
protected:
|
||||
MYSQL connection;
|
||||
|
@ -9,15 +9,14 @@ static uint64_t TIME_LENGTH = sizeof(MYSQL_TIME);
|
||||
|
||||
MySQL::Statement::Statement(MYSQL* connection, const char* statement):
|
||||
stmt(mysql_stmt_init(connection)),
|
||||
param(),
|
||||
lengths()
|
||||
param()
|
||||
{
|
||||
int result = mysql_stmt_prepare(stmt.get(), statement, strlen(statement));
|
||||
if (result != 0)
|
||||
throw std::runtime_error(std::string("Error preparing statement: ") + mysql_stmt_error(stmt.get()));
|
||||
}
|
||||
|
||||
void MySQL::Statement::bind(void* value, enum_field_types type) {
|
||||
void MySQL::Statement::bind(void* value, enum_field_types type, bool usigned) {
|
||||
MYSQL_BIND& result = param.emplace_back();
|
||||
std::memset(&result, 0, sizeof(result));
|
||||
|
||||
@ -27,13 +26,18 @@ void MySQL::Statement::bind(void* value, enum_field_types type) {
|
||||
switch (type) {
|
||||
case MYSQL_TYPE_STRING:
|
||||
case MYSQL_TYPE_VAR_STRING:
|
||||
result.length = &lengths.emplace_back(strlen(static_cast<char*>(value)));
|
||||
result.buffer_length = strlen(static_cast<char*>(value));
|
||||
break;
|
||||
case MYSQL_TYPE_DATE:
|
||||
result.length = &TIME_LENGTH;
|
||||
result.buffer_length = TIME_LENGTH;
|
||||
break;
|
||||
case MYSQL_TYPE_LONG:
|
||||
case MYSQL_TYPE_LONGLONG:
|
||||
case MYSQL_TYPE_SHORT:
|
||||
case MYSQL_TYPE_TINY:
|
||||
result.is_unsigned = usigned;
|
||||
break;
|
||||
default:
|
||||
lengths.pop_back();
|
||||
throw std::runtime_error("Type: " + std::to_string(type) + " is not yet supported in bind");
|
||||
break;
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
|
||||
#include "mysql.h"
|
||||
|
||||
|
||||
class MySQL::Statement {
|
||||
struct STMTDeleter {
|
||||
void operator () (MYSQL_STMT* stmt) {
|
||||
@ -17,11 +16,10 @@ class MySQL::Statement {
|
||||
public:
|
||||
Statement(MYSQL* connection, const char* statement);
|
||||
|
||||
void bind(void* value, enum_field_types type);
|
||||
void bind(void* value, enum_field_types type, bool usigned = false);
|
||||
void execute();
|
||||
|
||||
private:
|
||||
std::unique_ptr<MYSQL_STMT, STMTDeleter> stmt;
|
||||
std::vector<MYSQL_BIND> param;
|
||||
std::vector<uint64_t> lengths;
|
||||
};
|
||||
|
34
database/mysql/transaction.cpp
Normal file
34
database/mysql/transaction.cpp
Normal file
@ -0,0 +1,34 @@
|
||||
#include "transaction.h"
|
||||
|
||||
MySQL::Transaction::Transaction(MYSQL* connection):
|
||||
con(connection),
|
||||
opened(false)
|
||||
{
|
||||
if (mysql_autocommit(con, 0) != 0)
|
||||
throw std::runtime_error(std::string("Failed to start transaction") + mysql_error(con));
|
||||
|
||||
opened = true;
|
||||
}
|
||||
|
||||
MySQL::Transaction::~Transaction() {
|
||||
if (opened)
|
||||
abort();
|
||||
}
|
||||
|
||||
void MySQL::Transaction::commit() {
|
||||
if (mysql_commit(con) != 0)
|
||||
throw std::runtime_error(std::string("Failed to commit transaction") + mysql_error(con));
|
||||
|
||||
opened = false;
|
||||
if (mysql_autocommit(con, 1) != 0)
|
||||
throw std::runtime_error(std::string("Failed to return autocommit") + mysql_error(con));
|
||||
}
|
||||
|
||||
void MySQL::Transaction::abort() {
|
||||
opened = false;
|
||||
if (mysql_rollback(con) != 0)
|
||||
throw std::runtime_error(std::string("Failed to rollback transaction") + mysql_error(con));
|
||||
|
||||
if (mysql_autocommit(con, 1) != 0)
|
||||
throw std::runtime_error(std::string("Failed to return autocommit") + mysql_error(con));
|
||||
}
|
16
database/mysql/transaction.h
Normal file
16
database/mysql/transaction.h
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "mysql.h"
|
||||
|
||||
class MySQL::Transaction {
|
||||
public:
|
||||
Transaction(MYSQL* connection);
|
||||
~Transaction();
|
||||
|
||||
void commit();
|
||||
void abort();
|
||||
|
||||
private:
|
||||
MYSQL* con;
|
||||
bool opened;
|
||||
};
|
@ -3,20 +3,57 @@
|
||||
|
||||
#include "register.h"
|
||||
|
||||
Handler::Register::Register():
|
||||
Handler("register", Request::Method::post)
|
||||
#include "server/server.h"
|
||||
|
||||
Handler::Register::Register(Server* server):
|
||||
Handler("register", Request::Method::post),
|
||||
server(server)
|
||||
{}
|
||||
|
||||
void Handler::Register::handle(Request& request) {
|
||||
std::map form = request.getForm();
|
||||
std::map<std::string, std::string>::const_iterator itr = form.find("login");
|
||||
if (itr == form.end())
|
||||
return error(request, Result::noLogin);
|
||||
|
||||
std::cout << "Received form:" << std::endl;
|
||||
for (const auto& pair : form)
|
||||
std::cout << '\t' << pair.first << ": " << pair.second << std::endl;
|
||||
const std::string& login = itr->second;
|
||||
if (login.empty())
|
||||
return error(request, Result::emptyLogin);
|
||||
|
||||
//TODO login policies checkup
|
||||
|
||||
itr = form.find("password");
|
||||
if (itr == form.end())
|
||||
return error(request, Result::noPassword);
|
||||
|
||||
const std::string& password = itr->second;
|
||||
if (password.empty())
|
||||
return error(request, Result::emptyPassword);
|
||||
|
||||
//TODO password policies checkup
|
||||
|
||||
try {
|
||||
server->registerAccount(login, password);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Exception on registration:\n\t" << e.what() << std::endl;
|
||||
return error(request, Result::unknownError);
|
||||
} catch (...) {
|
||||
std::cerr << "Unknown exception on registration" << std::endl;
|
||||
return error(request, Result::unknownError);
|
||||
}
|
||||
|
||||
Response res(request);
|
||||
nlohmann::json body = nlohmann::json::object();
|
||||
body["result"] = "ok";
|
||||
body["result"] = Result::success;
|
||||
|
||||
res.setBody(body);
|
||||
res.send();
|
||||
}
|
||||
|
||||
void Handler::Register::error(Request& request, Result result) {
|
||||
Response res(request);
|
||||
nlohmann::json body = nlohmann::json::object();
|
||||
body["result"] = result;
|
||||
|
||||
res.setBody(body);
|
||||
res.send();
|
||||
|
@ -5,12 +5,30 @@
|
||||
|
||||
#include "handler.h"
|
||||
|
||||
class Server;
|
||||
namespace Handler {
|
||||
|
||||
class Register : public Handler::Handler {
|
||||
public:
|
||||
Register();
|
||||
Register(Server* server);
|
||||
virtual void handle(Request& request);
|
||||
|
||||
enum class Result {
|
||||
success,
|
||||
noLogin,
|
||||
emptyLogin,
|
||||
loginExists,
|
||||
loginPolicyViolation,
|
||||
noPassword,
|
||||
emptyPassword,
|
||||
passwordPolicyViolation,
|
||||
unknownError
|
||||
};
|
||||
|
||||
private:
|
||||
void error(Request& request, Result result);
|
||||
|
||||
private:
|
||||
Server* server;
|
||||
};
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ void Router::route(const std::string& path, std::unique_ptr<Request> request) {
|
||||
return handleNotFound(path, std::move(request));
|
||||
|
||||
try {
|
||||
std::cout << "Handling " << path << "..." << std::endl;
|
||||
itr->second->handle(*request.get());
|
||||
|
||||
if (request->currentState() != Request::State::responded)
|
||||
|
@ -3,11 +3,20 @@
|
||||
|
||||
#include "server.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
#include "handler/info.h"
|
||||
#include "handler/env.h"
|
||||
#include "handler/register.h"
|
||||
|
||||
constexpr const char* pepper = "well, not much of a secret, huh?";
|
||||
constexpr uint8_t currentDbVesion = 1;
|
||||
constexpr const char* randomChars = "0123456789abcdef";
|
||||
constexpr uint8_t saltSize = 16;
|
||||
constexpr uint8_t hashSize = 32;
|
||||
constexpr uint8_t hashParallel = 1;
|
||||
constexpr uint8_t hashIterations = 2;
|
||||
constexpr uint32_t hashMemoryCost = 65536;
|
||||
|
||||
Server::Server():
|
||||
terminating(false),
|
||||
@ -29,7 +38,7 @@ Server::Server():
|
||||
|
||||
router.addRoute(std::make_unique<Handler::Info>());
|
||||
router.addRoute(std::make_unique<Handler::Env>());
|
||||
router.addRoute(std::make_unique<Handler::Register>());
|
||||
router.addRoute(std::make_unique<Handler::Register>(this));
|
||||
}
|
||||
|
||||
Server::~Server() {}
|
||||
@ -63,3 +72,38 @@ void Server::handleRequest(std::unique_ptr<Request> request) {
|
||||
std::string path = request->getPath(serverName.value());
|
||||
router.route(path.data(), std::move(request));
|
||||
}
|
||||
|
||||
std::string Server::generateRandomString(std::size_t length) {
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::uniform_int_distribution<uint8_t> distribution(0, std::strlen(randomChars));
|
||||
|
||||
std::string result(length, 0);
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
result[i] = randomChars[distribution(gen)];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned int Server::registerAccount(const std::string& login, const std::string& password) {
|
||||
std::size_t encSize = argon2_encodedlen(
|
||||
hashIterations, hashMemoryCost,
|
||||
hashParallel, saltSize, hashSize, Argon2_id
|
||||
);
|
||||
|
||||
std::string hash(encSize, 0);
|
||||
std::string salt = generateRandomString(saltSize);
|
||||
std::string spiced = password + pepper;
|
||||
|
||||
int result = argon2id_hash_encoded(
|
||||
hashIterations, hashMemoryCost, hashParallel,
|
||||
spiced.data(), spiced.size(),
|
||||
salt.data(), saltSize,
|
||||
hashSize, hash.data(), encSize
|
||||
);
|
||||
|
||||
if (result != ARGON2_OK)
|
||||
throw std::runtime_error(std::string("Hashing failed: ") + argon2_error_message(result));
|
||||
|
||||
return db->registerAccount(login, hash);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
#include <fcgio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <argon2.h>
|
||||
|
||||
#include "request/request.h"
|
||||
#include "response/response.h"
|
||||
@ -31,8 +31,11 @@ public:
|
||||
|
||||
void run(int socketDescriptor);
|
||||
|
||||
unsigned int registerAccount(const std::string& login, const std::string& password);
|
||||
|
||||
private:
|
||||
void handleRequest(std::unique_ptr<Request> request);
|
||||
static std::string generateRandomString(std::size_t length);
|
||||
|
||||
private:
|
||||
bool terminating;
|
||||
|
Loading…
Reference in New Issue
Block a user