diff --git a/src/account/account_repository.hpp b/src/account/account_repository.hpp index 0d4dcc7abcf..6dfe2c65668 100644 --- a/src/account/account_repository.hpp +++ b/src/account/account_repository.hpp @@ -27,6 +27,8 @@ class AccountRepository { virtual bool loadBySession(const std::string &email, AccountInfo &acc) = 0; virtual bool save(const AccountInfo &accInfo) = 0; + virtual bool getCharacterByAccountIdAndName(const uint32_t &id, const std::string &name) = 0; + virtual bool getPassword(const uint32_t &id, std::string &password) = 0; virtual bool getCoins(const uint32_t &id, const uint8_t &type, uint32_t &coins) = 0; diff --git a/src/account/account_repository_db.cpp b/src/account/account_repository_db.cpp index b150a636a97..81da30c08c5 100644 --- a/src/account/account_repository_db.cpp +++ b/src/account/account_repository_db.cpp @@ -64,6 +64,16 @@ bool AccountRepositoryDB::save(const AccountInfo &accInfo) { return successful; }; +bool AccountRepositoryDB::getCharacterByAccountIdAndName(const uint32_t &id, const std::string &name) { + auto result = g_database().storeQuery(fmt::format("SELECT `id` FROM `players` WHERE `account_id` = {} AND `name` = {}", id, g_database().escapeString(name))); + if (!result) { + g_logger().error("Failed to get character: [{}] from account: [{}]!", name, id); + return false; + } + + return result->countResults() == 1; +} + bool AccountRepositoryDB::getPassword(const uint32_t &id, std::string &password) { auto result = g_database().storeQuery(fmt::format("SELECT * FROM `accounts` WHERE `id` = {}", id)); if (!result) { diff --git a/src/account/account_repository_db.hpp b/src/account/account_repository_db.hpp index 651600e3bc4..e34d864a090 100644 --- a/src/account/account_repository_db.hpp +++ b/src/account/account_repository_db.hpp @@ -20,6 +20,8 @@ class AccountRepositoryDB final : public AccountRepository { bool loadBySession(const std::string &esseionKey, AccountInfo &acc) override; bool save(const AccountInfo &accInfo) override; + bool getCharacterByAccountIdAndName(const uint32_t &id, const std::string &name) override; + bool getPassword(const uint32_t &id, std::string &password) override; bool getCoins(const uint32_t &id, const uint8_t &type, uint32_t &coins) override; diff --git a/src/io/iologindata.cpp b/src/io/iologindata.cpp index 132cead6412..fce5d0dc293 100644 --- a/src/io/iologindata.cpp +++ b/src/io/iologindata.cpp @@ -19,7 +19,7 @@ #include "enums/account_type.hpp" #include "enums/account_errors.hpp" -bool IOLoginData::gameWorldAuthentication(const std::string &accountDescriptor, const std::string &password, std::string &characterName, uint32_t &accountId, bool oldProtocol) { +bool IOLoginData::gameWorldAuthentication(const std::string &accountDescriptor, const std::string &password, std::string &characterName, uint32_t &accountId, bool oldProtocol, const uint32_t ip) { Account account(accountDescriptor); account.setProtocolCompat(oldProtocol); @@ -38,6 +38,11 @@ bool IOLoginData::gameWorldAuthentication(const std::string &accountDescriptor, } } + if (!g_accountRepository().getCharacterByAccountIdAndName(account.getID(), characterName)) { + g_logger().warn("IP [{}] trying to connect into another account character", convertIPToString(ip)); + return false; + } + if (AccountErrors_t::Ok != enumFromValue(account.load())) { g_logger().error("Failed to load account [{}]", accountDescriptor); return false; diff --git a/src/io/iologindata.hpp b/src/io/iologindata.hpp index 1451cf89778..79fa3b59ad7 100644 --- a/src/io/iologindata.hpp +++ b/src/io/iologindata.hpp @@ -17,7 +17,7 @@ using ItemBlockList = std::list>>; class IOLoginData { public: - static bool gameWorldAuthentication(const std::string &accountDescriptor, const std::string &sessionOrPassword, std::string &characterName, uint32_t &accountId, bool oldProcotol); + static bool gameWorldAuthentication(const std::string &accountDescriptor, const std::string &sessionOrPassword, std::string &characterName, uint32_t &accountId, bool oldProcotol, const uint32_t ip); static uint8_t getAccountType(uint32_t accountId); static void updateOnlineStatus(uint32_t guid, bool login); static bool loadPlayerById(std::shared_ptr player, uint32_t id, bool disableIrrelevantInfo = true); diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 032b9b512d0..aab3f88bd47 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -841,7 +841,7 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage &msg) { } uint32_t accountId; - if (!IOLoginData::gameWorldAuthentication(accountDescriptor, password, characterName, accountId, oldProtocol)) { + if (!IOLoginData::gameWorldAuthentication(accountDescriptor, password, characterName, accountId, oldProtocol, getIP())) { ss.str(std::string()); if (authType == "session") { ss << "Your session has expired. Please log in again."; diff --git a/tests/fixture/account/in_memory_account_repository.hpp b/tests/fixture/account/in_memory_account_repository.hpp index 40dbda38e08..8a294992274 100644 --- a/tests/fixture/account/in_memory_account_repository.hpp +++ b/tests/fixture/account/in_memory_account_repository.hpp @@ -120,6 +120,17 @@ namespace tests { return true; } + bool getCharacterByAccountIdAndName(const uint32_t &id, const std::string &name) final { + for (auto it = accounts.begin(); it != accounts.end(); ++it) { + if (it->second.id == id) { + if (it->second.players.find(name) != it->second.players.end()) { + return true; + } + } + } + return false; + } + InMemoryAccountRepository &reset() { accounts.clear(); coins_.clear(); diff --git a/tests/unit/account/account_test.cpp b/tests/unit/account/account_test.cpp index 9259703c77c..c0c3ebbb832 100644 --- a/tests/unit/account/account_test.cpp +++ b/tests/unit/account/account_test.cpp @@ -592,4 +592,32 @@ suite<"account"> accountTest = [] { expect(acc.load() == enumToValue(AccountErrors_t::Ok)); expect(acc.authenticate()); }; + + test("Account::getCharacterByAccountIdAndName using an account with the given character.") = [&injectionFixture] { + auto [accountRepository] = injectionFixture.get(); + + Account acc { 1 }; + accountRepository.addAccount( + "session-key", + AccountInfo { 1, 1, 1, AccountType::ACCOUNT_TYPE_GOD, { { "Canary", 1 }, { "Canary2", 2 } }, false, getTimeNow() + 24 * 60 * 60 * 1000 } + ); + + const auto hasCharacter = accountRepository.getCharacterByAccountIdAndName(1, "Canary"); + + expect(hasCharacter); + }; + + test("Account::getCharacterByAccountIdAndName using an account without the given character.") = [&injectionFixture] { + auto [accountRepository] = injectionFixture.get(); + + Account acc { 1 }; + accountRepository.addAccount( + "session-key", + AccountInfo { 1, 1, 1, AccountType::ACCOUNT_TYPE_GOD, { { "Canary", 1 }, { "Canary2", 2 } }, false, getTimeNow() + 24 * 60 * 60 * 1000 } + ); + + const auto hasCharacter = accountRepository.getCharacterByAccountIdAndName(1, "Invalid"); + + expect(!hasCharacter); + }; };