finally added session-based authentication and libcurl for https requests. (serverside for right now.)

This commit is contained in:
Matthew Toro 2026-04-03 04:28:52 -04:00
parent e979be42b2
commit 0fa5ae3037
10 changed files with 175 additions and 40 deletions

View file

@ -4,6 +4,21 @@ project(MinecraftConsoles LANGUAGES C CXX RC ASM_MASM)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
include(FetchContent)
set(BUILD_CURL_EXE OFF CACHE BOOL "" FORCE)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(CURL_USE_SCHANNEL ON CACHE BOOL "" FORCE)
set(BUILD_TESTING OFF CACHE BOOL "" FORCE)
set(CURL_DISABLE_LDAP ON CACHE BOOL "" FORCE)
set(CURL_DISABLE_LDAPS ON CACHE BOOL "" FORCE)
FetchContent_Declare(
curl
URL https://github.com/curl/curl/releases/download/curl-8_11_1/curl-8.11.1.tar.xz
URL_HASH SHA256=c7ca7db48b0909743eaef34250da02c19bc61d4f1dcedd6603f109409536ab56
FIND_PACKAGE_ARGS
)
FetchContent_MakeAvailable(curl)
if(NOT WIN32)
message(FATAL_ERROR "This CMake build currently supports Windows only.")
@ -18,7 +33,6 @@ set(CMAKE_CONFIGURATION_TYPES
"Release"
CACHE STRING "" FORCE
)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
function(configure_compiler_target target)
# MSVC and compatible compilers (like Clang-cl)

View file

@ -4139,8 +4139,7 @@ ClientConnection::DeferredEntityLinkPacket::DeferredEntityLinkPacket(shared_ptr<
void ClientConnection::beginAuth()
{
handshakeManager = new HandshakeManager(false);
handshakeManager->registerModule(new MojangAuthModule());
handshakeManager->registerModule(new ElyByAuthModule());
handshakeManager->registerModule(new SessionAuthModule());
handshakeManager->registerModule(new KeypairOfflineAuthModule());
handshakeManager->registerModule(new OfflineAuthModule());

View file

@ -411,8 +411,7 @@ bool PendingConnection::isDisconnected()
void PendingConnection::initAuth()
{
handshakeManager = new HandshakeManager(true);
handshakeManager->registerModule(new MojangAuthModule());
handshakeManager->registerModule(new ElyByAuthModule());
handshakeManager->registerModule(new SessionAuthModule());
handshakeManager->registerModule(new KeypairOfflineAuthModule());
handshakeManager->registerModule(new OfflineAuthModule());
}

View file

@ -1,5 +1,23 @@
#include "stdafx.h"
#include "AuthModule.h"
#include "HttpClient.h"
#include "StringHelpers.h"
#include "Common/vendor/nlohmann/json.hpp"
#include <random>
static string narrowStr(const wstring &w)
{
return string(w.begin(), w.end());
}
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;
}
bool AuthModule::validate(const wstring &uid, const wstring &username)
{
@ -16,38 +34,67 @@ bool AuthModule::extractIdentity(const vector<pair<wstring, wstring>> &fields, w
return validate(outUid, outUsername);
}
ElyByAuthModule::ElyByAuthModule(const wstring &endpoint)
: endpoint(endpoint)
SessionAuthModule::SessionAuthModule()
{
endpoints[L"mojang"] = {L"https://authserver.mojang.com", L"https://sessionserver.mojang.com"};
endpoints[L"elyby"] = {L"https://authserver.ely.by", L"https://authserver.ely.by"};
}
const wchar_t *ElyByAuthModule::schemeName() { return L"mcconsoles:elyby"; }
const wchar_t *SessionAuthModule::schemeName() { return L"mcconsoles:session"; }
vector<wstring> ElyByAuthModule::supportedVariations()
vector<wstring> SessionAuthModule::supportedVariations()
{
return {L"java"};
return {L"mojang", L"elyby"};
}
vector<pair<wstring, wstring>> ElyByAuthModule::getSettings(const wstring &variation)
vector<pair<wstring, wstring>> SessionAuthModule::getSettings(const wstring &variation)
{
return {{L"endpoint", endpoint}};
auto it = endpoints.find(variation);
if (it == endpoints.end()) return {};
activeSessionEndpoint = it->second.sessionEndpoint;
activeServerId = generateServerId();
return {
{L"authEndpoint", it->second.authEndpoint},
{L"sessionEndpoint", it->second.sessionEndpoint},
{L"serverId", activeServerId}
};
}
bool ElyByAuthModule::onAuthData(const vector<pair<wstring, wstring>> &fields, wstring &outUid, wstring &outUsername)
bool SessionAuthModule::onAuthData(const vector<pair<wstring, wstring>> &fields, wstring &outUid, wstring &outUsername)
{
return extractIdentity(fields, outUid, outUsername);
}
wstring username;
for (const auto &[k, v] : fields)
{
if (k == L"username") username = v;
}
MojangAuthModule::MojangAuthModule()
: ElyByAuthModule(L"https://sessionserver.mojang.com")
{
}
if (username.empty() || activeServerId.empty() || activeSessionEndpoint.empty())
return false;
const wchar_t *MojangAuthModule::schemeName() { return L"mcconsoles:mojang"; }
string url = narrowStr(activeSessionEndpoint)
+ "/session/minecraft/hasJoined?username=" + narrowStr(username)
+ "&serverId=" + narrowStr(activeServerId);
vector<wstring> MojangAuthModule::supportedVariations()
{
return {L"java", L"bedrock"};
auto response = HttpClient::get(url);
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 validate(outUid, outUsername);
}
const wchar_t *KeypairOfflineAuthModule::schemeName() { return L"mcconsoles:keypair_offline"; }

View file

@ -4,6 +4,7 @@ using namespace std;
#include <string>
#include <vector>
#include <utility>
#include <unordered_map>
class AuthModule
{
@ -21,13 +22,21 @@ protected:
bool extractIdentity(const vector<pair<wstring, wstring>> &fields, wstring &outUid, wstring &outUsername);
};
class ElyByAuthModule : public AuthModule
class SessionAuthModule : public AuthModule
{
protected:
wstring endpoint;
public:
struct EndpointPair {
wstring authEndpoint;
wstring sessionEndpoint;
};
private:
unordered_map<wstring, EndpointPair> endpoints;
wstring activeSessionEndpoint;
wstring activeServerId;
public:
ElyByAuthModule(const wstring &endpoint = L"https://authserver.ely.by");
SessionAuthModule();
const wchar_t *schemeName() override;
vector<wstring> supportedVariations() override;
@ -35,15 +44,6 @@ public:
bool onAuthData(const vector<pair<wstring, wstring>> &fields, wstring &outUid, wstring &outUsername) override;
};
class MojangAuthModule : public ElyByAuthModule
{
public:
MojangAuthModule();
const wchar_t *schemeName() override;
vector<wstring> supportedVariations() override;
};
class KeypairOfflineAuthModule : public AuthModule
{
public:

View file

@ -27,4 +27,6 @@ target_compile_definitions(Minecraft.World PRIVATE
)
target_precompile_headers(Minecraft.World PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:stdafx.h>")
target_link_libraries(Minecraft.World PUBLIC CURL::libcurl)
configure_compiler_target(Minecraft.World)

View file

@ -79,7 +79,8 @@ shared_ptr<AuthPacket> HandshakeManager::handleServer(const shared_ptr<AuthPacke
wstring uid, username;
if (!activeModule->onAuthData(packet->fields, uid, username))
return fail();
finalUid = uid;
finalUsername = username;
state = HandshakeState::AUTH_DATA_EXCHANGED;
return nullptr;
}
@ -93,11 +94,9 @@ shared_ptr<AuthPacket> HandshakeManager::handleServer(const shared_ptr<AuthPacke
else if (k == L"username") username = v;
}
if (!activeModule->validate(uid, username))
if (uid != finalUid || username != finalUsername)
return fail();
finalUid = uid;
finalUsername = username;
state = HandshakeState::IDENTITY_ASSIGNED;
return makePacket(AuthStage::ASSIGN_IDENTITY, {
{L"uid", finalUid},

View file

@ -0,0 +1,57 @@
#include "stdafx.h"
#include "HttpClient.h"
#include <curl/curl.h>
static size_t writeCallback(char *data, size_t size, size_t nmemb, void *userdata)
{
auto *buf = static_cast<std::string *>(userdata);
buf->append(data, size * nmemb);
return size * nmemb;
}
static HttpResponse performRequest(CURL *curl)
{
std::string responseBody;
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBody);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
CURLcode res = curl_easy_perform(curl);
int statusCode = 0;
if (res == CURLE_OK)
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode);
curl_easy_cleanup(curl);
return {statusCode, std::move(responseBody)};
}
HttpResponse HttpClient::get(const std::string &url)
{
CURL *curl = curl_easy_init();
if (!curl)
return {0, ""};
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
return performRequest(curl);
}
HttpResponse HttpClient::post(const std::string &url, const std::string &body, const std::string &contentType)
{
CURL *curl = curl_easy_init();
if (!curl)
return {0, ""};
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<long>(body.size()));
struct curl_slist *headers = nullptr;
headers = curl_slist_append(headers, ("Content-Type: " + contentType).c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
HttpResponse resp = performRequest(curl);
curl_slist_free_all(headers);
return resp;
}

View file

@ -0,0 +1,16 @@
#pragma once
#include <string>
struct HttpResponse
{
int statusCode;
std::string body;
};
class HttpClient
{
public:
static HttpResponse get(const std::string &url);
static HttpResponse post(const std::string &url, const std::string &body, const std::string &contentType = "application/json");
};

View file

@ -320,6 +320,8 @@ set(_MINECRAFT_WORLD_COMMON_NET_MINECRAFT_NETWORK_PACKET
"${CMAKE_CURRENT_SOURCE_DIR}/GetInfoPacket.h"
"${CMAKE_CURRENT_SOURCE_DIR}/HandshakeManager.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/HandshakeManager.h"
"${CMAKE_CURRENT_SOURCE_DIR}/HttpClient.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/HttpClient.h"
"${CMAKE_CURRENT_SOURCE_DIR}/InteractPacket.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/InteractPacket.h"
"${CMAKE_CURRENT_SOURCE_DIR}/KeepAlivePacket.cpp"