Skip to content

Commit

Permalink
Core/Authserver: Re-organize the accounts table (PR #25135)
Browse files Browse the repository at this point in the history
- no longer use sha_pass_hash for anything else core-side (.account, SOAP, RA)
- salt/verifier/session_key are now binary
- old s/v/sha_pass_hash fields kept around for backwards compatibility
- sha_pass_hash is still updated (for now), s/v are not
- sha_pass_hash is only read if s/v have been manually changed
- SRP6 b now uses the full 32 bytes of randomness (instead of randomly only using 19)

(cherry picked from commit 3164b58)
  • Loading branch information
Treeston authored and Shauren committed Aug 3, 2020
1 parent 77380f0 commit 73922d2
Show file tree
Hide file tree
Showing 16 changed files with 181 additions and 66 deletions.
11 changes: 7 additions & 4 deletions sql/base/auth_database.sql
Expand Up @@ -25,10 +25,12 @@ DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Identifier',
`username` varchar(32) NOT NULL DEFAULT '',
`salt` BINARY(32),
`verifier` BINARY(32),
`session_key` BINARY(40) DEFAULT NULL,
`sha_pass_hash` varchar(40) NOT NULL DEFAULT '',
`sessionkey` varchar(128) NOT NULL DEFAULT '',
`v` varchar(64) NOT NULL DEFAULT '',
`s` varchar(64) NOT NULL DEFAULT '',
`v` varchar(64) NOT NULL DEFAULT 'dummy value, use `verifier` instead',
`s` varchar(64) NOT NULL DEFAULT 'dummy value, use `salt` instead',
`token_key` varchar(100) NOT NULL DEFAULT '',
`email` varchar(255) NOT NULL DEFAULT '',
`reg_mail` varchar(255) NOT NULL DEFAULT '',
Expand Down Expand Up @@ -2363,7 +2365,8 @@ INSERT INTO `updates` VALUES
('2020_07_03_00_auth.sql','ED7175E51F248ADC5EF60E7CEA9627CC3191ED4C','RELEASED','2020-07-03 20:09:39',0),
('2020_07_23_00_auth.sql','5F47E1CEECA9F837C85C2DAC7ECD47AED321F502','RELEASED','2020-07-23 19:54:42',0),
('2020_07_24_00_auth.sql','06598162E9C84DDF8AA1F83D0410D056C3F7F69E','RELEASED','2020-07-24 00:44:34',0),
('2020_07_25_00_auth.sql','BE376B619ECB6FE827270D5022F311E20AD6E177','RELEASED','2020-07-25 00:00:49',0);
('2020_07_25_00_auth.sql','BE376B619ECB6FE827270D5022F311E20AD6E177','RELEASED','2020-07-25 00:00:49',0),
('2020_08_02_00_auth.sql','B0290F6558C59262D9DDD8071060A8803DD56930','ARCHIVED','2020-08-02 00:00:00',0);
/*!40000 ALTER TABLE `updates` ENABLE KEYS */;
UNLOCK TABLES;

Expand Down
13 changes: 13 additions & 0 deletions sql/updates/auth/master/2020_08_02_00_auth.sql
@@ -0,0 +1,13 @@
-- AUTH CLEANUP PHASE 3
-- update `account` structure
-- sha_pass_hash/s/v kept around for now, for backwards compatibility
ALTER TABLE `account`
DROP COLUMN `sessionkey`,
ADD COLUMN `salt` BINARY(32) AFTER `username`,
ADD COLUMN `verifier` BINARY(32) AFTER `salt`,
ADD COLUMN `session_key` BINARY(40) AFTER `verifier`,
MODIFY COLUMN `s` VARCHAR(64) NOT NULL DEFAULT 'dummy value, use `salt` instead',
MODIFY COLUMN `v` VARCHAR(64) NOT NULL DEFAULT 'dummy value, use `verifier` instead';

UPDATE `account` SET `salt`=REVERSE(UNHEX(`s`)), `s`=DEFAULT WHERE LENGTH(`s`)=64;
UPDATE `account` SET `verifier`=REVERSE(UNHEX(`v`)), `v`=DEFAULT WHERE LENGTH(`v`)=64;
11 changes: 8 additions & 3 deletions src/common/Cryptography/Authentication/SRP6.cpp
Expand Up @@ -24,7 +24,12 @@
using SHA1 = Trinity::Crypto::SHA1;
using SRP6 = Trinity::Crypto::SRP6;

/*static*/ std::array<uint8, 1> const SRP6::g = { 7 };
/*static*/ std::array<uint8, 1> const SRP6::g = []()
{
std::array<uint8, 1> g_temp;
g_temp[0] = 7;
return g_temp;
}();
/*static*/ std::array<uint8, 32> const SRP6::N = HexStrToByteArray<32>("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", true);
/*static*/ BigNumber const SRP6::_g(SRP6::g);
/*static*/ BigNumber const SRP6::_N(N);
Expand Down Expand Up @@ -54,7 +59,7 @@ using SRP6 = Trinity::Crypto::SRP6;
// merge this into CalculateVerifier once the sha_pass hack finally gets nuked from orbit
/*static*/ SRP6::Verifier SRP6::CalculateVerifierFromHash(SHA1::Digest const& hash, SRP6::Salt const& salt)
{
return _g.ModExp(SHA1::GetDigestOf(salt, hash), _N).ToByteArray<32>(false);
return _g.ModExp(SHA1::GetDigestOf(salt, hash), _N).ToByteArray<32>();
}

/*static*/ SessionKey SRP6::SHA1Interleave(SRP6::EphemeralKey const& S)
Expand Down Expand Up @@ -88,7 +93,7 @@ using SRP6 = Trinity::Crypto::SRP6;
}

SRP6::SRP6(std::string const& username, Salt const& salt, Verifier const& verifier)
: _I(SHA1::GetDigestOf(username)), _b(Crypto::GetRandomBytes<19>()), _v(verifier, false), s(salt), B(_B(_b, _v)) {}
: _I(SHA1::GetDigestOf(username)), _b(Crypto::GetRandomBytes<32>()), _v(verifier), s(salt), B(_B(_b, _v)) {}

Optional<SessionKey> SRP6::VerifyChallengeResponse(EphemeralKey const& A, SHA1::Digest const& clientM)
{
Expand Down
2 changes: 2 additions & 0 deletions src/server/bnetserver/Main.cpp
Expand Up @@ -154,6 +154,8 @@ int main(int argc, char** argv)
if (!StartDB())
return 1;

sSessionMgr.FixLegacyAuthHashes();

// Load IP Location Database
sIPLocation->Load();

Expand Down
45 changes: 45 additions & 0 deletions src/server/bnetserver/Server/SessionManager.cpp
Expand Up @@ -16,6 +16,9 @@
*/

#include "SessionManager.h"
#include "DatabaseEnv.h"
#include "SRP6.h"
#include "Util.h"

bool Battlenet::SessionManager::StartNetwork(Trinity::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int threadCount)
{
Expand All @@ -27,6 +30,48 @@ bool Battlenet::SessionManager::StartNetwork(Trinity::Asio::IoContext& ioContext
return true;
}

void Battlenet::SessionManager::FixLegacyAuthHashes()
{
TC_LOG_INFO("server.bnetserver", "Updating password hashes...");
uint32 const start = getMSTime();
// the auth update query nulls salt/verifier if they cannot be converted
// if they are non-null but s/v have been cleared, that means a legacy tool touched our auth DB (otherwise, the core might've done it itself, it used to use those hacks too)
QueryResult result = LoginDatabase.Query("SELECT id, sha_pass_hash, IF((salt IS null) AND (verifier IS null), 0, 1) AS shouldWarn FROM account WHERE s != DEFAULT(s) OR v != DEFAULT(v) OR salt IS NULL OR verifier IS NULL");
if (!result)
{
TC_LOG_INFO("server.bnetserver", ">> No password hashes to update - this took us %u ms to realize", GetMSTimeDiffToNow(start));
return;
}

bool hadWarning = false;
uint32 c = 0;
LoginDatabaseTransaction tx = LoginDatabase.BeginTransaction();
do
{
uint32 const id = (*result)[0].GetUInt32();
std::pair<Trinity::Crypto::SRP6::Salt, Trinity::Crypto::SRP6::Verifier> registrationData = Trinity::Crypto::SRP6::MakeRegistrationDataFromHash_DEPRECATED_DONOTUSE(
HexStrToByteArray<Trinity::Crypto::SHA1::DIGEST_LENGTH>((*result)[1].GetString())
);

if ((*result)[2].GetInt64() && !hadWarning)
{
hadWarning = true;
TC_LOG_WARN("server.bnetserver", "(!) You appear to be using an outdated external account management tool.\n(!!) This is INSECURE, has been deprecated, and will cease to function entirely in the near future.\n(!) Update your external tool.\n(!!) If no update is available, refer your tool's developer to https://github.com/TrinityCore/TrinityCore/issues/25157.");
}

LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON);
stmt->setBinary(0, registrationData.first);
stmt->setBinary(1, registrationData.second);
stmt->setUInt32(2, id);
tx->Append(stmt);

++c;
} while (result->NextRow());
LoginDatabase.CommitTransaction(tx);

TC_LOG_INFO("server.bnetserver", ">> %u password hashes updated in %u ms", c, GetMSTimeDiffToNow(start));
}

NetworkThread<Battlenet::Session>* Battlenet::SessionManager::CreateThreads() const
{
return new NetworkThread<Session>[GetNetworkThreadCount()];
Expand Down
2 changes: 2 additions & 0 deletions src/server/bnetserver/Server/SessionManager.h
Expand Up @@ -32,6 +32,8 @@ namespace Battlenet

bool StartNetwork(Trinity::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int threadCount = 1) override;

void FixLegacyAuthHashes();

protected:
NetworkThread<Session>* CreateThreads() const override;

Expand Down
7 changes: 7 additions & 0 deletions src/server/database/Database/Field.cpp
Expand Up @@ -16,6 +16,7 @@
*/

#include "Field.h"
#include "Errors.h"
#include "Log.h"
#include "MySQLHacks.h"

Expand Down Expand Up @@ -247,6 +248,12 @@ std::vector<uint8> Field::GetBinary() const
return result;
}

void Field::GetBinarySizeChecked(uint8* buf, size_t length) const
{
ASSERT(data.value && (data.length == length), "Expected %zu-byte binary blob, got %sdata (%u bytes) instead", length, data.value ? "" : "no ", data.length);
memcpy(buf, data.value, length);
}

void Field::SetByteValue(char const* newValue, uint32 length)
{
// This value stores raw bytes that have to be explicitly cast later
Expand Down
11 changes: 11 additions & 0 deletions src/server/database/Database/Field.h
Expand Up @@ -20,6 +20,7 @@

#include "Define.h"
#include "DatabaseEnvFwd.h"
#include <array>
#include <vector>

enum class DatabaseFieldTypes : uint8
Expand Down Expand Up @@ -104,6 +105,14 @@ class TC_DATABASE_API Field
char const* GetCString() const;
std::string GetString() const;
std::vector<uint8> GetBinary() const;
template <size_t S>
std::array<uint8, S> GetBinary() const
{
std::array<uint8, S> buf;
GetBinarySizeChecked(buf.data(), S);
return buf;
}


bool IsNull() const
{
Expand All @@ -129,6 +138,8 @@ class TC_DATABASE_API Field
QueryResultFieldMetadata const* meta;
void LogWrongType(char const* getter) const;
void SetMetadata(QueryResultFieldMetadata const* fieldMeta);

void GetBinarySizeChecked(uint8* buf, size_t size) const;
};

#endif
24 changes: 12 additions & 12 deletions src/server/database/Database/Implementation/LoginDatabase.cpp
Expand Up @@ -33,16 +33,17 @@ void LoginDatabaseConnection::DoPrepareStatements()
PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED_ALL, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 GROUP BY account.id", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED_BY_USERNAME, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 AND username LIKE CONCAT('%%', ?, '%%') GROUP BY account.id", CONNECTION_SYNCH);
PrepareStatement(LOGIN_DEL_ACCOUNT_BANNED, "DELETE FROM account_banned WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_ACCOUNT_INFO_CONTINUED_SESSION, "UPDATE account SET sessionkey = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_CONTINUED_SESSION, "SELECT username, sessionkey FROM account WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_SV, "UPDATE account SET s = ?, v = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_ACCOUNT_INFO_CONTINUED_SESSION, "UPDATE account SET session_key = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_CONTINUED_SESSION, "SELECT username, session_key FROM account WHERE id = ? AND LENGTH(session_key) = 40", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_LOGON, "UPDATE account SET salt = ?, verifier = ?, s = DEFAULT, v = DEFAULT WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_LOGON_LEGACY, "UPDATE account SET sha_pass_hash = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_ACCOUNT_ID_BY_NAME, "SELECT id FROM account WHERE username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_NAME, "SELECT id, username FROM account WHERE username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.sessionkey, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, ba.locale, a.recruiter, a.os, ba.id, aa.SecurityLevel, "
PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.session_key, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, ba.locale, a.recruiter, a.os, ba.id, aa.SecurityLevel, "
"bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id "
"FROM account a LEFT JOIN account r ON a.id = r.recruiter LEFT JOIN battlenet_accounts ba ON a.battlenet_account = ba.id "
"LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID IN (-1, ?) LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 "
"WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_ASYNC);
"WHERE a.username = ? AND LENGTH(a.session_key) = 64 ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_ASYNC);

PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_EMAIL, "SELECT id, username FROM account WHERE email = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_BY_IP, "SELECT id, username FROM account WHERE last_ip = ?", CONNECTION_SYNCH);
Expand All @@ -55,14 +56,13 @@ void LoginDatabaseConnection::DoPrepareStatements()
PrepareStatement(LOGIN_DEL_REALM_CHARACTERS, "DELETE FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_REALM_CHARACTERS, "INSERT INTO realmcharacters (numchars, acctid, realmid) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_SUM_REALM_CHARACTERS, "SELECT SUM(numchars) FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_ACCOUNT, "INSERT INTO account(username, sha_pass_hash, reg_mail, email, joindate, battlenet_account, battlenet_index) VALUES(?, ?, ?, ?, NOW(), ?, ?)", CONNECTION_SYNCH);
PrepareStatement(LOGIN_INS_REALM_CHARACTERS_INIT, "INSERT INTO realmcharacters (realmid, acctid, numchars) SELECT realmlist.id, account.id, 0 FROM realmlist, account LEFT JOIN realmcharacters ON acctid=account.id WHERE acctid IS NULL", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_ACCOUNT, "INSERT INTO account(username, salt, verifier, reg_mail, email, joindate, battlenet_account, battlenet_index) VALUES(?, ?, ?, ?, ?, NOW(), ?, ?)", CONNECTION_SYNCH);
PrepareStatement(LOGIN_INS_REALM_CHARACTERS_INIT, "INSERT INTO realmcharacters (realmid, acctid, numchars) SELECT realmlist.id, account.id, 0 FROM realmlist, account LEFT JOIN realmcharacters ON acctid = account.id WHERE acctid IS NULL", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_EXPANSION, "UPDATE account SET expansion = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_ACCOUNT_LOCK, "UPDATE account SET locked = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_ACCOUNT_LOCK_COUNTRY, "UPDATE account SET lock_country = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_LOG, "INSERT INTO logs (time, realm, type, level, string) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_USERNAME, "UPDATE account SET v = 0, s = 0, username = ?, sha_pass_hash = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_PASSWORD, "UPDATE account SET v = 0, s = 0, sha_pass_hash = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_USERNAME, "UPDATE account SET username = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_EMAIL, "UPDATE account SET email = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_REG_EMAIL, "UPDATE account SET reg_mail = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_MUTE_TIME, "UPDATE account SET mutetime = ? , mutereason = ? , muteby = ? WHERE id = ?", CONNECTION_ASYNC);
Expand All @@ -79,8 +79,8 @@ void LoginDatabaseConnection::DoPrepareStatements()
PrepareStatement(LOGIN_GET_ACCOUNT_ACCESS_GMLEVEL, "SELECT SecurityLevel FROM account_access WHERE AccountID = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_GET_GMLEVEL_BY_REALMID, "SELECT SecurityLevel FROM account_access WHERE AccountID = ? AND (RealmID = ? OR RealmID = -1)", CONNECTION_SYNCH);
PrepareStatement(LOGIN_GET_USERNAME_BY_ID, "SELECT username FROM account WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_CHECK_PASSWORD, "SELECT 1 FROM account WHERE id = ? AND sha_pass_hash = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_CHECK_PASSWORD_BY_NAME, "SELECT 1 FROM account WHERE username = ? AND sha_pass_hash = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_CHECK_PASSWORD, "SELECT salt, verifier FROM account WHERE id = ? AND salt IS NOT NULL AND verifier IS NOT NULL", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_CHECK_PASSWORD_BY_NAME, "SELECT salt, verifier FROM account WHERE username = ? AND salt IS NOT NULL AND verifier IS NOT NULL", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_PINFO, "SELECT a.username, aa.SecurityLevel, a.email, a.reg_mail, a.last_ip, DATE_FORMAT(a.last_login, '%Y-%m-%d %T'), a.mutetime, a.mutereason, a.muteby, a.failed_logins, a.locked, a.OS FROM account a LEFT JOIN account_access aa ON (a.id = aa.AccountID AND (aa.RealmID = ? OR aa.RealmID = -1)) WHERE a.id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_PINFO_BANS, "SELECT unbandate, bandate = unbandate, bannedby, banreason FROM account_banned WHERE id = ? AND active ORDER BY bandate ASC LIMIT 1", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_GM_ACCOUNTS, "SELECT a.username, aa.SecurityLevel FROM account a, account_access aa WHERE a.id=aa.AccountID AND aa.SecurityLevel >= ? AND (aa.realmid = -1 OR aa.realmid = ?)", CONNECTION_SYNCH);
Expand Down Expand Up @@ -123,7 +123,7 @@ void LoginDatabaseConnection::DoPrepareStatements()
" FROM battlenet_accounts ba LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account a ON ba.id = a.battlenet_account"
" LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID = -1 WHERE ba.LoginTicket = ? ORDER BY a.id", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_BNET_LAST_LOGIN_INFO, "UPDATE battlenet_accounts SET last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_BNET_GAME_ACCOUNT_LOGIN_INFO, "UPDATE account SET sessionkey = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_UPD_BNET_GAME_ACCOUNT_LOGIN_INFO, "UPDATE account SET session_key = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_BNET_CHARACTER_COUNTS_BY_ACCOUNT_ID, "SELECT rc.acctid, rc.numchars, r.id, r.Region, r.Battlegroup FROM realmcharacters rc INNER JOIN realmlist r ON rc.realmid = r.id WHERE rc.acctid = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID, "SELECT rc.acctid, rc.numchars, r.id, r.Region, r.Battlegroup FROM realmcharacters rc INNER JOIN realmlist r ON rc.realmid = r.id LEFT JOIN account a ON rc.acctid = a.id WHERE a.battlenet_account = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_BNET_LAST_PLAYER_CHARACTERS, "SELECT lpc.accountId, lpc.region, lpc.battlegroup, lpc.realmId, lpc.characterName, lpc.characterGUID, lpc.lastPlayedTime FROM account_last_played_character lpc LEFT JOIN account a ON lpc.accountId = a.id WHERE a.battlenet_account = ?", CONNECTION_ASYNC);
Expand Down
4 changes: 2 additions & 2 deletions src/server/database/Database/Implementation/LoginDatabase.h
Expand Up @@ -38,7 +38,8 @@ enum LoginDatabaseStatements : uint32
LOGIN_DEL_ACCOUNT_BANNED,
LOGIN_UPD_ACCOUNT_INFO_CONTINUED_SESSION,
LOGIN_SEL_ACCOUNT_INFO_CONTINUED_SESSION,
LOGIN_UPD_SV,
LOGIN_UPD_LOGON,
LOGIN_UPD_LOGON_LEGACY,
LOGIN_SEL_ACCOUNT_ID_BY_NAME,
LOGIN_SEL_ACCOUNT_LIST_BY_NAME,
LOGIN_SEL_ACCOUNT_INFO_BY_NAME,
Expand All @@ -62,7 +63,6 @@ enum LoginDatabaseStatements : uint32
LOGIN_UPD_ACCOUNT_LOCK_COUNTRY,
LOGIN_INS_LOG,
LOGIN_UPD_USERNAME,
LOGIN_UPD_PASSWORD,
LOGIN_UPD_EMAIL,
LOGIN_UPD_REG_EMAIL,
LOGIN_UPD_MUTE_TIME,
Expand Down
6 changes: 6 additions & 0 deletions src/server/database/Database/PreparedStatement.h
Expand Up @@ -96,6 +96,12 @@ class TC_DATABASE_API PreparedStatementBase
void setDouble(const uint8 index, const double value);
void setString(const uint8 index, const std::string& value);
void setBinary(const uint8 index, const std::vector<uint8>& value);
template <size_t Size>
void setBinary(const uint8 index, std::array<uint8, Size> const& value)
{
std::vector<uint8> vec(value.begin(), value.end());
setBinary(index, vec);
}
void setNull(const uint8 index);

uint32 GetIndex() const { return m_index; }
Expand Down

0 comments on commit 73922d2

Please sign in to comment.