MinecraftConsoles/Minecraft.World/SessionAuthModule.cpp
2026-04-06 02:33:39 -04:00

139 lines
3.9 KiB
C++

#include "stdafx.h"
#include "SessionAuthModule.h"
#include "HttpClient.h"
#include "StringHelpers.h"
#include "Common/vendor/nlohmann/json.hpp"
#include <random>
#include <fstream>
#include <algorithm>
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;
}
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()
{
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];
}
const wchar_t *SessionAuthModule::schemeName() { return L"mcconsoles:session"; }
vector<wstring> SessionAuthModule::supportedVariations()
{
vector<wstring> result;
for (const auto &p : YggdrasilRegistry::providers())
result.push_back(p.name);
return result;
}
vector<pair<wstring, wstring>> SessionAuthModule::getSettings(const wstring &variation)
{
auto *p = YggdrasilRegistry::find(variation);
if (!p) return {};
activeProvider = p;
activeServerId = generateServerId();
return {
{L"joinUrl", convStringToWstring(p->joinUrl())},
{L"serverId", activeServerId}
};
}
bool SessionAuthModule::serverVerify(const YggdrasilProviderConfig &provider, const wstring &username, const wstring &serverId, wstring &outUid, wstring &outUsername)
{
string url = provider.hasJoinedUrl() + "?username=" + narrowStr(username) + "&serverId=" + narrowStr(serverId);
app.DebugPrintf("AUTH SERVER [%ls]: hasJoined GET %s\n", provider.name.c_str(), url.c_str());
auto response = HttpClient::get(url);
app.DebugPrintf("AUTH SERVER [%ls]: hasJoined status=%d\n", provider.name.c_str(), response.statusCode);
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);
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;
return validate(outUid, outUsername);
}