//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" #include "handler/poll.h" #include "handler/assets.h" #include "handler/addasset.h" #include "handler/deleteasset.h" #include "handler/currencies.h" #include "handler/updateasset.h" #include "taskmanager/route.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 uint32_t pollTimout = 10000; 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 (): std::enable_shared_from_this(), terminating(false), requestCount(0), serverName(std::nullopt), router(std::make_shared()), pool(DB::Pool::create()), taskManager(std::make_shared()), scheduler(std::make_shared(taskManager)), 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); } Server::~Server () {} void Server::run (int socketDescriptor) { std::shared_ptr srv = shared_from_this(); router->addRoute(std::make_unique()); router->addRoute(std::make_unique()); router->addRoute(std::make_unique(srv)); router->addRoute(std::make_unique(srv)); router->addRoute(std::make_unique(srv)); router->addRoute(std::make_unique(srv)); router->addRoute(std::make_unique(srv)); router->addRoute(std::make_unique(srv)); router->addRoute(std::make_unique(srv)); router->addRoute(std::make_unique(srv)); taskManager->start(); scheduler->start(); 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()); auto route = std::make_unique(router, std::move(request)); taskManager->schedule(std::move(route)); } 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; DB::Session s; s.id = 0; int counter = 10; do { try { accessToken = generateRandomString(32); renewToken = generateRandomString(32); DB::Resource db = pool->request(); s = 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 (s.id == 0) throw std::runtime_error("Couldn't create session, ran out of attempts"); std::unique_ptr& session = sessions[accessToken] = std::make_unique(scheduler, s.id, s.owner, s.accessToken, s.renewToken, pollTimout); return *session.get(); } Session& Server::getSession (const std::string& accessToken) { Sessions::const_iterator itr = sessions.find(accessToken); if (itr != sessions.end()) return *(itr->second); DB::Resource db = pool->request(); DB::Session s = db->findSession(accessToken); std::unique_ptr& session = sessions[accessToken] = std::make_unique( scheduler, s.id, s.owner, s.accessToken, s.renewToken, pollTimout ); return *session.get(); } DB::Resource Server::getDatabase () { return pool->request(); }