2026-04-05 18:04:53 -04:00
|
|
|
#include "stdafx.h"
|
|
|
|
|
#include "SessionAuthModule.h"
|
|
|
|
|
#include "HttpClient.h"
|
|
|
|
|
#include "StringHelpers.h"
|
|
|
|
|
#include "Common/vendor/nlohmann/json.hpp"
|
|
|
|
|
#include <random>
|
2026-04-06 02:33:39 -04:00
|
|
|
#include <fstream>
|
|
|
|
|
#include <algorithm>
|
2026-04-05 18:04:53 -04:00
|
|
|
|
|
|
|
|
static wstring generateServerId()
|
|
|
|
|
{
|
|
|
|
|
static constexpr wchar_t hex[] = L"0123456789abcdef";
|
|
|
|
|
static std::mt19937 rng(std::random_device{}());
|
|
|
|
|
wstring id(16, L'0');
|
|
|
|
|
for (auto &c : id) c = hex[rng() & 0xF];
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 02:33:39 -04:00
|
|
|
static vector<YggdrasilProviderConfig> s_registryProviders;
|
|
|
|
|
static bool s_registryLoaded = false;
|
|
|
|
|
|
|
|
|
|
void YggdrasilRegistry::load()
|
|
|
|
|
{
|
|
|
|
|
s_registryProviders.clear();
|
|
|
|
|
|
|
|
|
|
std::ifstream file("yggdrasil.json");
|
|
|
|
|
if (file.is_open())
|
|
|
|
|
{
|
|
|
|
|
auto j = nlohmann::json::parse(file, nullptr, false);
|
|
|
|
|
if (j.is_array())
|
|
|
|
|
{
|
|
|
|
|
for (const auto &entry : j)
|
|
|
|
|
{
|
|
|
|
|
if (!entry.is_object()) continue;
|
|
|
|
|
YggdrasilProviderConfig cfg;
|
|
|
|
|
cfg.name = convStringToWstring(entry.value("name", ""));
|
|
|
|
|
cfg.displayName = convStringToWstring(entry.value("displayName", ""));
|
|
|
|
|
cfg.authUrl = entry.value("authUrl", "");
|
|
|
|
|
cfg.sessionUrl = entry.value("sessionUrl", "");
|
|
|
|
|
if (!cfg.name.empty() && !cfg.sessionUrl.empty())
|
|
|
|
|
s_registryProviders.push_back(std::move(cfg));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (std::none_of(s_registryProviders.begin(), s_registryProviders.end(),
|
|
|
|
|
[](const auto &p) { return p.name == L"elyby"; }))
|
|
|
|
|
s_registryProviders.insert(s_registryProviders.begin(),
|
|
|
|
|
{L"elyby", L"Ely.by", "https://authserver.ely.by/auth", "https://authserver.ely.by/session"});
|
|
|
|
|
|
|
|
|
|
s_registryProviders.push_back(
|
|
|
|
|
{L"mojang", L"Mojang", "", "https://sessionserver.mojang.com/session/minecraft"});
|
|
|
|
|
|
|
|
|
|
s_registryLoaded = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const vector<YggdrasilProviderConfig> &YggdrasilRegistry::providers()
|
2026-04-05 18:04:53 -04:00
|
|
|
{
|
2026-04-06 02:33:39 -04:00
|
|
|
if (!s_registryLoaded) load();
|
|
|
|
|
return s_registryProviders;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const YggdrasilProviderConfig *YggdrasilRegistry::find(const wstring &name)
|
|
|
|
|
{
|
|
|
|
|
for (const auto &p : providers())
|
|
|
|
|
if (p.name == name) return &p;
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const YggdrasilProviderConfig &YggdrasilRegistry::defaultProvider()
|
|
|
|
|
{
|
|
|
|
|
const auto &list = providers();
|
|
|
|
|
for (const auto &p : list)
|
|
|
|
|
if (p.name != L"mojang") return p;
|
|
|
|
|
return list[0];
|
2026-04-05 18:04:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const wchar_t *SessionAuthModule::schemeName() { return L"mcconsoles:session"; }
|
|
|
|
|
|
|
|
|
|
vector<wstring> SessionAuthModule::supportedVariations()
|
|
|
|
|
{
|
2026-04-06 02:33:39 -04:00
|
|
|
vector<wstring> result;
|
|
|
|
|
for (const auto &p : YggdrasilRegistry::providers())
|
|
|
|
|
result.push_back(p.name);
|
|
|
|
|
return result;
|
2026-04-05 18:04:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vector<pair<wstring, wstring>> SessionAuthModule::getSettings(const wstring &variation)
|
|
|
|
|
{
|
2026-04-06 02:33:39 -04:00
|
|
|
auto *p = YggdrasilRegistry::find(variation);
|
|
|
|
|
if (!p) return {};
|
2026-04-05 18:04:53 -04:00
|
|
|
|
2026-04-06 02:33:39 -04:00
|
|
|
activeProvider = p;
|
2026-04-05 18:04:53 -04:00
|
|
|
activeServerId = generateServerId();
|
|
|
|
|
return {
|
2026-04-06 02:33:39 -04:00
|
|
|
{L"joinUrl", convStringToWstring(p->joinUrl())},
|
2026-04-05 18:04:53 -04:00
|
|
|
{L"serverId", activeServerId}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 02:33:39 -04:00
|
|
|
bool SessionAuthModule::serverVerify(const YggdrasilProviderConfig &provider, const wstring &username, const wstring &serverId, wstring &outUid, wstring &outUsername)
|
2026-04-05 18:04:53 -04:00
|
|
|
{
|
2026-04-06 02:33:39 -04:00
|
|
|
string url = provider.hasJoinedUrl() + "?username=" + narrowStr(username) + "&serverId=" + narrowStr(serverId);
|
2026-04-05 18:04:53 -04:00
|
|
|
|
2026-04-06 02:33:39 -04:00
|
|
|
app.DebugPrintf("AUTH SERVER [%ls]: hasJoined GET %s\n", provider.name.c_str(), url.c_str());
|
2026-04-05 18:04:53 -04:00
|
|
|
auto response = HttpClient::get(url);
|
2026-04-06 02:33:39 -04:00
|
|
|
app.DebugPrintf("AUTH SERVER [%ls]: hasJoined status=%d\n", provider.name.c_str(), response.statusCode);
|
|
|
|
|
|
2026-04-05 18:04:53 -04:00
|
|
|
if (response.statusCode != 200)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
auto json = nlohmann::json::parse(response.body, nullptr, false);
|
|
|
|
|
if (json.is_discarded())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
string id = json.value("id", "");
|
|
|
|
|
string name = json.value("name", "");
|
|
|
|
|
if (id.empty() || name.empty())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
outUid = convStringToWstring(id);
|
|
|
|
|
outUsername = convStringToWstring(name);
|
2026-04-06 02:33:39 -04:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SessionAuthModule::onAuthData(const vector<pair<wstring, wstring>> &fields, wstring &outUid, wstring &outUsername)
|
|
|
|
|
{
|
|
|
|
|
wstring username;
|
|
|
|
|
for (const auto &[k, v] : fields)
|
|
|
|
|
if (k == L"username") username = v;
|
|
|
|
|
|
|
|
|
|
if (username.empty() || activeServerId.empty() || !activeProvider)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (!serverVerify(*activeProvider, username, activeServerId, outUid, outUsername))
|
|
|
|
|
return false;
|
2026-04-05 18:04:53 -04:00
|
|
|
|
|
|
|
|
return validate(outUid, outUsername);
|
|
|
|
|
}
|