major feat: auth v1
This commit is contained in:
parent
0fa5ae3037
commit
1daae8e2a8
15 changed files with 627 additions and 100 deletions
|
|
@ -2,9 +2,16 @@
|
|||
#include "AuthScreen.h"
|
||||
#include "Minecraft.h"
|
||||
#include "User.h"
|
||||
#include "..\Minecraft.World\AuthModule.h"
|
||||
#include "..\Minecraft.World\HttpClient.h"
|
||||
#include "..\Minecraft.World\StringHelpers.h"
|
||||
#include "Common/vendor/nlohmann/json.hpp"
|
||||
#include <fstream>
|
||||
#include <shellapi.h>
|
||||
|
||||
using json = nlohmann::json;
|
||||
static constexpr auto PROFILES_FILE = L"auth_profiles.dat";
|
||||
static constexpr auto MS_CLIENT_ID = "00000000441cc96b";
|
||||
|
||||
vector<AuthProfile> AuthProfileManager::profiles;
|
||||
int AuthProfileManager::selectedProfile = -1;
|
||||
|
|
@ -41,8 +48,10 @@ void AuthProfileManager::load()
|
|||
profiles.push_back(std::move(p));
|
||||
}
|
||||
|
||||
int32_t savedIdx = 0;
|
||||
file.read(reinterpret_cast<char *>(&savedIdx), sizeof(savedIdx));
|
||||
if (!profiles.empty())
|
||||
selectedProfile = 0;
|
||||
selectedProfile = (savedIdx >= 0 && savedIdx < static_cast<int>(profiles.size())) ? savedIdx : 0;
|
||||
}
|
||||
|
||||
void AuthProfileManager::save()
|
||||
|
|
@ -67,11 +76,15 @@ void AuthProfileManager::save()
|
|||
writeWstr(p.username);
|
||||
writeWstr(p.token);
|
||||
}
|
||||
|
||||
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)
|
||||
void AuthProfileManager::addProfile(AuthProfile::Type type, const wstring &username, const wstring &uid, const wstring &token)
|
||||
{
|
||||
profiles.push_back({type, L"offline_" + username, username, {}});
|
||||
wstring finalUid = uid.empty() ? L"offline_" + username : uid;
|
||||
profiles.push_back({type, finalUid, username, token});
|
||||
selectedProfile = static_cast<int>(profiles.size()) - 1;
|
||||
save();
|
||||
}
|
||||
|
|
@ -99,5 +112,266 @@ bool AuthProfileManager::applySelectedProfile()
|
|||
delete mc->user;
|
||||
|
||||
mc->user = new User(p.username, p.token);
|
||||
|
||||
// push auth name into the platform globals so ProfileManager.GetGamertag() picks it up
|
||||
// instead of returning the default "Player"
|
||||
extern char g_Win64Username[17];
|
||||
extern wchar_t g_Win64UsernameW[17];
|
||||
string narrow = narrowStr(p.username);
|
||||
strncpy_s(g_Win64Username, sizeof(g_Win64Username), narrow.c_str(), _TRUNCATE);
|
||||
wcsncpy_s(g_Win64UsernameW, 17, p.username.c_str(), _TRUNCATE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- AuthFlow ---
|
||||
|
||||
std::thread AuthFlow::workerThread;
|
||||
std::atomic<AuthFlowState> AuthFlow::state{AuthFlowState::IDLE};
|
||||
std::atomic<bool> AuthFlow::cancelRequested{false};
|
||||
AuthResult AuthFlow::result;
|
||||
wstring AuthFlow::userCode;
|
||||
wstring AuthFlow::verificationUri;
|
||||
|
||||
void AuthFlow::reset()
|
||||
{
|
||||
cancelRequested = true;
|
||||
if (workerThread.joinable())
|
||||
workerThread.detach();
|
||||
state = AuthFlowState::IDLE;
|
||||
result = {};
|
||||
userCode.clear();
|
||||
verificationUri.clear();
|
||||
cancelRequested = false;
|
||||
}
|
||||
|
||||
void AuthFlow::startMicrosoft()
|
||||
{
|
||||
reset();
|
||||
state = AuthFlowState::WAITING_FOR_USER;
|
||||
workerThread = std::thread(microsoftFlowThread);
|
||||
}
|
||||
|
||||
void AuthFlow::startElyBy(const wstring &username, const wstring &password)
|
||||
{
|
||||
reset();
|
||||
state = AuthFlowState::EXCHANGING;
|
||||
workerThread = std::thread(elybyFlowThread, narrowStr(username), narrowStr(password));
|
||||
}
|
||||
|
||||
static void authFail(AuthResult &result, std::atomic<AuthFlowState> &state, const wchar_t *msg)
|
||||
{
|
||||
result = {false, {}, {}, {}, msg};
|
||||
state = AuthFlowState::FAILED;
|
||||
}
|
||||
|
||||
// parse json response body, return discarded json on bad status
|
||||
static json parseResponse(const HttpResponse &resp, int expectedStatus = 200)
|
||||
{
|
||||
if (resp.statusCode != expectedStatus) return json::value_t::discarded;
|
||||
return json::parse(resp.body, nullptr, false);
|
||||
}
|
||||
|
||||
void AuthFlow::microsoftFlowThread()
|
||||
{
|
||||
auto dcResp = HttpClient::post(
|
||||
"https://login.live.com/oauth20_connect.srf",
|
||||
"client_id=" + string(MS_CLIENT_ID) + "&scope=service::user.auth.xboxlive.com::MBI_SSL&response_type=device_code",
|
||||
"application/x-www-form-urlencoded"
|
||||
);
|
||||
|
||||
auto dcJson = parseResponse(dcResp);
|
||||
if (dcJson.is_discarded())
|
||||
{
|
||||
authFail(result, state, L"Failed to get device code");
|
||||
return;
|
||||
}
|
||||
|
||||
string deviceCode = dcJson.value("device_code", "");
|
||||
string uCode = dcJson.value("user_code", "");
|
||||
string vUri = dcJson.value("verification_uri", "");
|
||||
int interval = dcJson.value("interval", 5);
|
||||
|
||||
if (deviceCode.empty() || uCode.empty())
|
||||
{
|
||||
authFail(result, state, L"Missing device code fields");
|
||||
return;
|
||||
}
|
||||
|
||||
userCode = convStringToWstring(uCode);
|
||||
verificationUri = convStringToWstring(vUri);
|
||||
|
||||
// copy code to clipboard so the user can just paste it
|
||||
if (OpenClipboard(nullptr))
|
||||
{
|
||||
EmptyClipboard();
|
||||
size_t bytes = (uCode.size() + 1) * sizeof(char);
|
||||
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, bytes);
|
||||
if (hMem)
|
||||
{
|
||||
memcpy(GlobalLock(hMem), uCode.c_str(), bytes);
|
||||
GlobalUnlock(hMem);
|
||||
SetClipboardData(CF_TEXT, hMem);
|
||||
}
|
||||
CloseClipboard();
|
||||
}
|
||||
|
||||
if (!vUri.empty())
|
||||
ShellExecuteW(nullptr, L"open", verificationUri.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
|
||||
|
||||
state = AuthFlowState::POLLING;
|
||||
string msAccessToken;
|
||||
|
||||
for (int attempt = 0; attempt < 180; attempt++)
|
||||
{
|
||||
for (int ms = 0; ms < interval * 1000; ms += 250)
|
||||
{
|
||||
if (cancelRequested) return;
|
||||
Sleep(250);
|
||||
}
|
||||
|
||||
auto pollResp = HttpClient::post(
|
||||
"https://login.live.com/oauth20_token.srf",
|
||||
"client_id=" + string(MS_CLIENT_ID) + "&device_code=" + deviceCode + "&grant_type=urn:ietf:params:oauth:grant-type:device_code",
|
||||
"application/x-www-form-urlencoded"
|
||||
);
|
||||
|
||||
auto pollJson = json::parse(pollResp.body, nullptr, false);
|
||||
if (pollJson.is_discarded()) continue;
|
||||
|
||||
if (pollResp.statusCode == 200)
|
||||
{
|
||||
msAccessToken = pollJson.value("access_token", "");
|
||||
if (!msAccessToken.empty()) break;
|
||||
}
|
||||
|
||||
string err = pollJson.value("error", "");
|
||||
if (err == "authorization_pending") continue;
|
||||
if (err == "slow_down") { interval += 5; continue; }
|
||||
if (!err.empty())
|
||||
{
|
||||
result = {false, {}, {}, {}, convStringToWstring("Auth error: " + err)};
|
||||
state = AuthFlowState::FAILED;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (msAccessToken.empty())
|
||||
{
|
||||
authFail(result, state, L"Timed out waiting for login");
|
||||
return;
|
||||
}
|
||||
|
||||
state = AuthFlowState::EXCHANGING;
|
||||
if (cancelRequested) return;
|
||||
|
||||
// xbox live auth
|
||||
auto xblResp = HttpClient::post("https://user.auth.xboxlive.com/user/authenticate", json({
|
||||
{"Properties", {{"AuthMethod", "RPS"}, {"SiteName", "user.auth.xboxlive.com"}, {"RpsTicket", msAccessToken}}},
|
||||
{"RelyingParty", "http://auth.xboxlive.com"},
|
||||
{"TokenType", "JWT"}
|
||||
}).dump());
|
||||
|
||||
auto xblJson = parseResponse(xblResp);
|
||||
if (xblJson.is_discarded())
|
||||
{
|
||||
authFail(result, state, L"Xbox Live auth failed");
|
||||
return;
|
||||
}
|
||||
|
||||
string xblToken = xblJson.value("Token", "");
|
||||
string userHash;
|
||||
try { userHash = xblJson["DisplayClaims"]["xui"][0].value("uhs", ""); } catch (...) {}
|
||||
|
||||
if (xblToken.empty() || userHash.empty())
|
||||
{
|
||||
authFail(result, state, L"Bad Xbox Live response");
|
||||
return;
|
||||
}
|
||||
|
||||
// xsts auth
|
||||
auto xstsResp = HttpClient::post("https://xsts.auth.xboxlive.com/xsts/authorize", json({
|
||||
{"Properties", {{"SandboxId", "RETAIL"}, {"UserTokens", {xblToken}}}},
|
||||
{"RelyingParty", "rp://api.minecraftservices.com/"},
|
||||
{"TokenType", "JWT"}
|
||||
}).dump());
|
||||
|
||||
auto xstsJson = parseResponse(xstsResp);
|
||||
string xstsToken = xstsJson.is_discarded() ? "" : xstsJson.value("Token", "");
|
||||
|
||||
if (xstsToken.empty())
|
||||
{
|
||||
authFail(result, state, L"XSTS auth failed");
|
||||
return;
|
||||
}
|
||||
|
||||
// minecraft login
|
||||
auto mcResp = HttpClient::post("https://api.minecraftservices.com/authentication/login_with_xbox",
|
||||
json({{"identityToken", "XBL3.0 x=" + userHash + ";" + xstsToken}}).dump());
|
||||
|
||||
auto mcJson = parseResponse(mcResp);
|
||||
string mcAccessToken = mcJson.is_discarded() ? "" : mcJson.value("access_token", "");
|
||||
|
||||
if (mcAccessToken.empty())
|
||||
{
|
||||
authFail(result, state, L"Minecraft auth failed");
|
||||
return;
|
||||
}
|
||||
|
||||
// get profile
|
||||
auto profResp = HttpClient::get("https://api.minecraftservices.com/minecraft/profile",
|
||||
{"Authorization: Bearer " + mcAccessToken});
|
||||
|
||||
auto profJson = parseResponse(profResp);
|
||||
if (profJson.is_discarded())
|
||||
{
|
||||
authFail(result, state, L"Failed to get Minecraft profile");
|
||||
return;
|
||||
}
|
||||
|
||||
string profId = profJson.value("id", "");
|
||||
string profName = profJson.value("name", "");
|
||||
|
||||
if (profId.empty() || profName.empty())
|
||||
{
|
||||
authFail(result, state, L"Profile missing id or name");
|
||||
return;
|
||||
}
|
||||
|
||||
result = {true, convStringToWstring(profName), convStringToWstring(profId), convStringToWstring(mcAccessToken), {}};
|
||||
state = AuthFlowState::COMPLETE;
|
||||
}
|
||||
|
||||
void AuthFlow::elybyFlowThread(const string &username, const string &password)
|
||||
{
|
||||
auto resp = HttpClient::post("https://authserver.ely.by/auth/authenticate", json({
|
||||
{"username", username},
|
||||
{"password", password},
|
||||
{"clientToken", "mcconsoles"},
|
||||
{"agent", {{"name", "Minecraft"}, {"version", 1}}}
|
||||
}).dump());
|
||||
|
||||
auto respJson = json::parse(resp.body, nullptr, false);
|
||||
|
||||
if (resp.statusCode != 200 || respJson.is_discarded())
|
||||
{
|
||||
string msg = "Ely.by auth failed";
|
||||
if (!respJson.is_discarded()) msg = respJson.value("errorMessage", msg);
|
||||
result = {false, {}, {}, {}, convStringToWstring(msg)};
|
||||
state = AuthFlowState::FAILED;
|
||||
return;
|
||||
}
|
||||
|
||||
string accessToken = respJson.value("accessToken", "");
|
||||
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");
|
||||
return;
|
||||
}
|
||||
|
||||
result = {true, convStringToWstring(name), convStringToWstring(uuid), convStringToWstring(accessToken), {}};
|
||||
state = AuthFlowState::COMPLETE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
using namespace std;
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
struct AuthProfile
|
||||
{
|
||||
|
|
@ -20,7 +22,7 @@ private:
|
|||
public:
|
||||
static void load();
|
||||
static void save();
|
||||
static void addProfile(AuthProfile::Type type, const wstring &username);
|
||||
static void addProfile(AuthProfile::Type type, const wstring &username, const wstring &uid = L"", const wstring &token = L"");
|
||||
static void removeSelectedProfile();
|
||||
static bool applySelectedProfile();
|
||||
|
||||
|
|
@ -28,3 +30,45 @@ public:
|
|||
static int getSelectedIndex() { return selectedProfile; }
|
||||
static void setSelectedIndex(int idx) { selectedProfile = idx; }
|
||||
};
|
||||
struct AuthResult
|
||||
{
|
||||
bool success;
|
||||
wstring username;
|
||||
wstring uuid;
|
||||
wstring accessToken;
|
||||
wstring error;
|
||||
};
|
||||
|
||||
enum class AuthFlowState : uint8_t
|
||||
{
|
||||
IDLE,
|
||||
WAITING_FOR_USER,
|
||||
POLLING,
|
||||
EXCHANGING,
|
||||
COMPLETE,
|
||||
FAILED
|
||||
};
|
||||
|
||||
class AuthFlow
|
||||
{
|
||||
private:
|
||||
static std::thread workerThread;
|
||||
static std::atomic<AuthFlowState> state;
|
||||
static std::atomic<bool> cancelRequested;
|
||||
static AuthResult result;
|
||||
static wstring userCode;
|
||||
static wstring verificationUri;
|
||||
|
||||
static void microsoftFlowThread();
|
||||
static void elybyFlowThread(const string &username, const string &password);
|
||||
|
||||
public:
|
||||
static void startMicrosoft();
|
||||
static void startElyBy(const wstring &username, const wstring &password);
|
||||
|
||||
static AuthFlowState getState() { return state.load(); }
|
||||
static const AuthResult &getResult() { return result; }
|
||||
static const wstring &getUserCode() { return userCode; }
|
||||
static const wstring &getVerificationUri() { return verificationUri; }
|
||||
static void reset();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@
|
|||
#include "DLCTexturePack.h"
|
||||
#include "..\Minecraft.World\HandshakeManager.h"
|
||||
#include "..\Minecraft.World\AuthModule.h"
|
||||
#include "AuthScreen.h"
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
#include "Xbox\Network\NetworkPlayerXbox.h"
|
||||
|
|
@ -4143,6 +4144,17 @@ void ClientConnection::beginAuth()
|
|||
handshakeManager->registerModule(new KeypairOfflineAuthModule());
|
||||
handshakeManager->registerModule(new OfflineAuthModule());
|
||||
|
||||
const auto &profiles = AuthProfileManager::getProfiles();
|
||||
int idx = AuthProfileManager::getSelectedIndex();
|
||||
if (idx >= 0 && idx < static_cast<int>(profiles.size()))
|
||||
{
|
||||
const auto &p = profiles[idx];
|
||||
wstring variation;
|
||||
if (p.type == AuthProfile::MICROSOFT) variation = L"mojang";
|
||||
else if (p.type == AuthProfile::ELYBY) variation = L"elyby";
|
||||
handshakeManager->setCredentials(p.token, p.uid, p.username, variation);
|
||||
}
|
||||
|
||||
auto initial = handshakeManager->createInitialPacket();
|
||||
if (initial) send(initial);
|
||||
}
|
||||
|
|
@ -4155,6 +4167,8 @@ void ClientConnection::handleAuth(const shared_ptr<AuthPacket> &packet)
|
|||
auto response = handshakeManager->handlePacket(packet);
|
||||
if (response) send(response);
|
||||
|
||||
for (auto &p : handshakeManager->drainPendingPackets())
|
||||
send(p);
|
||||
if (handshakeManager->isComplete())
|
||||
{
|
||||
authComplete = true;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ UIScene_Keyboard::UIScene_Keyboard(int iPad, void *initData, UILayer *parentLaye
|
|||
const wchar_t* defaultText = L"";
|
||||
|
||||
m_bPCMode = false;
|
||||
m_eKeyboardMode = C_4JInput::EKeyboardMode_Default;
|
||||
if (initData)
|
||||
{
|
||||
UIKeyboardInitData* kbData = static_cast<UIKeyboardInitData *>(initData);
|
||||
|
|
@ -36,6 +37,7 @@ UIScene_Keyboard::UIScene_Keyboard(int iPad, void *initData, UILayer *parentLaye
|
|||
if (kbData->defaultText) defaultText = kbData->defaultText;
|
||||
m_win64MaxChars = kbData->maxChars;
|
||||
m_bPCMode = kbData->pcMode;
|
||||
m_eKeyboardMode = kbData->keyboardMode;
|
||||
}
|
||||
|
||||
m_win64TextBuffer = defaultText;
|
||||
|
|
@ -276,7 +278,12 @@ void UIScene_Keyboard::tick()
|
|||
}
|
||||
|
||||
if (changed)
|
||||
m_KeyboardTextInput.setLabel(m_win64TextBuffer.c_str(), true /*instant*/);
|
||||
{
|
||||
if (m_eKeyboardMode == C_4JInput::EKeyboardMode_Password)
|
||||
m_KeyboardTextInput.setLabel(wstring(m_win64TextBuffer.length(), L'*').c_str(), true);
|
||||
else
|
||||
m_KeyboardTextInput.setLabel(m_win64TextBuffer.c_str(), true /*instant*/);
|
||||
}
|
||||
|
||||
if (m_bPCMode)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ private:
|
|||
wstring m_win64TextBuffer;
|
||||
int m_win64MaxChars;
|
||||
bool m_bPCMode; // Hides on-screen keyboard buttons; physical keyboard only
|
||||
C_4JInput::EKeyboardMode m_eKeyboardMode;
|
||||
int m_iCursorPos;
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ UIScene_MainMenu::UIScene_MainMenu(int iPad, void *initData, UILayer *parentLaye
|
|||
m_eAction=eAction_None;
|
||||
m_bIgnorePress=false;
|
||||
|
||||
// auto-apply saved auth profile on startup
|
||||
AuthProfileManager::load();
|
||||
AuthProfileManager::applySelectedProfile();
|
||||
|
||||
m_buttons[static_cast<int>(eControl_PlayGame)].init(IDS_PLAY_GAME,eControl_PlayGame);
|
||||
|
||||
|
|
@ -455,6 +458,41 @@ void UIScene_MainMenu::RunAction(int iPad)
|
|||
}
|
||||
|
||||
static int s_authPad = 0;
|
||||
static void *s_authParam = nullptr;
|
||||
static wstring s_elybyUsername;
|
||||
static bool s_authFlowActive = false;
|
||||
|
||||
static wstring ReadKeyboardText(int maxLen)
|
||||
{
|
||||
vector<uint16_t> buf(maxLen, 0);
|
||||
#ifdef _WINDOWS64
|
||||
Win64_GetKeyboardText(buf.data(), maxLen);
|
||||
#else
|
||||
InputManager.GetText(buf.data());
|
||||
#endif
|
||||
wstring result;
|
||||
for (int i = 0; i < maxLen && buf[i]; i++)
|
||||
result += static_cast<wchar_t>(buf[i]);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void ShowAuthMessageBox(int iPad, const wchar_t *title, const wchar_t *text,
|
||||
const wchar_t **options, int optionCount,
|
||||
int(*func)(LPVOID, int, C4JStorage::EMessageResult), void *lpParam, bool keepOpen = false)
|
||||
{
|
||||
MessageBoxInfo param = {};
|
||||
param.uiOptionC = optionCount;
|
||||
param.dwPad = iPad;
|
||||
param.Func = func;
|
||||
param.lpParam = lpParam;
|
||||
param.rawTitle = title;
|
||||
param.rawText = text;
|
||||
param.rawOptions = options;
|
||||
ui.NavigateToScene(iPad, eUIScene_MessageBox, ¶m, eUILayer_Alert, eUIGroup_Fullscreen);
|
||||
if (keepOpen)
|
||||
if (auto *scene = ui.FindScene(eUIScene_MessageBox))
|
||||
static_cast<UIScene_MessageBox *>(scene)->setKeepOpen(true);
|
||||
}
|
||||
|
||||
static const wchar_t *BuildAuthProfileText()
|
||||
{
|
||||
|
|
@ -484,34 +522,18 @@ static const wchar_t *BuildAuthProfileText()
|
|||
void UIScene_MainMenu::ShowAuthMenu(int iPad, void *pClass)
|
||||
{
|
||||
s_authPad = iPad;
|
||||
s_authParam = pClass;
|
||||
|
||||
static const wchar_t *authOptions[] = { L"Next", L"Use", L"Add", L"Back" };
|
||||
MessageBoxInfo param = {};
|
||||
param.uiOptionC = 4;
|
||||
param.dwPad = iPad;
|
||||
param.Func = &UIScene_MainMenu::AuthMenuReturned;
|
||||
param.lpParam = pClass;
|
||||
param.rawTitle = L"Authentication";
|
||||
param.rawText = BuildAuthProfileText();
|
||||
param.rawOptions = authOptions;
|
||||
ui.NavigateToScene(iPad, eUIScene_MessageBox, ¶m, eUILayer_Alert, eUIGroup_Fullscreen);
|
||||
UIScene *scene = ui.FindScene(eUIScene_MessageBox);
|
||||
if (scene)
|
||||
static_cast<UIScene_MessageBox *>(scene)->setKeepOpen(true);
|
||||
ShowAuthMessageBox(iPad, L"Authentication", BuildAuthProfileText(),
|
||||
authOptions, 4, &UIScene_MainMenu::AuthMenuReturned, pClass, true);
|
||||
}
|
||||
|
||||
void UIScene_MainMenu::ShowAuthAddMenu(int iPad, void *pClass)
|
||||
{
|
||||
static const wchar_t *addOptions[] = { L"Microsoft Auth", L"Ely.by Auth", L"Add Offline User", L"Back" };
|
||||
MessageBoxInfo param = {};
|
||||
param.uiOptionC = 4;
|
||||
param.dwPad = iPad;
|
||||
param.Func = &UIScene_MainMenu::AuthAddMenuReturned;
|
||||
param.lpParam = pClass;
|
||||
param.rawTitle = L"Add Profile";
|
||||
param.rawText = L"Select an auth type";
|
||||
param.rawOptions = addOptions;
|
||||
ui.NavigateToScene(iPad, eUIScene_MessageBox, ¶m, eUILayer_Alert, eUIGroup_Fullscreen);
|
||||
ShowAuthMessageBox(iPad, L"Add Profile", L"Select an auth type",
|
||||
addOptions, 4, &UIScene_MainMenu::AuthAddMenuReturned, pClass);
|
||||
}
|
||||
|
||||
int UIScene_MainMenu::AuthMenuReturned(LPVOID lpParam, int iPad, const C4JStorage::EMessageResult result)
|
||||
|
|
@ -534,6 +556,7 @@ int UIScene_MainMenu::AuthMenuReturned(LPVOID lpParam, int iPad, const C4JStorag
|
|||
{
|
||||
ui.NavigateBack(iPad);
|
||||
AuthProfileManager::applySelectedProfile();
|
||||
AuthProfileManager::save();
|
||||
break;
|
||||
}
|
||||
case C4JStorage::EMessage_ResultThirdOption:
|
||||
|
|
@ -556,13 +579,31 @@ int UIScene_MainMenu::AuthAddMenuReturned(LPVOID lpParam, int iPad, const C4JSto
|
|||
switch (result)
|
||||
{
|
||||
case C4JStorage::EMessage_ResultAccept:
|
||||
AuthProfileManager::addProfile(AuthProfile::MICROSOFT, L"Player");
|
||||
ShowAuthMenu(iPad, lpParam);
|
||||
{
|
||||
AuthFlow::startMicrosoft();
|
||||
s_authFlowActive = true;
|
||||
|
||||
static const wchar_t *cancelOptions[] = { L"Cancel" };
|
||||
ShowAuthMessageBox(iPad, L"Microsoft Login", L"Starting...",
|
||||
cancelOptions, 1, &UIScene_MainMenu::AuthMsFlowReturned, lpParam, true);
|
||||
break;
|
||||
}
|
||||
case C4JStorage::EMessage_ResultDecline:
|
||||
AuthProfileManager::addProfile(AuthProfile::ELYBY, L"Player");
|
||||
ShowAuthMenu(iPad, lpParam);
|
||||
{
|
||||
#ifdef _WINDOWS64
|
||||
UIKeyboardInitData kbData;
|
||||
kbData.title = L"Ely.by Username";
|
||||
kbData.defaultText = L"";
|
||||
kbData.maxChars = 64;
|
||||
kbData.callback = &UIScene_MainMenu::ElyByUsernameReturned;
|
||||
kbData.lpParam = lpParam;
|
||||
kbData.pcMode = g_KBMInput.IsKBMActive();
|
||||
ui.NavigateToScene(iPad, eUIScene_Keyboard, &kbData);
|
||||
#else
|
||||
InputManager.RequestKeyboard(L"Ely.by Username", L"", (DWORD)0, 64, &UIScene_MainMenu::ElyByUsernameReturned, lpParam, C_4JInput::EKeyboardMode_Default);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case C4JStorage::EMessage_ResultThirdOption:
|
||||
{
|
||||
#ifdef _WINDOWS64
|
||||
|
|
@ -586,24 +627,72 @@ int UIScene_MainMenu::AuthAddMenuReturned(LPVOID lpParam, int iPad, const C4JSto
|
|||
return 0;
|
||||
}
|
||||
|
||||
int UIScene_MainMenu::AuthMsFlowReturned(LPVOID lpParam, int iPad, const C4JStorage::EMessageResult result)
|
||||
{
|
||||
s_authFlowActive = false;
|
||||
AuthFlow::reset();
|
||||
ui.NavigateBack(iPad);
|
||||
ShowAuthMenu(iPad, lpParam);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int UIScene_MainMenu::ElyByUsernameReturned(LPVOID lpParam, const bool bRes)
|
||||
{
|
||||
if (bRes)
|
||||
{
|
||||
wstring name = ReadKeyboardText(128);
|
||||
if (!name.empty())
|
||||
{
|
||||
s_elybyUsername = std::move(name);
|
||||
#ifdef _WINDOWS64
|
||||
UIKeyboardInitData kbData;
|
||||
kbData.title = L"Ely.by Password";
|
||||
kbData.defaultText = L"";
|
||||
kbData.maxChars = 128;
|
||||
kbData.callback = &UIScene_MainMenu::ElyByPasswordReturned;
|
||||
kbData.lpParam = lpParam;
|
||||
kbData.pcMode = g_KBMInput.IsKBMActive();
|
||||
kbData.keyboardMode = C_4JInput::EKeyboardMode_Password;
|
||||
ui.NavigateToScene(s_authPad, eUIScene_Keyboard, &kbData);
|
||||
#else
|
||||
InputManager.RequestKeyboard(L"Ely.by Password", L"", (DWORD)0, 128, &UIScene_MainMenu::ElyByPasswordReturned, lpParam, C_4JInput::EKeyboardMode_Password);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
ShowAuthMenu(s_authPad, lpParam);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int UIScene_MainMenu::ElyByPasswordReturned(LPVOID lpParam, const bool bRes)
|
||||
{
|
||||
if (bRes)
|
||||
{
|
||||
wstring pass = ReadKeyboardText(256);
|
||||
if (!pass.empty())
|
||||
{
|
||||
AuthFlow::startElyBy(s_elybyUsername, pass);
|
||||
s_authFlowActive = true;
|
||||
|
||||
static const wchar_t *waitOptions[] = { L"Cancel" };
|
||||
ShowAuthMessageBox(s_authPad, L"Ely.by Login", L"Authenticating...",
|
||||
waitOptions, 1, &UIScene_MainMenu::AuthMsFlowReturned, lpParam, true);
|
||||
|
||||
SecureZeroMemory(pass.data(), pass.size() * sizeof(wchar_t));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
ShowAuthMenu(s_authPad, lpParam);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int UIScene_MainMenu::AuthKeyboardReturned(LPVOID lpParam, const bool bRes)
|
||||
{
|
||||
if (bRes)
|
||||
{
|
||||
uint16_t ui16Text[128];
|
||||
ZeroMemory(ui16Text, 128 * sizeof(uint16_t));
|
||||
#ifdef _WINDOWS64
|
||||
Win64_GetKeyboardText(ui16Text, 128);
|
||||
#else
|
||||
InputManager.GetText(ui16Text);
|
||||
#endif
|
||||
if (ui16Text[0] != 0)
|
||||
{
|
||||
wchar_t wName[128] = {};
|
||||
for (int k = 0; k < 127 && ui16Text[k]; k++)
|
||||
wName[k] = static_cast<wchar_t>(ui16Text[k]);
|
||||
AuthProfileManager::addProfile(AuthProfile::OFFLINE, wName);
|
||||
}
|
||||
wstring name = ReadKeyboardText(128);
|
||||
if (!name.empty())
|
||||
AuthProfileManager::addProfile(AuthProfile::OFFLINE, name);
|
||||
}
|
||||
ShowAuthMenu(s_authPad, lpParam);
|
||||
return 0;
|
||||
|
|
@ -2016,6 +2105,54 @@ void UIScene_MainMenu::RunUnlockOrDLC(int iPad)
|
|||
void UIScene_MainMenu::tick()
|
||||
{
|
||||
UIScene::tick();
|
||||
if (s_authFlowActive)
|
||||
{
|
||||
auto flowState = AuthFlow::getState();
|
||||
auto *scene = ui.FindScene(eUIScene_MessageBox);
|
||||
auto *msgBox = scene ? static_cast<UIScene_MessageBox *>(scene) : nullptr;
|
||||
|
||||
switch (flowState)
|
||||
{
|
||||
case AuthFlowState::WAITING_FOR_USER:
|
||||
case AuthFlowState::POLLING:
|
||||
{
|
||||
if (msgBox && !AuthFlow::getUserCode().empty())
|
||||
{
|
||||
static wstring statusText;
|
||||
statusText = L"Go to: " + AuthFlow::getVerificationUri() + L"\nEnter code: " + AuthFlow::getUserCode();
|
||||
if (flowState == AuthFlowState::POLLING) statusText += L"\n\nWaiting for login...";
|
||||
msgBox->updateContent(statusText.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AuthFlowState::EXCHANGING:
|
||||
{
|
||||
if (msgBox) msgBox->updateContent(L"Exchanging tokens...");
|
||||
break;
|
||||
}
|
||||
case AuthFlowState::COMPLETE:
|
||||
{
|
||||
s_authFlowActive = false;
|
||||
const auto &r = AuthFlow::getResult();
|
||||
auto type = AuthFlow::getUserCode().empty() ? AuthProfile::ELYBY : AuthProfile::MICROSOFT;
|
||||
AuthProfileManager::addProfile(type, r.username, r.uuid, r.accessToken);
|
||||
AuthFlow::reset();
|
||||
if (scene) ui.NavigateBack(s_authPad);
|
||||
ShowAuthMenu(s_authPad, s_authParam);
|
||||
break;
|
||||
}
|
||||
case AuthFlowState::FAILED:
|
||||
{
|
||||
s_authFlowActive = false;
|
||||
const auto &r = AuthFlow::getResult();
|
||||
if (msgBox) msgBox->updateContent(r.error.empty() ? L"Authentication failed" : r.error.c_str());
|
||||
AuthFlow::reset();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( (eNavigateWhenReady >= 0) )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -150,6 +150,9 @@ private:
|
|||
static int AuthMenuReturned(void *pParam, int iPad, C4JStorage::EMessageResult result);
|
||||
static int AuthAddMenuReturned(void *pParam, int iPad, C4JStorage::EMessageResult result);
|
||||
static int AuthKeyboardReturned(LPVOID lpParam, const bool bRes);
|
||||
static int AuthMsFlowReturned(void *pParam, int iPad, C4JStorage::EMessageResult result);
|
||||
static int ElyByUsernameReturned(LPVOID lpParam, const bool bRes);
|
||||
static int ElyByPasswordReturned(LPVOID lpParam, const bool bRes);
|
||||
|
||||
static void LoadTrial();
|
||||
|
||||
|
|
|
|||
|
|
@ -144,6 +144,8 @@ void UIScene_MessageBox::handlePress(F64 controlId, F64 childId)
|
|||
void UIScene_MessageBox::updateContent(const wchar_t *text)
|
||||
{
|
||||
m_labelContent.init(text);
|
||||
IggyDataValue result;
|
||||
IggyPlayerCallMethodRS(getMovie(), &result, IggyPlayerRootPath(getMovie()), m_funcAutoResize, 0, nullptr);
|
||||
}
|
||||
|
||||
bool UIScene_MessageBox::hasFocus(int iPad)
|
||||
|
|
|
|||
|
|
@ -295,8 +295,9 @@ typedef struct _UIKeyboardInitData
|
|||
int(*callback)(LPVOID, const bool);
|
||||
LPVOID lpParam;
|
||||
bool pcMode; // When true, disables on-screen keyboard buttons (PC keyboard users only need the text field)
|
||||
C_4JInput::EKeyboardMode keyboardMode;
|
||||
|
||||
_UIKeyboardInitData() : title(nullptr), defaultText(nullptr), maxChars(25), callback(nullptr), lpParam(nullptr), pcMode(false) {}
|
||||
_UIKeyboardInitData() : title(nullptr), defaultText(nullptr), maxChars(25), callback(nullptr), lpParam(nullptr), pcMode(false), keyboardMode(C_4JInput::EKeyboardMode_Default) {}
|
||||
} UIKeyboardInitData;
|
||||
|
||||
// Stores the text typed in UIScene_Keyboard so callbacks can retrieve it
|
||||
|
|
|
|||
|
|
@ -5,11 +5,6 @@
|
|||
#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";
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@ using namespace std;
|
|||
#include <utility>
|
||||
#include <unordered_map>
|
||||
|
||||
inline string narrowStr(const wstring &w)
|
||||
{
|
||||
return string(w.begin(), w.end());
|
||||
}
|
||||
|
||||
class AuthModule
|
||||
{
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,19 @@
|
|||
#include "stdafx.h"
|
||||
#include "HandshakeManager.h"
|
||||
#include "AuthModule.h"
|
||||
#include "HttpClient.h"
|
||||
#include "StringHelpers.h"
|
||||
#include "Common/vendor/nlohmann/json.hpp"
|
||||
|
||||
static constexpr auto PROTOCOL_VERSION = L"1.0";
|
||||
|
||||
static wstring getField(const vector<pair<wstring, wstring>> &fields, const wchar_t *key)
|
||||
{
|
||||
for (const auto &[k, v] : fields)
|
||||
if (k == key) return v;
|
||||
return {};
|
||||
}
|
||||
|
||||
HandshakeManager::HandshakeManager(bool isServer)
|
||||
: isServer(isServer), state(HandshakeState::IDLE), activeModule(nullptr)
|
||||
{
|
||||
|
|
@ -20,6 +30,21 @@ void HandshakeManager::registerModule(AuthModule *module)
|
|||
modules[module->schemeName()] = module;
|
||||
}
|
||||
|
||||
void HandshakeManager::setCredentials(const wstring &token, const wstring &uid, const wstring &username, const wstring &variation)
|
||||
{
|
||||
accessToken = token;
|
||||
clientUid = uid;
|
||||
clientUsername = username;
|
||||
preferredVariation = variation;
|
||||
}
|
||||
|
||||
vector<shared_ptr<AuthPacket>> HandshakeManager::drainPendingPackets()
|
||||
{
|
||||
vector<shared_ptr<AuthPacket>> out;
|
||||
out.swap(pendingPackets);
|
||||
return out;
|
||||
}
|
||||
|
||||
shared_ptr<AuthPacket> HandshakeManager::handlePacket(const shared_ptr<AuthPacket> &packet)
|
||||
{
|
||||
return isServer ? handleServer(packet) : handleClient(packet);
|
||||
|
|
@ -37,10 +62,7 @@ shared_ptr<AuthPacket> HandshakeManager::handleServer(const shared_ptr<AuthPacke
|
|||
{
|
||||
case AuthStage::ANNOUNCE_VERSION:
|
||||
{
|
||||
protocolVersion = L"";
|
||||
for (const auto &[k, v] : packet->fields)
|
||||
if (k == L"version") protocolVersion = v;
|
||||
|
||||
protocolVersion = getField(packet->fields, L"version");
|
||||
if (protocolVersion != PROTOCOL_VERSION)
|
||||
return fail();
|
||||
|
||||
|
|
@ -58,11 +80,7 @@ shared_ptr<AuthPacket> HandshakeManager::handleServer(const shared_ptr<AuthPacke
|
|||
|
||||
case AuthStage::ACCEPT_SCHEME:
|
||||
{
|
||||
wstring variation;
|
||||
for (const auto &[k, v] : packet->fields)
|
||||
if (k == L"variation") variation = v;
|
||||
|
||||
activeVariation = variation;
|
||||
activeVariation = getField(packet->fields, L"variation");
|
||||
state = HandshakeState::SETTINGS_SENT;
|
||||
auto settings = activeModule->getSettings(activeVariation);
|
||||
return makePacket(AuthStage::SCHEME_SETTINGS, std::move(settings));
|
||||
|
|
@ -87,14 +105,7 @@ shared_ptr<AuthPacket> HandshakeManager::handleServer(const shared_ptr<AuthPacke
|
|||
|
||||
case AuthStage::AUTH_DONE:
|
||||
{
|
||||
wstring uid, username;
|
||||
for (const auto &[k, v] : packet->fields)
|
||||
{
|
||||
if (k == L"uid") uid = v;
|
||||
else if (k == L"username") username = v;
|
||||
}
|
||||
|
||||
if (uid != finalUid || username != finalUsername)
|
||||
if (getField(packet->fields, L"uid") != finalUid || getField(packet->fields, L"username") != finalUsername)
|
||||
return fail();
|
||||
|
||||
state = HandshakeState::IDENTITY_ASSIGNED;
|
||||
|
|
@ -106,14 +117,7 @@ shared_ptr<AuthPacket> HandshakeManager::handleServer(const shared_ptr<AuthPacke
|
|||
|
||||
case AuthStage::CONFIRM_IDENTITY:
|
||||
{
|
||||
wstring uid, username;
|
||||
for (const auto &[k, v] : packet->fields)
|
||||
{
|
||||
if (k == L"uid") uid = v;
|
||||
else if (k == L"username") username = v;
|
||||
}
|
||||
|
||||
if (uid != finalUid || username != finalUsername)
|
||||
if (getField(packet->fields, L"uid") != finalUid || getField(packet->fields, L"username") != finalUsername)
|
||||
return fail();
|
||||
|
||||
state = HandshakeState::COMPLETE;
|
||||
|
|
@ -131,12 +135,8 @@ shared_ptr<AuthPacket> HandshakeManager::handleClient(const shared_ptr<AuthPacke
|
|||
{
|
||||
case AuthStage::DECLARE_SCHEME:
|
||||
{
|
||||
wstring scheme;
|
||||
for (const auto &[k, v] : packet->fields)
|
||||
{
|
||||
if (k == L"version") protocolVersion = v;
|
||||
else if (k == L"scheme") scheme = v;
|
||||
}
|
||||
protocolVersion = getField(packet->fields, L"version");
|
||||
wstring scheme = getField(packet->fields, L"scheme");
|
||||
|
||||
if (protocolVersion != PROTOCOL_VERSION)
|
||||
return fail();
|
||||
|
|
@ -148,7 +148,11 @@ shared_ptr<AuthPacket> HandshakeManager::handleClient(const shared_ptr<AuthPacke
|
|||
activeModule = it->second;
|
||||
|
||||
auto variations = activeModule->supportedVariations();
|
||||
activeVariation = variations.empty() ? L"" : variations[0];
|
||||
if (!preferredVariation.empty() &&
|
||||
std::find(variations.begin(), variations.end(), preferredVariation) != variations.end())
|
||||
activeVariation = preferredVariation;
|
||||
else
|
||||
activeVariation = variations.empty() ? L"" : variations[0];
|
||||
|
||||
state = HandshakeState::SCHEME_ACCEPTED;
|
||||
return makePacket(AuthStage::ACCEPT_SCHEME, {{L"variation", activeVariation}});
|
||||
|
|
@ -156,17 +160,38 @@ shared_ptr<AuthPacket> HandshakeManager::handleClient(const shared_ptr<AuthPacke
|
|||
|
||||
case AuthStage::SCHEME_SETTINGS:
|
||||
{
|
||||
wstring serverId = getField(packet->fields, L"serverId");
|
||||
wstring sessionEndpoint = getField(packet->fields, L"sessionEndpoint");
|
||||
wstring scheme(activeModule->schemeName());
|
||||
if (scheme == L"mcconsoles:session" && !accessToken.empty())
|
||||
{
|
||||
nlohmann::json body = {
|
||||
{"accessToken", narrowStr(accessToken)},
|
||||
{"selectedProfile", narrowStr(clientUid)},
|
||||
{"serverId", narrowStr(serverId)}
|
||||
};
|
||||
auto resp = HttpClient::post(narrowStr(sessionEndpoint) + "/session/minecraft/join", body.dump());
|
||||
if (resp.statusCode != 204)
|
||||
return fail();
|
||||
}
|
||||
|
||||
state = HandshakeState::AUTH_IN_PROGRESS;
|
||||
return makePacket(AuthStage::BEGIN_AUTH);
|
||||
pendingPackets.push_back(makePacket(AuthStage::BEGIN_AUTH));
|
||||
pendingPackets.push_back(makePacket(AuthStage::AUTH_DATA, {
|
||||
{L"uid", clientUid},
|
||||
{L"username", clientUsername}
|
||||
}));
|
||||
pendingPackets.push_back(makePacket(AuthStage::AUTH_DONE, {
|
||||
{L"uid", clientUid},
|
||||
{L"username", clientUsername}
|
||||
}));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
case AuthStage::ASSIGN_IDENTITY:
|
||||
{
|
||||
for (const auto &[k, v] : packet->fields)
|
||||
{
|
||||
if (k == L"uid") finalUid = v;
|
||||
else if (k == L"username") finalUsername = v;
|
||||
}
|
||||
finalUid = getField(packet->fields, L"uid");
|
||||
finalUsername = getField(packet->fields, L"username");
|
||||
|
||||
state = HandshakeState::IDENTITY_CONFIRMED;
|
||||
return makePacket(AuthStage::CONFIRM_IDENTITY, {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,13 @@ private:
|
|||
wstring activeVariation;
|
||||
wstring protocolVersion;
|
||||
|
||||
wstring accessToken;
|
||||
wstring clientUid;
|
||||
wstring clientUsername;
|
||||
wstring preferredVariation;
|
||||
|
||||
vector<shared_ptr<AuthPacket>> pendingPackets;
|
||||
|
||||
public:
|
||||
wstring finalUid;
|
||||
wstring finalUsername;
|
||||
|
|
@ -41,8 +48,10 @@ public:
|
|||
~HandshakeManager();
|
||||
|
||||
void registerModule(AuthModule *module);
|
||||
void setCredentials(const wstring &token, const wstring &uid, const wstring &username, const wstring &variation = L"");
|
||||
shared_ptr<AuthPacket> handlePacket(const shared_ptr<AuthPacket> &packet);
|
||||
shared_ptr<AuthPacket> createInitialPacket();
|
||||
vector<shared_ptr<AuthPacket>> drainPendingPackets();
|
||||
|
||||
bool isComplete() const { return state == HandshakeState::COMPLETE; }
|
||||
bool isFailed() const { return state == HandshakeState::FAILED; }
|
||||
|
|
|
|||
|
|
@ -9,13 +9,15 @@ static size_t writeCallback(char *data, size_t size, size_t nmemb, void *userdat
|
|||
return size * nmemb;
|
||||
}
|
||||
|
||||
static HttpResponse performRequest(CURL *curl)
|
||||
static HttpResponse performRequest(CURL *curl, struct curl_slist *extraHeaders = nullptr)
|
||||
{
|
||||
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);
|
||||
if (extraHeaders)
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, extraHeaders);
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
|
||||
|
|
@ -24,20 +26,30 @@ static HttpResponse performRequest(CURL *curl)
|
|||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode);
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
if (extraHeaders)
|
||||
curl_slist_free_all(extraHeaders);
|
||||
return {statusCode, std::move(responseBody)};
|
||||
}
|
||||
|
||||
HttpResponse HttpClient::get(const std::string &url)
|
||||
static struct curl_slist *buildHeaders(const std::vector<std::string> &headers)
|
||||
{
|
||||
struct curl_slist *list = nullptr;
|
||||
for (const auto &h : headers)
|
||||
list = curl_slist_append(list, h.c_str());
|
||||
return list;
|
||||
}
|
||||
|
||||
HttpResponse HttpClient::get(const std::string &url, const std::vector<std::string> &headers)
|
||||
{
|
||||
CURL *curl = curl_easy_init();
|
||||
if (!curl)
|
||||
return {0, ""};
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
return performRequest(curl);
|
||||
return performRequest(curl, headers.empty() ? nullptr : buildHeaders(headers));
|
||||
}
|
||||
|
||||
HttpResponse HttpClient::post(const std::string &url, const std::string &body, const std::string &contentType)
|
||||
HttpResponse HttpClient::post(const std::string &url, const std::string &body, const std::string &contentType, const std::vector<std::string> &headers)
|
||||
{
|
||||
CURL *curl = curl_easy_init();
|
||||
if (!curl)
|
||||
|
|
@ -47,11 +59,8 @@ HttpResponse HttpClient::post(const std::string &url, const std::string &body, c
|
|||
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);
|
||||
auto *headerList = buildHeaders(headers);
|
||||
headerList = curl_slist_append(headerList, ("Content-Type: " + contentType).c_str());
|
||||
|
||||
HttpResponse resp = performRequest(curl);
|
||||
curl_slist_free_all(headers);
|
||||
return resp;
|
||||
return performRequest(curl, headerList);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct HttpResponse
|
||||
{
|
||||
|
|
@ -11,6 +12,6 @@ struct HttpResponse
|
|||
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");
|
||||
static HttpResponse get(const std::string &url, const std::vector<std::string> &headers = {});
|
||||
static HttpResponse post(const std::string &url, const std::string &body, const std::string &contentType = "application/json", const std::vector<std::string> &headers = {});
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue