major refactor: auth v3
This commit is contained in:
parent
8a8b4b2573
commit
179d21d7b6
18 changed files with 376 additions and 166 deletions
|
|
@ -3,6 +3,7 @@
|
|||
#include "Minecraft.h"
|
||||
#include "User.h"
|
||||
#include "..\Minecraft.World\AuthModule.h"
|
||||
#include "..\Minecraft.World\SessionAuthModule.h"
|
||||
#include "..\Minecraft.World\HttpClient.h"
|
||||
#include "..\Minecraft.World\StringHelpers.h"
|
||||
#include "Common/vendor/nlohmann/json.hpp"
|
||||
|
|
@ -12,24 +13,33 @@
|
|||
|
||||
using json = nlohmann::json;
|
||||
static constexpr auto PROFILES_FILE = L"auth_profiles.dat";
|
||||
static constexpr auto MS_CLIENT_ID = "00000000441cc96b";
|
||||
static constexpr auto MS_CLIENT_ID = "00000000402b5328";
|
||||
static json parseResponse(const HttpResponse &resp, int expectedStatus = 200);
|
||||
static bool msTokenExchange(const string &msAccessToken, string &mcToken, string &profId, string &profName);
|
||||
static bool msRefreshOAuth(const string &refreshToken, string &newAccessToken, string &newRefreshToken);
|
||||
static bool elybyValidate(const string &accessToken, const string &clientToken);
|
||||
static bool elybyRefresh(const string &accessToken, const string &clientToken, string &newAccessToken, string &newClientToken);
|
||||
static bool yggdrasilValidate(const string &accessToken, const string &clientToken, const string &validateUrl);
|
||||
static bool yggdrasilRefresh(const string &accessToken, const string &clientToken, string &newAccessToken, string &newClientToken, const string &refreshUrl);
|
||||
|
||||
vector<AuthProfile> AuthProfileManager::profiles;
|
||||
int AuthProfileManager::selectedProfile = -1;
|
||||
|
||||
static constexpr uint32_t PROFILE_MAGIC = 0x4D435032;
|
||||
|
||||
void AuthProfileManager::load()
|
||||
{
|
||||
profiles.clear();
|
||||
std::ifstream file(PROFILES_FILE, std::ios::binary);
|
||||
if (!file) return;
|
||||
|
||||
uint32_t header = 0;
|
||||
file.read(reinterpret_cast<char *>(&header), sizeof(header));
|
||||
bool hasVariation = (header == PROFILE_MAGIC);
|
||||
|
||||
uint32_t count = 0;
|
||||
file.read(reinterpret_cast<char *>(&count), sizeof(count));
|
||||
if (hasVariation)
|
||||
file.read(reinterpret_cast<char *>(&count), sizeof(count));
|
||||
else
|
||||
count = header;
|
||||
|
||||
for (uint32_t i = 0; i < count && file.good(); i++)
|
||||
{
|
||||
|
|
@ -52,6 +62,10 @@ void AuthProfileManager::load()
|
|||
p.username = readWstr();
|
||||
p.token = readWstr();
|
||||
p.clientToken = readWstr();
|
||||
if (hasVariation)
|
||||
p.variation = readWstr();
|
||||
if (p.variation.empty() && p.type == AuthProfile::YGGDRASIL)
|
||||
p.variation = L"elyby";
|
||||
profiles.push_back(std::move(p));
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +80,9 @@ void AuthProfileManager::save()
|
|||
std::ofstream file(PROFILES_FILE, std::ios::binary | std::ios::trunc);
|
||||
if (!file) return;
|
||||
|
||||
uint32_t magic = PROFILE_MAGIC;
|
||||
file.write(reinterpret_cast<const char *>(&magic), sizeof(magic));
|
||||
|
||||
uint32_t count = static_cast<uint32_t>(profiles.size());
|
||||
file.write(reinterpret_cast<const char *>(&count), sizeof(count));
|
||||
|
||||
|
|
@ -83,16 +100,17 @@ void AuthProfileManager::save()
|
|||
writeWstr(p.username);
|
||||
writeWstr(p.token);
|
||||
writeWstr(p.clientToken);
|
||||
writeWstr(p.variation);
|
||||
}
|
||||
|
||||
int32_t idx = static_cast<int32_t>(selectedProfile);
|
||||
file.write(reinterpret_cast<const char *>(&idx), sizeof(idx));
|
||||
}
|
||||
|
||||
void AuthProfileManager::addProfile(AuthProfile::Type type, const wstring &username, const wstring &uid, const wstring &token, const wstring &clientToken)
|
||||
void AuthProfileManager::addProfile(AuthProfile::Type type, const wstring &username, const wstring &uid, const wstring &token, const wstring &clientToken, const wstring &variation)
|
||||
{
|
||||
wstring finalUid = uid.empty() ? L"offline_" + username : uid;
|
||||
profiles.push_back({type, finalUid, username, token, clientToken});
|
||||
profiles.push_back({type, finalUid, username, token, clientToken, variation});
|
||||
selectedProfile = static_cast<int>(profiles.size()) - 1;
|
||||
save();
|
||||
}
|
||||
|
|
@ -137,12 +155,14 @@ bool AuthProfileManager::applySelectedProfile()
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (p.type == AuthProfile::ELYBY && !p.token.empty())
|
||||
else if (p.type == AuthProfile::YGGDRASIL && !p.token.empty())
|
||||
{
|
||||
if (!elybyValidate(narrowStr(p.token), narrowStr(p.clientToken)))
|
||||
auto *provider = YggdrasilRegistry::find(p.variation);
|
||||
if (!provider) provider = &YggdrasilRegistry::defaultProvider();
|
||||
if (!yggdrasilValidate(narrowStr(p.token), narrowStr(p.clientToken), provider->validateUrl()))
|
||||
{
|
||||
string newAccess, newClient;
|
||||
if (elybyRefresh(narrowStr(p.token), narrowStr(p.clientToken), newAccess, newClient))
|
||||
if (yggdrasilRefresh(narrowStr(p.token), narrowStr(p.clientToken), newAccess, newClient, provider->refreshUrl()))
|
||||
{
|
||||
p.token = convStringToWstring(newAccess);
|
||||
if (!newClient.empty()) p.clientToken = convStringToWstring(newClient);
|
||||
|
|
@ -169,7 +189,6 @@ bool AuthProfileManager::applySelectedProfile()
|
|||
return true;
|
||||
}
|
||||
|
||||
// --- AuthFlow ---
|
||||
|
||||
std::thread AuthFlow::workerThread;
|
||||
std::atomic<AuthFlowState> AuthFlow::state{AuthFlowState::IDLE};
|
||||
|
|
@ -177,6 +196,7 @@ std::atomic<bool> AuthFlow::cancelRequested{false};
|
|||
AuthResult AuthFlow::result;
|
||||
wstring AuthFlow::userCode;
|
||||
wstring AuthFlow::verificationUri;
|
||||
wstring AuthFlow::activeVariation;
|
||||
|
||||
void AuthFlow::reset()
|
||||
{
|
||||
|
|
@ -187,6 +207,7 @@ void AuthFlow::reset()
|
|||
result = {};
|
||||
userCode.clear();
|
||||
verificationUri.clear();
|
||||
activeVariation.clear();
|
||||
cancelRequested = false;
|
||||
}
|
||||
|
||||
|
|
@ -197,11 +218,15 @@ void AuthFlow::startMicrosoft()
|
|||
workerThread = std::thread(microsoftFlowThread);
|
||||
}
|
||||
|
||||
void AuthFlow::startElyBy(const wstring &username, const wstring &password)
|
||||
void AuthFlow::startYggdrasil(const wstring &username, const wstring &password, const wstring &providerName)
|
||||
{
|
||||
reset();
|
||||
state = AuthFlowState::EXCHANGING;
|
||||
workerThread = std::thread(elybyFlowThread, narrowStr(username), narrowStr(password));
|
||||
wstring resolvedName = providerName.empty() ? YggdrasilRegistry::defaultProvider().name : providerName;
|
||||
activeVariation = resolvedName;
|
||||
auto *provider = YggdrasilRegistry::find(resolvedName);
|
||||
string authUrl = provider ? provider->authenticateUrl() : YggdrasilRegistry::defaultProvider().authenticateUrl();
|
||||
workerThread = std::thread(yggdrasilFlowThread, narrowStr(username), narrowStr(password), authUrl);
|
||||
}
|
||||
|
||||
static void authFail(AuthResult &result, std::atomic<AuthFlowState> &state, const wchar_t *msg)
|
||||
|
|
@ -210,7 +235,6 @@ static void authFail(AuthResult &result, std::atomic<AuthFlowState> &state, cons
|
|||
state = AuthFlowState::FAILED;
|
||||
}
|
||||
|
||||
// parse json response body, return discarded json on bad status
|
||||
static json parseResponse(const HttpResponse &resp, int expectedStatus)
|
||||
{
|
||||
if (resp.statusCode != expectedStatus) return json::value_t::discarded;
|
||||
|
|
@ -273,19 +297,15 @@ static bool msRefreshOAuth(const string &refreshToken, string &newAccessToken, s
|
|||
newRefreshToken = j.value("refresh_token", "");
|
||||
return !newAccessToken.empty();
|
||||
}
|
||||
|
||||
// validate ely.by token via yggdrasil /validate endpoint
|
||||
static bool elybyValidate(const string &accessToken, const string &clientToken)
|
||||
static bool yggdrasilValidate(const string &accessToken, const string &clientToken, const string &validateUrl)
|
||||
{
|
||||
auto resp = HttpClient::post("https://authserver.ely.by/auth/validate",
|
||||
auto resp = HttpClient::post(validateUrl,
|
||||
json({{"accessToken", accessToken}, {"clientToken", clientToken}}).dump());
|
||||
return resp.statusCode == 200;
|
||||
}
|
||||
|
||||
// refresh ely.by token via yggdrasil /refresh endpoint
|
||||
static bool elybyRefresh(const string &accessToken, const string &clientToken, string &newAccessToken, string &newClientToken)
|
||||
static bool yggdrasilRefresh(const string &accessToken, const string &clientToken, string &newAccessToken, string &newClientToken, const string &refreshUrl)
|
||||
{
|
||||
auto resp = HttpClient::post("https://authserver.ely.by/auth/refresh",
|
||||
auto resp = HttpClient::post(refreshUrl,
|
||||
json({{"accessToken", accessToken}, {"clientToken", clientToken}}).dump());
|
||||
|
||||
auto j = parseResponse(resp);
|
||||
|
|
@ -403,9 +423,9 @@ void AuthFlow::microsoftFlowThread()
|
|||
state = AuthFlowState::COMPLETE;
|
||||
}
|
||||
|
||||
void AuthFlow::elybyFlowThread(const string &username, const string &password)
|
||||
void AuthFlow::yggdrasilFlowThread(const string &username, const string &password, const string &authenticateUrl)
|
||||
{
|
||||
auto resp = HttpClient::post("https://authserver.ely.by/auth/authenticate", json({
|
||||
auto resp = HttpClient::post(authenticateUrl, json({
|
||||
{"username", username},
|
||||
{"password", password},
|
||||
{"clientToken", "mcconsoles"},
|
||||
|
|
@ -416,7 +436,7 @@ void AuthFlow::elybyFlowThread(const string &username, const string &password)
|
|||
|
||||
if (resp.statusCode != 200 || respJson.is_discarded())
|
||||
{
|
||||
string msg = "Ely.by auth failed";
|
||||
string msg = "Yggdrasil auth failed";
|
||||
if (!respJson.is_discarded()) msg = respJson.value("errorMessage", msg);
|
||||
result = {false, {}, {}, {}, {}, convStringToWstring(msg)};
|
||||
state = AuthFlowState::FAILED;
|
||||
|
|
@ -424,16 +444,16 @@ void AuthFlow::elybyFlowThread(const string &username, const string &password)
|
|||
}
|
||||
|
||||
string accessToken = respJson.value("accessToken", "");
|
||||
string elyClientToken = respJson.value("clientToken", "");
|
||||
string yggClientToken = respJson.value("clientToken", "");
|
||||
string uuid, name;
|
||||
try { uuid = respJson["selectedProfile"].value("id", ""); name = respJson["selectedProfile"].value("name", ""); } catch (...) {}
|
||||
|
||||
if (accessToken.empty() || uuid.empty() || name.empty())
|
||||
{
|
||||
authFail(result, state, L"Ely.by response missing profile");
|
||||
authFail(result, state, L"Yggdrasil response missing profile");
|
||||
return;
|
||||
}
|
||||
|
||||
result = {true, convStringToWstring(name), convStringToWstring(uuid), convStringToWstring(accessToken), convStringToWstring(elyClientToken), {}};
|
||||
result = {true, convStringToWstring(name), convStringToWstring(uuid), convStringToWstring(accessToken), convStringToWstring(yggClientToken), {}};
|
||||
state = AuthFlowState::COMPLETE;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue