TrinityCore
Loading...
Searching...
No Matches
AuthenticationService.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
20#include "Client/api/client/v2/authentication_listener.pb.h"
21#include "Client/challenge_service.pb.h"
22#include "CryptoRandom.h"
23#include "DatabaseEnv.h"
24#include "IPLocation.h"
25#include "LoginRESTService.h"
26#include "Session.h"
27#include "SslContext.h"
28#include "Timezone.h"
29#include <rapidjson/document.h>
30
31namespace Battlenet::Services
32{
33namespace Shared
34{
35uint32 Authentication::HandleLogon(Session* session, ClientBuild::Program::Id program, std::string_view platform,
36 std::string_view locale, uint32 applicationVersion, std::string_view deviceId)
37{
38 if (program != ClientBuild::Program::WoW)
39 {
40 TC_LOG_DEBUG("session", "[Battlenet::Authentication::LogonRequest] {} attempted to log in with game other than WoW (using {})!", session->GetClientInfo(), program);
41 return ERROR_BAD_PROGRAM;
42 }
43
44 if (!ClientBuild::Platform::IsValid(platform))
45 {
46 TC_LOG_DEBUG("session", "[Battlenet::Authentication::LogonRequest] {} attempted to log in from an unsupported platform (using {})!", session->GetClientInfo(), platform);
47 return ERROR_BAD_PLATFORM;
48 }
49
50 if (!IsValidLocale(GetLocaleByName(locale)))
51 {
52 TC_LOG_DEBUG("session", "[Battlenet::Authentication::LogonRequest] {} attempted to log in with unsupported locale (using {})!", session->GetClientInfo(), locale);
53 return ERROR_BAD_LOCALE;
54 }
55
56 Minutes timezoneOffset = [&]
57 {
58 if (deviceId.empty())
59 return 0min;
60
61 rapidjson::Document doc;
62 doc.Parse(deviceId.data(), deviceId.length());
63 if (doc.HasParseError())
64 return 0min;
65
66 auto itr = doc.FindMember("UTCO");
67 if (itr == doc.MemberEnd())
68 return 0min;
69
70 if (!itr->value.IsUint())
71 return 0min;
72
73 return Trinity::Timezone::GetOffsetByHash(itr->value.GetUint());
74 }();
75
76 session->OnLogon(platform, locale, applicationVersion, timezoneOffset);
77 return ERROR_OK;
78}
79
80uint32 Authentication::HandleVerifyAuthToken(Session* session, std::string_view authToken,
81 std::function<void(uint32)> sendResponse, std::function<void(AccountInfo const*, std::string_view)> sendLogonComplete)
82{
84 stmt->setString(0, authToken);
85
86 std::shared_ptr<AccountInfo> accountInfo = std::make_shared<AccountInfo>();
87 session->QueueQuery(LoginDatabase.AsyncQuery(stmt).WithChainingPreparedCallback([accountInfo, sendResponse](QueryCallback& callback, PreparedQueryResult result)
88 {
89 if (!result)
90 {
91 sendResponse(ERROR_DENIED);
92 return;
93 }
94
95 accountInfo->LoadResult(result);
96
97 if (accountInfo->LoginTicketExpiry < time(nullptr))
98 {
99 sendResponse(ERROR_TIMED_OUT);
100 return;
101 }
102
104 stmt->setUInt32(0, accountInfo->Id);
105 callback.SetNextQuery(LoginDatabase.AsyncQuery(stmt));
106 })
107 .WithChainingPreparedCallback([accountInfo](QueryCallback& callback, PreparedQueryResult characterCountsResult)
108 {
109 if (characterCountsResult)
110 {
111 do
112 {
113 Field* fields = characterCountsResult->Fetch();
114 accountInfo->GameAccounts[fields[0].GetUInt32()]
115 .CharacterCounts[Battlenet::RealmHandle{ fields[3].GetUInt8(), fields[4].GetUInt8(), fields[2].GetUInt32() }.GetAddress()] = fields[1].GetUInt8();
116
117 } while (characterCountsResult->NextRow());
118 }
119
121 stmt->setUInt32(0, accountInfo->Id);
122 callback.SetNextQuery(LoginDatabase.AsyncQuery(stmt));
123 })
124 .WithPreparedCallback([session, accountInfo, sendResponse, sendLogonComplete](PreparedQueryResult lastPlayerCharactersResult)
125 {
126 if (lastPlayerCharactersResult)
127 {
128 do
129 {
130 Field* fields = lastPlayerCharactersResult->Fetch();
131 Battlenet::RealmHandle realmId{ fields[1].GetUInt8(), fields[2].GetUInt8(), fields[3].GetUInt32() };
132 Battlenet::LastPlayedCharacterInfo& lastPlayedCharacter = accountInfo->GameAccounts[fields[0].GetUInt32()]
133 .LastPlayedCharacters[realmId.GetSubRegionAddress()];
134
135 lastPlayedCharacter.RealmId = realmId;
136 lastPlayedCharacter.CharacterName = fields[4].GetString();
137 lastPlayedCharacter.CharacterGUID = fields[5].GetUInt64();
138 lastPlayedCharacter.LastPlayedTime = fields[6].GetUInt32();
139
140 } while (lastPlayerCharactersResult->NextRow());
141 }
142
143 std::string ip_address = session->GetRemoteIpAddress().to_string();
144
145 std::string ipCountry;
146 if (IpLocationRecord const* location = sIPLocation->GetLocationRecord(ip_address))
147 ipCountry = location->CountryCode;
148
149 // If the IP is 'locked', check that the player comes indeed from the correct IP address
150 if (accountInfo->IsLockedToIP)
151 {
152 TC_LOG_DEBUG("session", "[Session::HandleVerifyWebCredentials] Account '{}' is locked to IP - '{}' is logging in from '{}'",
153 accountInfo->Login, accountInfo->LastIP, ip_address);
154
155 if (accountInfo->LastIP != ip_address)
156 {
157 sendResponse(ERROR_RISK_ACCOUNT_LOCKED);
158 return;
159 }
160 }
161 else
162 {
163 TC_LOG_DEBUG("session", "[Session::HandleVerifyWebCredentials] Account '{}' is not locked to ip", accountInfo->Login);
164 if (accountInfo->LockCountry.empty() || accountInfo->LockCountry == "00")
165 TC_LOG_DEBUG("session", "[Session::HandleVerifyWebCredentials] Account '{}' is not locked to country", accountInfo->Login);
166 else if (!accountInfo->LockCountry.empty() && !ipCountry.empty())
167 {
168 TC_LOG_DEBUG("session", "[Session::HandleVerifyWebCredentials] Account '{}' is locked to country: '{}' Player country is '{}'",
169 accountInfo->Login, accountInfo->LockCountry, ipCountry);
170
171 if (ipCountry != accountInfo->LockCountry)
172 {
173 sendResponse(ERROR_RISK_ACCOUNT_LOCKED);
174 return;
175 }
176 }
177 }
178
179 // If the account is banned, reject the logon attempt
180 if (accountInfo->IsBanned)
181 {
182 if (accountInfo->IsPermanenetlyBanned)
183 {
184 TC_LOG_DEBUG("session", "{} [Session::HandleVerifyWebCredentials] Banned account {} tried to login!", session->GetClientInfo(), accountInfo->Login);
185 sendResponse(ERROR_GAME_ACCOUNT_BANNED);
186 return;
187 }
188 else
189 {
190 TC_LOG_DEBUG("session", "{} [Session::HandleVerifyWebCredentials] Temporarily banned account {} tried to login!", session->GetClientInfo(), accountInfo->Login);
191 sendResponse(ERROR_GAME_ACCOUNT_SUSPENDED);
192 return;
193 }
194 }
195
196 sendResponse(ERROR_OK);
197 sendLogonComplete(accountInfo.get(), ipCountry);
198
199 session->OnLogonSuccess(std::move(accountInfo), ipCountry);
200 }));
201
202 return ERROR_OK;
203}
204
205uint32 Authentication::HandleGenerateAuthToken(Session* session, std::function<void(std::string_view)> sendResponse)
206{
208 stmt->setUInt32(0, session->GetAccountId());
209
210 session->QueueQuery(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback([sendResponse = std::move(sendResponse)](PreparedQueryResult result)
211 {
212 // just send existing credentials back (not the best but it works for now with them being stored in db)
213 sendResponse((*result)[0].GetStringView());
214 }));
215
216 return ERROR_OK;
217}
218}
219
220namespace V1
221{
222Authentication::Authentication(Session* session) : AuthenticationService(session)
223{
224}
225
226uint32 Authentication::HandleLogon(authentication::v1::LogonRequest const* request, NoData* /*response*/, std::function<void(ServiceBase*, uint32, google::protobuf::Message const*)>& continuation)
227{
229 std::string_view deviceId;
230
231 if (request->has_device_id())
232 deviceId = request->device_id();
233
234 uint32 result = Shared::Authentication::HandleLogon(_session, titleId, request->platform(), request->locale(), request->application_version(), deviceId);
235 if (result == ERROR_OK)
236 {
237 if (request->has_cached_web_credentials())
238 return HandleVerifyWebCredentials(request->cached_web_credentials(), continuation);
239
240 challenge::v1::ChallengeExternalRequest externalChallenge;
241 externalChallenge.set_payload_type("web_auth_url");
242 externalChallenge.set_payload(Trinity::StringFormat("http{}://{}:{}/bnetserver/login/", !SslContext::UsesDevWildcardCertificate() ? "s" : "",
243 sLoginService.GetHostnameForClient(_session->GetRemoteIpAddress()), sLoginService.GetPort()));
244 Service<challenge::v1::ChallengeListener>(_session).OnExternalChallenge(&externalChallenge);
245 }
246
247 return result;
248}
249
250uint32 Authentication::HandleVerifyWebCredentials(authentication::v1::VerifyWebCredentialsRequest const* request, NoData* /*response*/, std::function<void(ServiceBase*, uint32, google::protobuf::Message const*)>& continuation)
251{
252 if (!request->has_web_credentials())
253 return ERROR_DENIED;
254
255 return HandleVerifyWebCredentials(request->web_credentials(), continuation);
256}
257
258uint32 Authentication::HandleVerifyWebCredentials(std::string_view webCredentials,
259 std::function<void(ServiceBase*, uint32, google::protobuf::Message const*)>& continuation)
260{
261 return Shared::Authentication::HandleVerifyAuthToken(_session, webCredentials, [session = _session, continuation = std::move(continuation)](uint32 result) mutable
262 {
263 Authentication asyncContinuationService(session);
264 NoData response;
265 continuation(&asyncContinuationService, result, &response);
266 },
267 [session = _session](AccountInfo const* accountInfo, std::string_view country)
268 {
269 authentication::v1::LogonResult logonResult;
270 logonResult.set_error_code(0);
271 logonResult.mutable_account_id()->set_low(accountInfo->Id);
272 logonResult.mutable_account_id()->set_high(UI64LIT(0x100000000000000));
273 for (auto const& [id, gameAccountInfo] : accountInfo->GameAccounts)
274 {
275 EntityId* gameAccountId = logonResult.add_game_account_id();
276 gameAccountId->set_low(gameAccountInfo.Id);
277 gameAccountId->set_high(UI64LIT(0x200000200576F57));
278 }
279
280 if (!country.empty())
281 logonResult.set_geoip_country(country.data(), country.size());
282
283 std::array<uint8, 64> k = Trinity::Crypto::GetRandomBytes<64>();
284 logonResult.set_session_key(k.data(), 64);
285
286 Service<authentication::v1::AuthenticationListener>(session).OnLogonComplete(&logonResult);
287 });
288}
289
290uint32 Authentication::HandleGenerateWebCredentials(authentication::v1::GenerateWebCredentialsRequest const* /*request*/, authentication::v1::GenerateWebCredentialsResponse* /*response*/, std::function<void(ServiceBase*, uint32, google::protobuf::Message const*)>& continuation)
291{
292 if (!_session->IsAuthed())
293 return ERROR_DENIED;
294
295 return Shared::Authentication::HandleGenerateAuthToken(_session, [session = _session, continuation = std::move(continuation)](std::string_view webCredentials)
296 {
297 Authentication asyncContinuationService(session);
298 authentication::v1::GenerateWebCredentialsResponse response;
299 response.set_web_credentials(webCredentials.data(), webCredentials.size());
300 continuation(&asyncContinuationService, ERROR_OK, &response);
301 });
302}
303}
304
305namespace V2
306{
310
311uint32 Authentication::HandleLogon(authentication::v2::client::LogonRequest const* request, NoData* /*response*/,
312 std::function<void(ServiceBase*, uint32, google::protobuf::Message const*)>& continuation)
313{
314 ClientBuild::Program::Id titleId{ request->title_id() };
315 std::string_view deviceId;
316 std::string_view cachedAuthToken;
317
318 if (request->has_logon_options())
319 {
320 authentication::v2::client::LogonOptions const& logonOptions = request->logon_options();
321 if (logonOptions.has_device_id())
322 deviceId = logonOptions.device_id();
323
324 if (logonOptions.has_auth_token())
325 cachedAuthToken = logonOptions.auth_token();
326 }
327
328 uint32 result = Shared::Authentication::HandleLogon(_session, titleId, request->platform(), request->locale(), request->application_version(), deviceId);
329 if (result == ERROR_OK)
330 {
331 if (!cachedAuthToken.empty())
332 return HandleVerifyAuthToken(cachedAuthToken, continuation);
333
334 authentication::v2::client::ExternalChallengeNotification externalChallenge;
335 externalChallenge.set_payload_type("web_auth_url");
336 externalChallenge.set_payload(Trinity::StringFormat("http{}://{}:{}/bnetserver/login/", !SslContext::UsesDevWildcardCertificate() ? "s" : "",
337 sLoginService.GetHostnameForClient(_session->GetRemoteIpAddress()), sLoginService.GetPort()));
338 Service<authentication::v2::client::AuthenticationListener>(_session).OnExternalChallenge(&externalChallenge);
339 }
340
341 return result;
342}
343
344uint32 Authentication::HandleVerifyAuthToken(authentication::v2::client::VerifyAuthTokenRequest const* request, NoData* /*response*/,
345 std::function<void(ServiceBase*, uint32, google::protobuf::Message const*)>& continuation)
346{
347 if (!request->has_auth_token())
348 return ERROR_DENIED;
349
350 return HandleVerifyAuthToken(request->auth_token(), continuation);
351}
352
353uint32 Authentication::HandleGenerateAuthToken(authentication::v2::client::GenerateAuthTokenRequest const* /*request*/,
354 authentication::v2::client::GenerateAuthTokenResponse* /*response*/, std::function<void(ServiceBase*, uint32, google::protobuf::Message const*)>& continuation)
355{
356 if (!_session->IsAuthed())
357 return ERROR_DENIED;
358
359 return Shared::Authentication::HandleGenerateAuthToken(_session, [session = _session, continuation = std::move(continuation)](std::string_view webCredentials)
360 {
361 Authentication asyncContinuationService(session);
362 authentication::v2::client::GenerateAuthTokenResponse response;
363 response.set_auth_token(webCredentials.data(), webCredentials.size());
364 continuation(&asyncContinuationService, ERROR_OK, &response);
365 });
366}
367
368uint32 Authentication::HandleVerifyAuthToken(std::string_view authToken, std::function<void(ServiceBase*, uint32, google::protobuf::Message const*)>& continuation)
369{
370 return Shared::Authentication::HandleVerifyAuthToken(_session, authToken, [session = _session, continuation = std::move(continuation)](uint32 result) mutable
371 {
372 Authentication asyncContinuationService(session);
373 NoData response;
374 continuation(&asyncContinuationService, result, &response);
375 },
376 [session = _session](AccountInfo const* accountInfo, std::string_view country)
377 {
378 authentication::v2::client::LogonCompleteNotification logonResult;
379 logonResult.set_error_code(0);
380 authentication::v2::client::LogonRecord* logonRecord = logonResult.mutable_record();
381 logonRecord->set_account_id(accountInfo->Id);
382 for (auto const& [id, gameAccountInfo] : accountInfo->GameAccounts)
383 {
384 account::v2::GameAccountHandle* gameAccount = logonRecord->add_game_account();
385 gameAccount->set_id(gameAccountInfo.Id);
386 gameAccount->set_title_id(ClientBuild::Program::WoW);
387 gameAccount->set_region(2);
388 }
389
390 if (!country.empty())
391 logonRecord->set_geoip_country(country.data(), country.size());
392
393 std::array<uint8, 64> k = Trinity::Crypto::GetRandomBytes<64>();
394 logonRecord->set_session_key(k.data(), 64);
395
396 Service<authentication::v2::client::AuthenticationListener>(session).OnLogonComplete(&logonResult);
397 });
398}
399}
400}
@ ERROR_GAME_ACCOUNT_BANNED
@ ERROR_RISK_ACCOUNT_LOCKED
@ ERROR_BAD_PROGRAM
@ ERROR_GAME_ACCOUNT_SUSPENDED
@ ERROR_BAD_LOCALE
@ ERROR_BAD_PLATFORM
@ ERROR_TIMED_OUT
LocaleConstant GetLocaleByName(std::string_view name)
Definition Common.cpp:36
constexpr bool IsValidLocale(LocaleConstant locale)
Definition Common.h:98
std::shared_ptr< PreparedResultSet > PreparedQueryResult
DatabaseWorkerPool< LoginDatabaseConnection > LoginDatabase
Accessor to the realm/login database.
#define UI64LIT(N)
Definition Define.h:139
uint32_t uint32
Definition Define.h:154
std::chrono::minutes Minutes
Minutes shorthand typedef.
Definition Duration.h:32
#define sIPLocation
Definition IPLocation.h:56
#define TC_LOG_DEBUG(filterType__, message__,...)
Definition Log.h:173
@ LOGIN_SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID
@ LOGIN_SEL_BNET_EXISTING_AUTHENTICATION_BY_ID
@ LOGIN_SEL_BNET_ACCOUNT_INFO
@ LOGIN_SEL_BNET_LAST_PLAYER_CHARACTERS
#define sLoginService
static uint32 HandleVerifyAuthToken(Session *session, std::string_view authToken, std::function< void(uint32)> sendResponse, std::function< void(AccountInfo const *, std::string_view)> sendLogonComplete)
static uint32 HandleLogon(Session *session, ClientBuild::Program::Id program, std::string_view platform, std::string_view locale, uint32 applicationVersion, std::string_view deviceId)
static uint32 HandleGenerateAuthToken(Session *session, std::function< void(std::string_view)> sendResponse)
uint32 HandleGenerateWebCredentials(authentication::v1::GenerateWebCredentialsRequest const *request, authentication::v1::GenerateWebCredentialsResponse *response, std::function< void(ServiceBase *, uint32, google::protobuf::Message const *)> &continuation) override
uint32 HandleVerifyWebCredentials(authentication::v1::VerifyWebCredentialsRequest const *request, NoData *response, std::function< void(ServiceBase *, uint32, ::google::protobuf::Message const *)> &continuation) override
uint32 HandleLogon(authentication::v1::LogonRequest const *request, NoData *response, std::function< void(ServiceBase *, uint32, ::google::protobuf::Message const *)> &continuation) override
uint32 HandleLogon(authentication::v2::client::LogonRequest const *request, NoData *response, std::function< void(ServiceBase *, uint32, ::google::protobuf::Message const *)> &continuation) override
uint32 HandleVerifyAuthToken(authentication::v2::client::VerifyAuthTokenRequest const *request, NoData *response, std::function< void(ServiceBase *, uint32, ::google::protobuf::Message const *)> &continuation) override
uint32 HandleGenerateAuthToken(authentication::v2::client::GenerateAuthTokenRequest const *request, authentication::v2::client::GenerateAuthTokenResponse *response, std::function< void(ServiceBase *, uint32, google::protobuf::Message const *)> &continuation) override
std::string GetClientInfo() const
Definition Session.cpp:345
uint32 GetAccountId() const
Definition Session.h:100
void OnLogon(std::string_view platform, std::string_view locale, uint32 applicationVersion, Minutes timezoneOffset)
Definition Session.cpp:214
bool IsAuthed() const
Definition Session.h:94
void QueueQuery(QueryCallback &&queryCallback)
Definition Session.cpp:209
void OnLogonSuccess(std::shared_ptr< AccountInfo > accountInfo, std::string_view ipCountry)
Definition Session.cpp:223
boost::asio::ip::address const & GetRemoteIpAddress() const
Definition Session.h:89
static bool UsesDevWildcardCertificate()
Definition SslContext.h:32
Class used to access individual fields of database query result.
Definition Field.h:94
uint64 GetUInt64() const noexcept
Definition Field.cpp:71
uint32 GetUInt32() const noexcept
Definition Field.cpp:57
uint8 GetUInt8() const noexcept
Definition Field.cpp:29
std::string GetString() const noexcept
Definition Field.cpp:113
void setString(uint8 index, std::string &&value)
void setUInt32(uint8 index, uint32 value)
void SetNextQuery(QueryCallback &&next)
Minutes GetOffsetByHash(uint32 hash)
Definition Timezone.cpp:130
std::string StringFormat(FormatString< Args... > fmt, Args &&... args) noexcept
Default TC string format function.
Battlenet::RealmHandle RealmId
Definition Session.h:42
static TC_SHARED_API bool IsValid(std::string_view platform)
static constexpr Id WoW
static constexpr FourCC FromString(std::string_view text)
Definition FourCC.h:33