#include "stdafx.h" #include "SessionAuthModule.h" #include "HttpClient.h" #include "StringHelpers.h" #include "Common/vendor/nlohmann/json.hpp" #include #include #include 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 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 &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 SessionAuthModule::supportedVariations() { vector result; for (const auto &p : YggdrasilRegistry::providers()) result.push_back(p.name); return result; } vector> 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> &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); }