diff options
author | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2023-04-21 11:36:47 +0200 |
---|---|---|
committer | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2023-05-05 16:03:23 +0000 |
commit | ee4624e7904f37c411fc7215d7c41d7a1b6b7d4f (patch) | |
tree | 0af36a4da03b7372dc34f9b08f7ee5ed958da90b /src/account-server/main-account.cpp | |
parent | d9ae7419ab16d6e91469fcdc6a89dbc3afc5a030 (diff) | |
download | manaserv-ee4624e7904f37c411fc7215d7c41d7a1b6b7d4f.tar.gz manaserv-ee4624e7904f37c411fc7215d7c41d7a1b6b7d4f.tar.bz2 manaserv-ee4624e7904f37c411fc7215d7c41d7a1b6b7d4f.tar.xz manaserv-ee4624e7904f37c411fc7215d7c41d7a1b6b7d4f.zip |
Added support for logging in with Stellar
* Added PAMSG_STELLAR_LOGIN / PAMSG_STELLAR_LOGIN_RESPONSE, which is
used by the client to request a login token that can be signed using a
Stellar wallet.
* Added uWebSockets dependency, used to listen for a separate server
that verifies signed tokens (the Stellar Bridge). When a token is
verified, it is sent to manaserv-account, which then sends a
APMSG_LOGIN_RESPONSE to the client matching the token.
* Added RapidJSON dependency to parse the JSON WebSocket messages.
* To keep everything in a single thread, uWebSockets is now driving the
event loop. Processing of ENet hosts, writing stats and expired bans
have been moved to uSocket timers.
* C++ standard updated to C++17, as required by uWebSockets.
When Stellar is used to login, the public key is used as the username.
It might be better to introduce an explicit field for this, especially
when we want to enable an account to feature both Stellar login as well
as login with username / password.
Diffstat (limited to 'src/account-server/main-account.cpp')
-rw-r--r-- | src/account-server/main-account.cpp | 131 |
1 files changed, 105 insertions, 26 deletions
diff --git a/src/account-server/main-account.cpp b/src/account-server/main-account.cpp index 73382f23..4d5bf02a 100644 --- a/src/account-server/main-account.cpp +++ b/src/account-server/main-account.cpp @@ -45,7 +45,6 @@ #include "utils/processorutils.h" #include "utils/stringfilter.h" #include "utils/time.h" -#include "utils/timer.h" #include <cstdlib> #include <getopt.h> @@ -54,6 +53,8 @@ #include <fstream> #include <physfs.h> #include <enet/enet.h> +#include <rapidjson/document.h> +#include <App.h> using utils::Logger; @@ -185,20 +186,26 @@ static void deinitializeServer() /** * Dumps statistics. */ -static void dumpStatistics(std::string accountAddress, int accountClientPort, - int accountGamePort, int chatClientPort) +static void dumpStatistics() { - std::ofstream os(statisticsFile.c_str()); + std::ofstream os(statisticsFile); + os << "<statistics>\n"; + // Print last heartbeat os << "<heartbeat=\"" << utils::getCurrentDate() << "_" - << utils::getCurrentTime() << "\" />\n"; + << utils::getCurrentTime() << "\" />\n"; + // Add account server information - os << "<accountserver address=\"" << accountAddress << "\" clientport=\"" - << accountClientPort << "\" gameport=\"" << accountGamePort - << "\" chatclientport=\"" << chatClientPort << "\" />\n"; + auto accountHandler = AccountClientHandler::getConnectionHandler(); + auto gameHandler = GameServerHandler::getConnectionHandler(); + os << "<accountserver address=\"" << accountHandler->getHostname() << "\" clientport=\"" + << accountHandler->getPort() << "\" gameport=\"" << gameHandler->getPort() + << "\" chatclientport=\"" << chatHandler->getPort() << "\" />\n"; + // Add game servers information GameServerHandler::dumpStatistics(os); + os << "</statistics>\n"; } @@ -333,6 +340,8 @@ int main(int argc, char *argv[]) "localhost"); std::string accountListenToGameHost = Configuration::getValue("net_accountListenToGameHost", accountHost); + std::string accountListenToStellarBridgeHost = Configuration::getValue("net_accountListenToStellarBridgeHost", + accountListenToGameHost); // We separate the chat host as the chat server will be separated out // from the account server. @@ -350,6 +359,8 @@ int main(int argc, char *argv[]) options.port + 1); int chatClientPort = Configuration::getValue("net_chatListenToClientPort", options.port + 2); + int accountStellarBridgePort = Configuration::getValue("net_accountListenToStellarBridgePort", + options.port + 4); bool debugNetwork = Configuration::getBoolValue("net_debugMode", false); MessageOut::setDebugModeEnabled(debugNetwork); @@ -363,13 +374,78 @@ int main(int argc, char *argv[]) return EXIT_NET_EXCEPTION; } - // Dump statistics every 10 seconds. - utils::Timer statTimer(10000); - // Check for expired bans every 30 seconds - utils::Timer banTimer(30000); - - statTimer.start(); - banTimer.start(); + struct us_loop_t *loop = (struct us_loop_t *) uWS::Loop::get(); + struct us_timer_t *processTimer = us_create_timer(loop, 0, 0); + struct us_timer_t *statTimer = us_create_timer(loop, 0, 0); + struct us_timer_t *banTimer = us_create_timer(loop, 0, 0); + + constexpr int processTimerIntervalMs = 1000 / 20; // 20 times per second + constexpr int statTimerIntervalMs = 10 * 1000; // dump statistics every 10 seconds + constexpr int banTimerIntervalMs = 30 * 1000; // check bans every 30 seconds + + us_timer_set(processTimer, [](struct us_timer_t *) { + AccountClientHandler::getConnectionHandler()->process(); + GameServerHandler::getConnectionHandler()->process(); + chatHandler->process(); + }, processTimerIntervalMs, processTimerIntervalMs); + + us_timer_set(statTimer, [](struct us_timer_t *) { + dumpStatistics(); + }, statTimerIntervalMs, statTimerIntervalMs); + + us_timer_set(banTimer, [](struct us_timer_t *) { + storage->checkBannedAccounts(); + }, banTimerIntervalMs, banTimerIntervalMs); + + uWS::App wsApp; + + struct PerSocketData {}; + wsApp.ws<PerSocketData>("/stellar-auth", { + .maxPayloadLength = 1024, // we only expect small messages + .open = [](auto *ws) { + std::cout << "WebSocket opened " << ws->getRemoteAddressAsText() << std::endl; + }, + .message = [](auto *ws, std::string_view message, uWS::OpCode /*opCode*/) { + rapidjson::Document d; + d.Parse(message.data(), message.size()); + if (d.HasParseError()) { + LOG_ERROR("WebSocket: received JSON parse error"); + return; + } + if (!d.IsObject()) { + std::cout << "not an object" << std::endl; + LOG_ERROR("WebSocket: received JSON not an object"); + return; + } + + const auto pubkeyMember = d.FindMember("pubkey"); + const auto tokenMember = d.FindMember("token"); + + if (pubkeyMember == d.MemberEnd() || tokenMember == d.MemberEnd()) { + LOG_ERROR("WebSocket: received JSON missing pubkey or token"); + return; + } + if (!pubkeyMember->value.IsString() || !tokenMember->value.IsString()) { + LOG_ERROR("WebSocket: received JSON pubkey or token not a string"); + return; + } + + const std::string pubkey = pubkeyMember->value.GetString(); + const std::string token = tokenMember->value.GetString(); + + AccountClientHandler::handleStellarLogin(token, pubkey); + }, + .close = [](auto *ws, int code, std::string_view message) { + std::cout << "WebSocket closed " << ws->getRemoteAddressAsText() << " with code " << code << ": " << message << std::endl; + } + }).listen(accountListenToStellarBridgeHost, accountStellarBridgePort, [](us_listen_socket_t *listen_socket) { + if (listen_socket) { + /* Note that us_listen_socket_t is castable to us_socket_t */ + std::cout << "WebSocket: Thread " << std::this_thread::get_id() << " listening on port " << us_socket_local_port(1, (struct us_socket_t *) listen_socket) << std::endl; + } else { + std::cout << "WebSocket: Thread " << std::this_thread::get_id() << " failed to listen on port 3000" << std::endl; + } + }); // Write startup time to database as system world state variable std::stringstream timestamp; @@ -377,19 +453,22 @@ int main(int argc, char *argv[]) storage->setWorldStateVar("accountserver_startup", timestamp.str(), Storage::SystemMap); - while (running) - { - AccountClientHandler::process(); - GameServerHandler::process(); - chatHandler->process(50); + // Start a thread to close the sockets and timers when running is set to false + std::thread([&wsApp,processTimer,statTimer,banTimer] { + while (running) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } - if (statTimer.poll()) - dumpStatistics(accountHost, options.port, accountGamePort, - chatClientPort); + LOG_INFO("Closing uWebSockets app..."); + wsApp.close(); - if (banTimer.poll()) - storage->checkBannedAccounts(); - } + LOG_INFO("Closing uSockets timers..."); + us_timer_close(processTimer); + us_timer_close(statTimer); + us_timer_close(banTimer); + }).detach(); + + wsApp.run(); LOG_INFO("Received: Quit signal, closing down..."); chatHandler->stopListen(); |