48 [](boost::asio::ip::tcp::endpoint
const& endpoint) { return endpoint.address(); });
61 [](boost::asio::ip::tcp::endpoint
const& endpoint) { return endpoint.address(); });
69 if (!HttpService::StartNetwork(ioContext, bindIp, port, threadCount))
92 }, RequestHandlerFlag::DoNotLogRequestContent);
99 RegisterHandler(boost::beast::http::verb::post,
"/bnetserver/refreshLoginTicket/"sv, [
this](std::shared_ptr<LoginHttpSession> session,
HttpRequestContext& context)
138 if (address.is_loopback())
147 auto itr = request.find(boost::beast::http::field::authorization);
148 if (itr == request.end())
152 constexpr std::string_view BASIC_PREFIX =
"Basic "sv;
154 if (authorization.starts_with(BASIC_PREFIX))
155 authorization.remove_prefix(BASIC_PREFIX.length());
161 std::string_view decodedHeader(
reinterpret_cast<char const*
>(decoded->data()), decoded->size());
163 if (std::size_t ticketEnd = decodedHeader.find(
':'); ticketEnd != std::string_view::npos)
164 decodedHeader.remove_suffix(decodedHeader.length() - ticketEnd);
166 ticket = decodedHeader;
176 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
178 return RequestHandlerResult::Handled;
190 .WithPreparedCallback([session, context = std::move(context)](
PreparedQueryResult result)
mutable
192 JSON::Login::GameAccountList gameAccounts;
195 auto formatDisplayName = [](char const* name) -> std::string
197 if (char const* hashPos = strchr(name,
'#'))
198 return std::string(
"WoW") + ++hashPos;
203 time_t now = time(nullptr);
206 Field* fields = result->Fetch();
207 JSON::Login::GameAccountInfo* gameAccount = gameAccounts.add_game_accounts();
208 gameAccount->set_display_name(formatDisplayName(fields[0].GetCString()));
209 gameAccount->set_expansion(fields[1].GetUInt8());
210 if (!fields[2].IsNull())
212 uint32 banDate = fields[2].GetUInt32();
213 uint32 unbanDate = fields[3].GetUInt32();
214 gameAccount->set_is_suspended(unbanDate > now);
215 gameAccount->set_is_banned(banDate == unbanDate);
216 gameAccount->set_suspension_reason(fields[4].GetString());
217 gameAccount->set_suspension_expires(unbanDate);
219 } while (result->NextRow());
222 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
224 session->SendResponse(context);
227 return RequestHandlerResult::Async;
232 context.
response.set(boost::beast::http::field::content_type,
"text/plain");
234 return RequestHandlerResult::Handled;
239 std::shared_ptr<JSON::Login::LoginForm> loginForm = std::make_shared<JSON::Login::LoginForm>();
245 loginResult.
set_error_message(
"There was an internal error while connecting to Battle.net. Please try again later.");
247 context.
response.result(boost::beast::http::status::bad_request);
248 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
250 session->SendResponse(context);
252 return RequestHandlerResult::Handled;
257 for (
int32 i = 0; i < loginForm->inputs_size(); ++i)
258 if (loginForm->inputs(i).input_id() == inputId)
259 return loginForm->inputs(i).value();
263 std::string login(getInputValue(loginForm.get(),
"account_name"));
270 .WithChainingPreparedCallback([
this, session, context = std::move(context), loginForm = std::move(loginForm), getInputValue](
QueryCallback& callback,
PreparedQueryResult result)
mutable
274 JSON::Login::LoginResult loginResult;
275 loginResult.set_authentication_state(JSON::Login::DONE);
276 context.response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
277 context.response.body() = ::JSON::Serialize(loginResult);
278 session->SendResponse(context);
282 std::string login(getInputValue(loginForm.get(),
"account_name"));
284 bool passwordCorrect =
false;
287 Field* fields = result->Fetch();
289 if (!session->GetSessionState()->Srp)
295 session->GetSessionState()->Srp = CreateSrpImplementation(version, SrpHashFunction::Sha256, srpUsername, s, v);
297 std::string password(getInputValue(loginForm.get(),
"password"));
301 passwordCorrect = session->GetSessionState()->Srp->CheckCredentials(srpUsername, password);
305 BigNumber A(getInputValue(loginForm.get(),
"public_A"));
306 BigNumber M1(getInputValue(loginForm.get(),
"client_evidence_M1"));
307 if (
Optional<BigNumber> sessionKey = session->GetSessionState()->Srp->VerifyClientEvidence(A, M1))
309 passwordCorrect =
true;
310 serverM2 = session->GetSessionState()->Srp->CalculateServerEvidence(A, M1, *sessionKey).AsHexStr();
315 std::string loginTicket = fields[5].
GetString();
317 bool isBanned = fields[7].
GetUInt64() != 0;
319 if (!passwordCorrect)
323 std::string ip_address = session->GetRemoteIpAddress().to_string();
326 if (
sConfigMgr->GetBoolDefault(
"WrongPass.Logging",
false))
327 TC_LOG_DEBUG(
"server.http.login",
"[{}, Account {}, Id {}] Attempted to connect with wrong password!", ip_address, login, accountId);
329 if (maxWrongPassword)
338 TC_LOG_DEBUG(
"server.http.login",
"MaxWrongPass : {}, failed_login : {}", maxWrongPassword, accountId);
340 if (failedLogins >= maxWrongPassword)
371 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
373 session->SendResponse(context);
377 if (loginTicket.empty() || loginTicketExpiry < time(
nullptr))
382 stmt->
setUInt32(1, time(
nullptr) + _loginTicketDuration);
392 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
394 session->SendResponse(context);
398 return RequestHandlerResult::Async;
409 loginResult.
set_error_message(
"There was an internal error while connecting to Battle.net. Please try again later.");
411 context.
response.result(boost::beast::http::status::bad_request);
412 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
414 session->SendResponse(context);
416 return RequestHandlerResult::Handled;
431 .WithPreparedCallback([session, context = std::move(context), login = std::move(login)](
PreparedQueryResult result)
mutable
435 JSON::Login::LoginResult loginResult;
436 loginResult.set_authentication_state(JSON::Login::DONE);
437 context.response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
438 context.response.body() = ::JSON::Serialize(loginResult);
439 session->SendResponse(context);
443 Field* fields = result->Fetch();
450 session->GetSessionState()->Srp = CreateSrpImplementation(version, hashFunction, srpUsername, s, v);
451 if (!session->GetSessionState()->Srp)
453 context.
response.result(boost::beast::http::status::internal_server_error);
454 session->SendResponse(context);
459 challenge.
set_version(session->GetSessionState()->Srp->GetVersion());
460 challenge.
set_iterations(session->GetSessionState()->Srp->GetXIterations());
461 challenge.
set_modulus(session->GetSessionState()->Srp->GetN().AsHexStr());
462 challenge.
set_generator(session->GetSessionState()->Srp->Getg().AsHexStr());
465 switch (hashFunction)
467 case SrpHashFunction::Sha256:
469 case SrpHashFunction::Sha512:
478 challenge.
set_public_b(session->GetSessionState()->Srp->B.AsHexStr());
480 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
482 session->SendResponse(context);
485 return RequestHandlerResult::Async;
490 std::string ticket = ExtractAuthorization(context.
request);
492 return HandleUnauthorized(std::move(session), context);
497 .WithPreparedCallback([
this, session, context = std::move(context), ticket = std::move(ticket)](
PreparedQueryResult result)
mutable
499 JSON::Login::LoginRefreshResult loginRefreshResult;
502 uint32 loginTicketExpiry = (*result)[0].GetUInt32();
503 time_t now = time(nullptr);
504 if (loginTicketExpiry > now)
506 loginRefreshResult.set_login_ticket_expiry(now + _loginTicketDuration);
508 LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_EXISTING_AUTHENTICATION);
509 stmt->setUInt32(0, uint32(now + _loginTicketDuration));
510 stmt->setString(1, ticket);
511 LoginDatabase.Execute(stmt);
514 loginRefreshResult.set_is_expired(true);
517 loginRefreshResult.set_is_expired(
true);
519 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
521 session->SendResponse(context);
524 return RequestHandlerResult::Async;
527std::unique_ptr<Trinity::Crypto::SRP::BnetSRP6Base> LoginRESTService::CreateSrpImplementation(
SrpVersion version,
SrpHashFunction hashFunction,
532 if (hashFunction == SrpHashFunction::Sha256)
533 return std::make_unique<Trinity::Crypto::SRP::BnetSRP6v2<Trinity::Crypto::SHA256>>(username, salt, verifier);
534 if (hashFunction == SrpHashFunction::Sha512)
535 return std::make_unique<Trinity::Crypto::SRP::BnetSRP6v2<Trinity::Crypto::SHA512>>(username, salt, verifier);
540 if (hashFunction == SrpHashFunction::Sha256)
541 return std::make_unique<Trinity::Crypto::SRP::BnetSRP6v1<Trinity::Crypto::SHA256>>(username, salt, verifier);
542 if (hashFunction == SrpHashFunction::Sha512)
543 return std::make_unique<Trinity::Crypto::SRP::BnetSRP6v1<Trinity::Crypto::SHA512>>(username, salt, verifier);
549std::shared_ptr<Trinity::Net::Http::SessionState> LoginRESTService::CreateNewSessionState(boost::asio::ip::address
const& address)
551 std::shared_ptr<LoginSessionState> state = std::make_shared<LoginSessionState>();
552 InitAndStoreSessionState(state, address);
556void LoginRESTService::MigrateLegacyPasswordHashes()
const
558 if (!
LoginDatabase.Query(
"SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = SCHEMA() AND TABLE_NAME = 'battlenet_accounts' AND COLUMN_NAME = 'sha_pass_hash'"))
561 TC_LOG_INFO(_logger,
"Updating password hashes...");
565 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");
572 bool hadWarning =
false;
577 uint32 const id = (*result)[0].GetUInt32();
583 if ((*result)[2].GetInt64())
590 "(!) You appear to be using an outdated external account management tool.\n"
591 "(!) Update your external tool.\n"
592 "(!!) If no update is available, refer your tool's developer to https://github.com/TrinityCore/TrinityCore/issues/25157.\n"
604 tx->Append(
Trinity::StringFormat(
"UPDATE battlenet_accounts SET sha_pass_hash = DEFAULT(sha_pass_hash) WHERE id = {}",
id).c_str());
606 if (tx->GetSize() >= 10000)
613 }
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_DEBUG(filterType__, message__,...)
#define TC_LOG_ERROR(filterType__, message__,...)
#define TC_LOG_INFO(filterType__, message__,...)
#define TC_LOG_WARN(filterType__, message__,...)
@ 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)
void set_authentication_state(::Battlenet::JSON::Login::AuthenticationState value)
void set_server_evidence_m2(const ::std::string &value)
void set_error_code(const ::std::string &value)
void set_login_ticket(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)
RequestHandlerResult HandleGetPortal(std::shared_ptr< LoginHttpSession > session, HttpRequestContext &context) const
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
std::size_t _firstLocalAddressIndex
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()
uint32 _loginTicketDuration
std::string _localHostname
std::vector< boost::asio::ip::address > _addresses
void MigrateLegacyPasswordHashes() const
static RequestHandlerResult HandlePostLoginSrpChallenge(std::shared_ptr< LoginHttpSession > session, HttpRequestContext &context)
std::string _externalHostname
Trinity::Net::Http::Request HttpRequest
static bool UsesDevWildcardCertificate()
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.
std::vector< uint8 > GetBinary() const noexcept
uint64 GetUInt64() const noexcept
uint32 GetUInt32() const noexcept
std::string GetString() const noexcept
void setInt8(uint8 index, int8 value)
void setBinary(uint8 index, std::vector< uint8 > &&value)
void setString(uint8 index, std::string &&value)
void setUInt32(uint8 index, uint32 value)
QueryCallback && WithPreparedCallback(std::function< void(PreparedQueryResult)> &&callback)
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)
std::vector< boost::asio::ip::tcp::endpoint > ResolveAll(std::string_view host, std::string_view service)
TC_SHARED_API bool Deserialize(std::string const &json, google::protobuf::Message *message)
TC_SHARED_API std::string Serialize(google::protobuf::Message const &message)
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) noexcept
Default TC string format function.
static Optional< std::vector< uint8 > > Decode(std::string_view data)