// SPDX-FileCopyrightText: 2023 Yury Gubich // SPDX-License-Identifier: GPL-3.0-or-later #include "server.h" #include #include "database/exceptions.h" #include "handler/info.h" #include "handler/env.h" #include "handler/register.h" #include "handler/login.h" constexpr const char* pepper = "well, not much of a secret, huh?"; constexpr const char* dbLogin = "pica"; constexpr const char* dbPassword = "pica"; constexpr const char* dbName = "pica"; constexpr const char* dbPath = "/run/mysqld/mysqld.sock"; constexpr uint8_t dbConnectionsCount = 4; constexpr const char* randomChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 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; constexpr uint8_t currentDbVesion = 1; Server::Server(): terminating(false), requestCount(0), serverName(std::nullopt), router(), pool(DB::Pool::create()), sessions() { std::cout << "Startig pica..." << std::endl; std::cout << "Database type: MySQL" << std::endl; pool->addInterfaces( DB::Interface::Type::mysql, dbConnectionsCount, dbLogin, dbPassword, dbName, dbPath ); DB::Resource db = pool->request(); db->migrate(currentDbVesion); router.addRoute(std::make_unique()); router.addRoute(std::make_unique()); router.addRoute(std::make_unique(this)); router.addRoute(std::make_unique(this)); } Server::~Server() {} void Server::run(int socketDescriptor) { while (!terminating) { std::unique_ptr request = std::make_unique(); bool result = request->wait(socketDescriptor); if (!result) { std::cerr << "Error accepting a request" << std::endl; return; } handleRequest(std::move(request)); } } void Server::handleRequest(std::unique_ptr request) { ++requestCount; if (!serverName) { try { serverName = request->getServerName(); std::cout << "received server name " << serverName.value() << std::endl; } catch (...) { std::cerr << "failed to read server name" << std::endl; Response& error = request->createResponse(Response::Status::internalError); error.send(); return; } } request->readPath(serverName.value()); router.route(std::move(request)); } std::string Server::generateRandomString(std::size_t length) { std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution distribution(0, std::strlen(randomChars) - 1); 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)); DB::Resource db = pool->request(); return db->registerAccount(login, hash); } bool Server::validatePassword(const std::string& login, const std::string& password) { DB::Resource db = pool->request(); std::string hash = db->getAccountHash(login); std::string spiced = password + pepper; int result = argon2id_verify(hash.data(), spiced.data(), spiced.size()); switch (result) { case ARGON2_OK: return true; case ARGON2_VERIFY_MISMATCH: return false; default: throw std::runtime_error(std::string("Failed to verify password: ") + argon2_error_message(result)); } } Session& Server::openSession(const std::string& login) { std::string accessToken, renewToken; unsigned int sessionId = 0; int counter = 10; do { try { accessToken = generateRandomString(32); renewToken = generateRandomString(32); DB::Resource db = pool->request(); sessionId = db->createSession(login, accessToken, renewToken); break; } catch (const DB::Duplicate& e) { std::cout << "Duplicate on creating session, trying again with different tokens"; } } while (--counter != 0); if (sessionId == 0) throw std::runtime_error("Couldn't create session, ran out of attempts"); std::unique_ptr& session = sessions[accessToken] = std::make_unique(sessionId, accessToken, renewToken); return *session.get(); }