41 if (!HttpService::StartNetwork(ioContext, bindIp, port, threadCount))
64 }, RequestHandlerFlag::DoNotLogRequestContent);
134 if (address.is_loopback())
142 using namespace std::string_view_literals;
145 auto itr = request.find(boost::beast::http::field::authorization);
146 if (itr == request.end())
150 constexpr std::string_view BASIC_PREFIX =
"Basic "sv;
152 if (authorization.starts_with(BASIC_PREFIX))
153 authorization.remove_prefix(BASIC_PREFIX.length());
159 std::string_view decodedHeader(
reinterpret_cast<char const*
>(decoded->data()), decoded->size());
161 if (std::size_t ticketEnd = decodedHeader.find(
':'); ticketEnd != std::string_view::npos)
162 decodedHeader.remove_suffix(decodedHeader.length() - ticketEnd);
164 ticket = decodedHeader;
173 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
175 return RequestHandlerResult::Handled;
187 .WithPreparedCallback([session, context = std::move(context)](
PreparedQueryResult result)
mutable
189 JSON::Login::GameAccountList gameAccounts;
192 auto formatDisplayName = [](char const* name) -> std::string
194 if (char const* hashPos = strchr(name,
'#'))
195 return std::string(
"WoW") + ++hashPos;
200 time_t now = time(nullptr);
203 Field* fields = result->Fetch();
204 JSON::Login::GameAccountInfo* gameAccount = gameAccounts.add_game_accounts();
205 gameAccount->set_display_name(formatDisplayName(fields[0].GetCString()));
206 gameAccount->set_expansion(fields[1].GetUInt8());
207 if (!fields[2].IsNull())
209 uint32 banDate = fields[2].GetUInt32();
210 uint32 unbanDate = fields[3].GetUInt32();
211 gameAccount->set_is_suspended(unbanDate > now);
212 gameAccount->set_is_banned(banDate == unbanDate);
213 gameAccount->set_suspension_reason(fields[4].GetString());
214 gameAccount->set_suspension_expires(unbanDate);
216 } while (result->NextRow());
219 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
221 session->SendResponse(context);
224 return RequestHandlerResult::Async;
229 context.
response.set(boost::beast::http::field::content_type,
"text/plain");
231 return RequestHandlerResult::Handled;
236 std::shared_ptr<JSON::Login::LoginForm> loginForm = std::make_shared<JSON::Login::LoginForm>();
242 loginResult.
set_error_message(
"There was an internal error while connecting to Battle.net. Please try again later.");
244 context.
response.result(boost::beast::http::status::bad_request);
245 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
247 session->SendResponse(context);
249 return RequestHandlerResult::Handled;
254 for (
int32 i = 0; i < loginForm->inputs_size(); ++i)
255 if (loginForm->inputs(i).input_id() == inputId)
256 return loginForm->inputs(i).value();
260 std::string login(getInputValue(loginForm.get(),
"account_name"));
267 .WithChainingPreparedCallback([
this, session, context = std::move(context), loginForm = std::move(loginForm), getInputValue](
QueryCallback& callback,
PreparedQueryResult result)
mutable
271 JSON::Login::LoginResult loginResult;
272 loginResult.set_authentication_state(JSON::Login::DONE);
273 context.response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
274 context.response.body() = ::JSON::Serialize(loginResult);
275 session->SendResponse(context);
279 std::string login(getInputValue(loginForm.get(),
"account_name"));
281 bool passwordCorrect =
false;
284 Field* fields = result->Fetch();
286 if (!session->GetSessionState()->Srp)
288 SrpVersion version = SrpVersion(fields[1].GetInt8());
289 std::string srpUsername = ByteArrayToHexStr(Trinity::Crypto::SHA256::GetDigestOf(login));
290 Trinity::Crypto::SRP::Salt s = fields[2].GetBinary<Trinity::Crypto::SRP::SALT_LENGTH>();
291 Trinity::Crypto::SRP::Verifier v = fields[3].GetBinary();
292 session->GetSessionState()->Srp = CreateSrpImplementation(version, SrpHashFunction::Sha256, srpUsername, s, v);
294 std::string password(getInputValue(loginForm.get(),
"password"));
295 if (version == SrpVersion::v1)
296 Utf8ToUpperOnlyLatin(password);
298 passwordCorrect = session->GetSessionState()->Srp->CheckCredentials(srpUsername, password);
302 BigNumber A(getInputValue(loginForm.get(),
"public_A"));
303 BigNumber M1(getInputValue(loginForm.get(),
"client_evidence_M1"));
304 if (Optional<BigNumber> sessionKey = session->GetSessionState()->Srp->VerifyClientEvidence(A, M1))
306 passwordCorrect = true;
307 serverM2 = session->GetSessionState()->Srp->CalculateServerEvidence(A, M1, *sessionKey).AsHexStr();
311 uint32 failedLogins = fields[4].GetUInt32();
312 std::string loginTicket = fields[5].GetString();
313 uint32 loginTicketExpiry = fields[6].GetUInt32();
314 bool isBanned = fields[7].GetUInt64() != 0;
316 if (!passwordCorrect)
320 std::string ip_address = session->GetRemoteIpAddress().to_string();
323 if (
sConfigMgr->GetBoolDefault(
"WrongPass.Logging",
false))
324 TC_LOG_DEBUG(
"server.http.login",
"[{}, Account {}, Id {}] Attempted to connect with wrong password!", ip_address, login, accountId);
326 if (maxWrongPassword)
335 TC_LOG_DEBUG(
"server.http.login",
"MaxWrongPass : {}, failed_login : {}", maxWrongPassword, accountId);
337 if (failedLogins >= maxWrongPassword)
365 JSON::Login::LoginResult loginResult;
368 context.response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
370 session->SendResponse(context);
374 if (loginTicket.empty() || loginTicketExpiry < time(
nullptr))
379 stmt->
setUInt32(1, time(
nullptr) + _loginTicketDuration);
381 callback.WithPreparedCallback([session, context = std::move(context), loginTicket = std::move(loginTicket), serverM2 = std::move(serverM2)](
PreparedQueryResult)
mutable
383 JSON::Login::LoginResult loginResult;
385 loginResult.set_login_ticket(loginTicket);
387 loginResult.set_server_evidence_m2(*serverM2);
389 context.response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
391 session->SendResponse(context);
395 return RequestHandlerResult::Async;
406 loginResult.
set_error_message(
"There was an internal error while connecting to Battle.net. Please try again later.");
408 context.
response.result(boost::beast::http::status::bad_request);
409 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
411 session->SendResponse(context);
413 return RequestHandlerResult::Handled;
428 .WithPreparedCallback([session, context = std::move(context), login = std::move(login)](
PreparedQueryResult result)
mutable
432 JSON::Login::LoginResult loginResult;
433 loginResult.set_authentication_state(JSON::Login::DONE);
434 context.response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
435 context.response.body() = ::JSON::Serialize(loginResult);
436 session->SendResponse(context);
440 Field* fields = result->Fetch();
447 session->GetSessionState()->Srp = CreateSrpImplementation(version, hashFunction, srpUsername, s, v);
448 if (!session->GetSessionState()->Srp)
450 context.
response.result(boost::beast::http::status::internal_server_error);
451 session->SendResponse(context);
456 challenge.
set_version(session->GetSessionState()->Srp->GetVersion());
457 challenge.
set_iterations(session->GetSessionState()->Srp->GetXIterations());
458 challenge.
set_modulus(session->GetSessionState()->Srp->GetN().AsHexStr());
459 challenge.
set_generator(session->GetSessionState()->Srp->Getg().AsHexStr());
462 switch (hashFunction)
464 case SrpHashFunction::Sha256:
466 case SrpHashFunction::Sha512:
475 challenge.
set_public_b(session->GetSessionState()->Srp->B.AsHexStr());
477 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
479 session->SendResponse(context);
482 return RequestHandlerResult::Async;
487 std::string ticket = ExtractAuthorization(context.
request);
489 return HandleUnauthorized(std::move(session), context);
494 .WithPreparedCallback([
this, session, context = std::move(context), ticket = std::move(ticket)](
PreparedQueryResult result)
mutable
496 JSON::Login::LoginRefreshResult loginRefreshResult;
499 uint32 loginTicketExpiry = (*result)[0].GetUInt32();
500 time_t now = time(nullptr);
501 if (loginTicketExpiry > now)
503 loginRefreshResult.set_login_ticket_expiry(now + _loginTicketDuration);
505 LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_EXISTING_AUTHENTICATION);
506 stmt->setUInt32(0, uint32(now + _loginTicketDuration));
507 stmt->setString(1, ticket);
508 LoginDatabase.Execute(stmt);
511 loginRefreshResult.set_is_expired(true);
514 loginRefreshResult.set_is_expired(
true);
516 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
518 session->SendResponse(context);
521 return RequestHandlerResult::Async;
524std::unique_ptr<Trinity::Crypto::SRP::BnetSRP6Base> LoginRESTService::CreateSrpImplementation(
SrpVersion version,
SrpHashFunction hashFunction,
529 if (hashFunction == SrpHashFunction::Sha256)
530 return std::make_unique<Trinity::Crypto::SRP::BnetSRP6v2<Trinity::Crypto::SHA256>>(username, salt, verifier);
531 if (hashFunction == SrpHashFunction::Sha512)
532 return std::make_unique<Trinity::Crypto::SRP::BnetSRP6v2<Trinity::Crypto::SHA512>>(username, salt, verifier);
537 if (hashFunction == SrpHashFunction::Sha256)
538 return std::make_unique<Trinity::Crypto::SRP::BnetSRP6v1<Trinity::Crypto::SHA256>>(username, salt, verifier);
539 if (hashFunction == SrpHashFunction::Sha512)
540 return std::make_unique<Trinity::Crypto::SRP::BnetSRP6v1<Trinity::Crypto::SHA512>>(username, salt, verifier);
546std::shared_ptr<Trinity::Net::Http::SessionState> LoginRESTService::CreateNewSessionState(boost::asio::ip::address
const& address)
548 std::shared_ptr<LoginSessionState> state = std::make_shared<LoginSessionState>();
549 InitAndStoreSessionState(state, address);
558void LoginRESTService::MigrateLegacyPasswordHashes()
const
560 if (!
LoginDatabase.Query(
"SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = SCHEMA() AND TABLE_NAME = 'battlenet_accounts' AND COLUMN_NAME = 'sha_pass_hash'"))
563 TC_LOG_INFO(_logger,
"Updating password hashes...");
567 QueryResult result =
LoginDatabase.Query(
"SELECT id, sha_pass_hash, IF((salt IS null) OR (verifier IS null), 0, 1) AS shouldWarn FROM battlenet_accounts WHERE sha_pass_hash != DEFAULT(sha_pass_hash) OR salt IS NULL OR verifier IS NULL");
574 bool hadWarning =
false;
579 uint32 const id = (*result)[0].GetUInt32();
585 if ((*result)[2].GetInt64())
592 "(!) You appear to be using an outdated external account management tool.\n"
593 "(!) Update your external tool.\n"
594 "(!!) If no update is available, refer your tool's developer to https://github.com/TrinityCore/TrinityCore/issues/25157.\n"
606 tx->Append(
Trinity::StringFormat(
"UPDATE battlenet_accounts SET sha_pass_hash = DEFAULT(sha_pass_hash) WHERE id = {}",
id).c_str());
608 if (tx->GetSize() >= 10000)
615 }
while (result->NextRow());
SQLTransaction< LoginDatabaseConnection > LoginDatabaseTransaction
std::shared_ptr< ResultSet > QueryResult
std::shared_ptr< PreparedResultSet > PreparedQueryResult
DatabaseWorkerPool< LoginDatabaseConnection > LoginDatabase
Accessor to the realm/login database.
#define TC_LOG_WARN(filterType__,...)
#define TC_LOG_DEBUG(filterType__,...)
#define TC_LOG_ERROR(filterType__,...)
#define TC_LOG_INFO(filterType__,...)
@ LOGIN_UPD_BNET_RESET_FAILED_LOGINS
@ LOGIN_SEL_BNET_AUTHENTICATION
@ LOGIN_UPD_BNET_FAILED_LOGINS
@ LOGIN_INS_BNET_ACCOUNT_AUTO_BANNED
@ LOGIN_SEL_BNET_CHECK_PASSWORD_BY_EMAIL
@ LOGIN_UPD_BNET_AUTHENTICATION
@ LOGIN_SEL_BNET_EXISTING_AUTHENTICATION
@ LOGIN_SEL_BNET_GAME_ACCOUNT_LIST
@ LOGIN_INS_IP_AUTO_BANNED
std::optional< T > Optional
Optional helper class to wrap optional values within.
BanMode
Ban function modes.
uint32 GetMSTimeDiffToNow(uint32 oldMSTime)
bool Utf8ToUpperOnlyLatin(std::string &utf8String)
std::string ByteArrayToHexStr(Container const &c, bool reverse=false)
constexpr std::underlying_type< E >::type AsUnderlyingType(E enumValue)
static void OnSocketAccept(boost::asio::ip::tcp::socket &&sock, uint32 threadIndex)
void AsyncAcceptWithCallback()
void set_authentication_state(::Battlenet::JSON::Login::AuthenticationState value)
void set_error_code(const ::std::string &value)
void set_error_message(const ::std::string &value)
void set_iterations(::google::protobuf::uint32 value)
void set_version(::google::protobuf::uint32 value)
void set_salt(const ::std::string &value)
void set_public_b(const ::std::string &value)
void set_hash_function(const ::std::string &value)
void set_username(const ::std::string &value)
void set_modulus(const ::std::string &value)
void set_generator(const ::std::string &value)
std::array< boost::asio::ip::address, 2 > _addresses
RequestHandlerResult HandleGetPortal(std::shared_ptr< LoginHttpSession > session, HttpRequestContext &context) const
std::array< std::string, 2 > _hostnames
RequestHandlerResult HandlePostLogin(std::shared_ptr< LoginHttpSession > session, HttpRequestContext &context) const
RequestHandlerResult HandleGetForm(std::shared_ptr< LoginHttpSession > session, HttpRequestContext &context) const
std::string const & GetHostnameForClient(boost::asio::ip::address const &address) const
static std::string ExtractAuthorization(HttpRequest const &request)
static RequestHandlerResult HandleGetGameAccounts(std::shared_ptr< LoginHttpSession > session, HttpRequestContext &context)
RequestHandlerResult HandlePostRefreshLoginTicket(std::shared_ptr< LoginHttpSession > session, HttpRequestContext &context) const
JSON::Login::FormInputs _formInputs
bool StartNetwork(Trinity::Asio::IoContext &ioContext, std::string const &bindIp, uint16 port, int32 threadCount=1) override
static LoginRESTService & Instance()
static void OnSocketAccept(boost::asio::ip::tcp::socket &&sock, uint32 threadIndex)
uint32 _loginTicketDuration
void MigrateLegacyPasswordHashes() const
static RequestHandlerResult HandlePostLoginSrpChallenge(std::shared_ptr< LoginHttpSession > session, HttpRequestContext &context)
Trinity::Net::Http::Request HttpRequest
BigNumber ModExp(BigNumber const &bn1, BigNumber const &bn2) const
std::vector< uint8 > ToByteVector(int32 minSize=0, bool littleEndian=true) const
Class used to access individual fields of database query result.
void setBinary(const uint8 index, const std::vector< uint8 > &value)
void setUInt32(const uint8 index, const uint32 value)
void setString(const uint8 index, const std::string &value)
void setInt8(const uint8 index, const int8 value)
AsyncAcceptor * _acceptor
Optional< boost::asio::ip::tcp::endpoint > Resolve(boost::asio::ip::tcp const &protocol, std::string const &host, std::string const &service)
static Digest GetDigestOf(uint8 const *data, size_t len)
static RequestHandlerResult HandleUnauthorized(std::shared_ptr< AbstractSocket > session, RequestContext &context)
void RegisterHandler(boost::beast::http::verb method, std::string_view path, Callable handler, RequestHandlerFlag flags=RequestHandlerFlag::None)
TC_SHARED_API bool Deserialize(std::string const &json, google::protobuf::Message *message)
TC_SHARED_API std::string Serialize(google::protobuf::Message const &message)
decltype(auto) post(boost::asio::io_context &ioContext, T &&t)
std::array< uint8, SALT_LENGTH > Salt
std::vector< uint8 > Verifier
static constexpr size_t SALT_LENGTH
std::string_view ToStdStringView(boost::beast::string_view bsw)
Optional< std::size_t > SelectAddressForClient(boost::asio::ip::address const &clientAddress, std::span< boost::asio::ip::address const > const &addresses)
std::string StringFormat(FormatString< Args... > fmt, Args &&... args)
Default TC string format function.
static Optional< std::vector< uint8 > > Decode(std::string_view data)