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)
107 JSON::Login::FormInput* input;
110 input->set_input_id(
"account_name");
111 input->set_type(
"text");
112 input->set_label(
"E-mail");
113 input->set_max_length(320);
116 input->set_input_id(
"password");
117 input->set_type(
"password");
118 input->set_label(
"Password");
119 input->set_max_length(128);
122 input->set_input_id(
"log_in_submit");
123 input->set_type(
"submit");
124 input->set_label(
"Log In");
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>();
242 JSON::Login::LoginResult loginResult;
243 loginResult.set_authentication_state(JSON::Login::LOGIN);
244 loginResult.set_error_code(
"UNABLE_TO_DECODE");
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");
251 return RequestHandlerResult::Handled;
254 auto getInputValue = [](JSON::Login::LoginForm
const* loginForm, std::string_view inputId) -> std::string
256 for (
int32 i = 0; i < loginForm->inputs_size(); ++i)
257 if (loginForm->inputs(i).input_id() == inputId)
258 return loginForm->inputs(i).value();
262 std::string login(getInputValue(loginForm.get(),
"account_name"));
269 .WithChainingPreparedCallback([
this, session, context = std::move(context), loginForm = std::move(loginForm), getInputValue](
QueryCallback& callback,
PreparedQueryResult result)
mutable
273 JSON::Login::LoginResult loginResult;
274 loginResult.set_authentication_state(JSON::Login::DONE);
275 context.response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
276 context.response.body() = ::JSON::Serialize(loginResult);
277 session->SendResponse(context);
281 std::string login(getInputValue(loginForm.get(),
"account_name"));
283 bool passwordCorrect =
false;
286 Field* fields = result->Fetch();
288 if (!session->GetSessionState()->Srp)
294 session->GetSessionState()->Srp = CreateSrpImplementation(version, SrpHashFunction::Sha256, srpUsername, s, v);
296 std::string password(getInputValue(loginForm.get(),
"password"));
300 passwordCorrect = session->GetSessionState()->Srp->CheckCredentials(srpUsername, password);
304 BigNumber A(getInputValue(loginForm.get(),
"public_A"));
305 BigNumber M1(getInputValue(loginForm.get(),
"client_evidence_M1"));
306 if (
Optional<BigNumber> sessionKey = session->GetSessionState()->Srp->VerifyClientEvidence(A, M1))
308 passwordCorrect =
true;
309 serverM2 = session->GetSessionState()->Srp->CalculateServerEvidence(A, M1, *sessionKey).AsHexStr();
314 std::string loginTicket = fields[5].
GetString();
316 bool isBanned = fields[7].
GetUInt64() != 0;
318 if (!passwordCorrect)
322 std::string ip_address = session->GetRemoteIpAddress().to_string();
325 if (
sConfigMgr->GetBoolDefault(
"WrongPass.Logging",
false))
326 TC_LOG_DEBUG(
"server.http.login",
"[{}, Account {}, Id {}] Attempted to connect with wrong password!", ip_address, login, accountId);
328 if (maxWrongPassword)
337 TC_LOG_DEBUG(
"server.http.login",
"MaxWrongPass : {}, failed_login : {}", maxWrongPassword, accountId);
339 if (failedLogins >= maxWrongPassword)
367 JSON::Login::LoginResult loginResult;
368 loginResult.set_authentication_state(JSON::Login::DONE);
370 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
372 session->SendResponse(context);
376 if (loginTicket.empty() || loginTicketExpiry < time(
nullptr))
381 stmt->
setUInt32(1, time(
nullptr) + _loginTicketDuration);
385 JSON::Login::LoginResult loginResult;
386 loginResult.set_authentication_state(JSON::Login::DONE);
387 loginResult.set_login_ticket(loginTicket);
389 loginResult.set_server_evidence_m2(*serverM2);
391 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
393 session->SendResponse(context);
397 return RequestHandlerResult::Async;
402 JSON::Login::LoginForm loginForm;
405 JSON::Login::LoginResult loginResult;
406 loginResult.set_authentication_state(JSON::Login::LOGIN);
407 loginResult.set_error_code(
"UNABLE_TO_DECODE");
408 loginResult.set_error_message(
"There was an internal error while connecting to Battle.net. Please try again later.");
410 context.
response.result(boost::beast::http::status::bad_request);
411 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
414 return RequestHandlerResult::Handled;
419 for (
int32 i = 0; i < loginForm.inputs_size(); ++i)
420 if (loginForm.inputs(i).input_id() ==
"account_name")
421 login = loginForm.inputs(i).value();
429 .WithPreparedCallback([session, context = std::move(context), login = std::move(login)](
PreparedQueryResult result)
mutable
433 JSON::Login::LoginResult loginResult;
434 loginResult.set_authentication_state(JSON::Login::DONE);
435 context.response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
436 context.response.body() = ::JSON::Serialize(loginResult);
437 session->SendResponse(context);
441 Field* fields = result->Fetch();
448 session->GetSessionState()->Srp = CreateSrpImplementation(version, hashFunction, srpUsername, s, v);
449 if (!session->GetSessionState()->Srp)
451 context.
response.result(boost::beast::http::status::internal_server_error);
452 session->SendResponse(context);
456 JSON::Login::SrpLoginChallenge challenge;
457 challenge.set_version(session->GetSessionState()->Srp->GetVersion());
458 challenge.set_iterations(session->GetSessionState()->Srp->GetXIterations());
459 challenge.set_modulus(session->GetSessionState()->Srp->GetN().AsHexStr());
460 challenge.set_generator(session->GetSessionState()->Srp->Getg().AsHexStr());
461 challenge.set_hash_function([=]
463 switch (hashFunction)
465 case SrpHashFunction::Sha256:
467 case SrpHashFunction::Sha512:
474 challenge.set_username(srpUsername);
476 challenge.set_public_b(session->GetSessionState()->Srp->B.AsHexStr());
478 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
480 session->SendResponse(context);
483 return RequestHandlerResult::Async;
488 std::string ticket = ExtractAuthorization(context.
request);
490 return HandleUnauthorized(std::move(session), context);
495 .WithPreparedCallback([
this, session, context = std::move(context), ticket = std::move(ticket)](
PreparedQueryResult result)
mutable
497 JSON::Login::LoginRefreshResult loginRefreshResult;
500 uint32 loginTicketExpiry = (*result)[0].GetUInt32();
501 time_t now = time(nullptr);
502 if (loginTicketExpiry > now)
504 loginRefreshResult.set_login_ticket_expiry(now + _loginTicketDuration);
506 LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_EXISTING_AUTHENTICATION);
507 stmt->setUInt32(0, uint32(now + _loginTicketDuration));
508 stmt->setString(1, ticket);
509 LoginDatabase.Execute(stmt);
512 loginRefreshResult.set_is_expired(true);
515 loginRefreshResult.set_is_expired(
true);
517 context.
response.set(boost::beast::http::field::content_type,
"application/json;charset=utf-8");
519 session->SendResponse(context);
522 return RequestHandlerResult::Async;
525std::unique_ptr<Trinity::Crypto::SRP::BnetSRP6Base> LoginRESTService::CreateSrpImplementation(
SrpVersion version,
SrpHashFunction hashFunction,
530 if (hashFunction == SrpHashFunction::Sha256)
531 return std::make_unique<Trinity::Crypto::SRP::BnetSRP6v2<Trinity::Crypto::SHA256>>(username, salt, verifier);
532 if (hashFunction == SrpHashFunction::Sha512)
533 return std::make_unique<Trinity::Crypto::SRP::BnetSRP6v2<Trinity::Crypto::SHA512>>(username, salt, verifier);
538 if (hashFunction == SrpHashFunction::Sha256)
539 return std::make_unique<Trinity::Crypto::SRP::BnetSRP6v1<Trinity::Crypto::SHA256>>(username, salt, verifier);
540 if (hashFunction == SrpHashFunction::Sha512)
541 return std::make_unique<Trinity::Crypto::SRP::BnetSRP6v1<Trinity::Crypto::SHA512>>(username, salt, verifier);
547std::shared_ptr<Trinity::Net::Http::SessionState> LoginRESTService::CreateNewSessionState(boost::asio::ip::address
const& address)
549 std::shared_ptr<LoginSessionState> state = std::make_shared<LoginSessionState>();
550 InitAndStoreSessionState(state, address);
554void LoginRESTService::MigrateLegacyPasswordHashes()
const
556 if (!
LoginDatabase.Query(
"SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = SCHEMA() AND TABLE_NAME = 'battlenet_accounts' AND COLUMN_NAME = 'sha_pass_hash'"))
559 TC_LOG_INFO(_logger,
"Updating password hashes...");
563 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");
570 bool hadWarning =
false;
575 uint32 const id = (*result)[0].GetUInt32();
581 if ((*result)[2].GetInt64())
588 "(!) You appear to be using an outdated external account management tool.\n"
589 "(!) Update your external tool.\n"
590 "(!!) If no update is available, refer your tool's developer to https://github.com/TrinityCore/TrinityCore/issues/25157.\n"
602 tx->Append(
Trinity::StringFormat(
"UPDATE battlenet_accounts SET sha_pass_hash = DEFAULT(sha_pass_hash) WHERE id = {}",
id).c_str());
604 if (tx->GetSize() >= 10000)
611 }
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)
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 std::string Serialize(google::protobuf::Message const &message)
TC_SHARED_API bool Deserialize(std::string_view json, google::protobuf::Message *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)