mirror of
https://forge.banditvault.co.uk/racoon/MinecraftConsoles.git
synced 2026-05-22 00:05:11 +00:00
feat: Implement authenticated JSON request handling with session refresh
This commit is contained in:
@@ -692,6 +692,81 @@ namespace
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sends an authenticated request, transparently refreshing the session
|
||||
// on HTTP 401 and retrying once. Caller fills in `req.path`, `req.body`,
|
||||
// and `req.method` (if non-default); the bearer header is applied here.
|
||||
//
|
||||
// On return:
|
||||
// - true → request reached the server. Inspect *outStatus for the
|
||||
// final HTTP code; treat any non-2xx as a logical failure.
|
||||
// - false → transport failed, or refresh on 401 failed. *outError is
|
||||
// populated with a human-readable reason. The session may
|
||||
// have been cleared (cookie-equivalent expired).
|
||||
bool PerformAuthenticatedJsonRequest(
|
||||
RequestContext &req,
|
||||
DWORD *outStatus,
|
||||
std::string *outResponseBody,
|
||||
std::string *outError)
|
||||
{
|
||||
if (outStatus == nullptr || outResponseBody == nullptr)
|
||||
return false;
|
||||
|
||||
std::string accessToken;
|
||||
std::string refreshToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
{
|
||||
accessToken = g_state.session.accessToken;
|
||||
refreshToken = g_state.session.refreshToken;
|
||||
}
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
{
|
||||
if (outError != nullptr)
|
||||
*outError = "Not signed in to LCELive.";
|
||||
return false;
|
||||
}
|
||||
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
outResponseBody->clear();
|
||||
if (!PerformJsonRequest(req, outStatus, outResponseBody))
|
||||
{
|
||||
if (outError != nullptr)
|
||||
*outError = "Failed to contact LCELive.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*outStatus != 401 || refreshToken.empty())
|
||||
return true;
|
||||
|
||||
// Access token rejected. Try to refresh and retry exactly once.
|
||||
std::string refreshError;
|
||||
if (!RefreshSessionSync(&refreshError))
|
||||
{
|
||||
if (outError != nullptr)
|
||||
*outError = refreshError.empty()
|
||||
? std::string("LCELive sign-in expired.")
|
||||
: refreshError;
|
||||
return false;
|
||||
}
|
||||
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
outResponseBody->clear();
|
||||
if (!PerformJsonRequest(req, outStatus, outResponseBody))
|
||||
{
|
||||
if (outError != nullptr)
|
||||
*outError = "Failed to contact LCELive after refreshing sign-in.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DWORD WINAPI RequestThreadProc(LPVOID)
|
||||
{
|
||||
RequestContext request = {};
|
||||
@@ -1130,24 +1205,18 @@ namespace Win64LceLive
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
std::string accessToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, std::string(), "Not signed in to LCELive." };
|
||||
|
||||
RequestContext req = {};
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/sessions/ticket";
|
||||
req.body = "{}";
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody) || status < 200 || status >= 300)
|
||||
std::string authError;
|
||||
if (!PerformAuthenticatedJsonRequest(req, &status, &responseBody, &authError))
|
||||
return { false, std::string(), authError };
|
||||
|
||||
if (status < 200 || status >= 300)
|
||||
return { false, std::string(), ParseErrorMessage(responseBody, "Failed to obtain join ticket.") };
|
||||
|
||||
const Json responseJson = Json::parse(responseBody, nullptr, false);
|
||||
@@ -1196,23 +1265,18 @@ namespace Win64LceLive
|
||||
FriendsListResult GetFriendsSync()
|
||||
{
|
||||
EnsureInitialized();
|
||||
std::string accessToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, {}, "Not signed in to LCELive." };
|
||||
|
||||
RequestContext req = {};
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/social/friends";
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody) || status < 200 || status >= 300)
|
||||
std::string authError;
|
||||
if (!PerformAuthenticatedJsonRequest(req, &status, &responseBody, &authError))
|
||||
return { false, {}, authError };
|
||||
|
||||
if (status < 200 || status >= 300)
|
||||
return { false, {}, ParseErrorMessage(responseBody, "Failed to get friends list.") };
|
||||
|
||||
const Json responseJson = Json::parse(responseBody, nullptr, false);
|
||||
@@ -1240,23 +1304,18 @@ namespace Win64LceLive
|
||||
PendingRequestsResult GetPendingRequestsSync()
|
||||
{
|
||||
EnsureInitialized();
|
||||
std::string accessToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, {}, {}, "Not signed in to LCELive." };
|
||||
|
||||
RequestContext req = {};
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/social/requests";
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody) || status < 200 || status >= 300)
|
||||
std::string authError;
|
||||
if (!PerformAuthenticatedJsonRequest(req, &status, &responseBody, &authError))
|
||||
return { false, {}, {}, authError };
|
||||
|
||||
if (status < 200 || status >= 300)
|
||||
return { false, {}, {}, ParseErrorMessage(responseBody, "Failed to get pending requests.") };
|
||||
|
||||
const Json responseJson = Json::parse(responseBody, nullptr, false);
|
||||
@@ -1291,18 +1350,6 @@ namespace Win64LceLive
|
||||
SocialActionResult SendFriendRequestSync(const std::string &username)
|
||||
{
|
||||
EnsureInitialized();
|
||||
std::string accessToken;
|
||||
std::string refreshToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
{
|
||||
accessToken = g_state.session.accessToken;
|
||||
refreshToken = g_state.session.refreshToken;
|
||||
}
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, "Not signed in to LCELive." };
|
||||
|
||||
LCELOG("LCE", "sending friend request for username='%s'", username.c_str());
|
||||
|
||||
@@ -1310,41 +1357,17 @@ namespace Win64LceLive
|
||||
bodyJson["username"] = username;
|
||||
|
||||
RequestContext req = {};
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/social/request";
|
||||
req.body = bodyJson.dump();
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/social/request";
|
||||
req.body = bodyJson.dump();
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody))
|
||||
std::string authError;
|
||||
if (!PerformAuthenticatedJsonRequest(req, &status, &responseBody, &authError))
|
||||
{
|
||||
LCELOG("LCE", "friend request transport failure");
|
||||
return { false, "Failed to contact LCELive while sending friend request." };
|
||||
}
|
||||
|
||||
if (status == 401 && !refreshToken.empty())
|
||||
{
|
||||
std::string refreshError;
|
||||
if (RefreshSessionSync(&refreshError))
|
||||
{
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
responseBody.clear();
|
||||
if (!PerformJsonRequest(req, &status, &responseBody))
|
||||
{
|
||||
LCELOG("LCE", "friend request transport failure after refresh");
|
||||
return { false, "Failed to contact LCELive while sending friend request." };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LCELOG("LCE", "friend request refresh failed: %s", refreshError.c_str());
|
||||
return { false, refreshError };
|
||||
}
|
||||
LCELOG("LCE", "friend request transport/auth failure: %s", authError.c_str());
|
||||
return { false, authError };
|
||||
}
|
||||
|
||||
if (status < 200 || status >= 300)
|
||||
@@ -1366,24 +1389,19 @@ namespace Win64LceLive
|
||||
SocialActionResult AcceptFriendRequestSync(const std::string &fromAccountId)
|
||||
{
|
||||
EnsureInitialized();
|
||||
std::string accessToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, "Not signed in to LCELive." };
|
||||
|
||||
RequestContext req = {};
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/social/requests/" + fromAccountId + "/accept";
|
||||
req.body = "{}";
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/social/requests/" + fromAccountId + "/accept";
|
||||
req.body = "{}";
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody) || status < 200 || status >= 300)
|
||||
std::string authError;
|
||||
if (!PerformAuthenticatedJsonRequest(req, &status, &responseBody, &authError))
|
||||
return { false, authError };
|
||||
|
||||
if (status < 200 || status >= 300)
|
||||
return { false, ParseErrorMessage(responseBody, "Failed to accept friend request.") };
|
||||
|
||||
return { true, std::string() };
|
||||
@@ -1392,24 +1410,19 @@ namespace Win64LceLive
|
||||
SocialActionResult DeclineFriendRequestSync(const std::string &fromAccountId)
|
||||
{
|
||||
EnsureInitialized();
|
||||
std::string accessToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, "Not signed in to LCELive." };
|
||||
|
||||
RequestContext req = {};
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/social/requests/" + fromAccountId + "/decline";
|
||||
req.body = "{}";
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/social/requests/" + fromAccountId + "/decline";
|
||||
req.body = "{}";
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody) || status < 200 || status >= 300)
|
||||
std::string authError;
|
||||
if (!PerformAuthenticatedJsonRequest(req, &status, &responseBody, &authError))
|
||||
return { false, authError };
|
||||
|
||||
if (status < 200 || status >= 300)
|
||||
return { false, ParseErrorMessage(responseBody, "Failed to decline friend request.") };
|
||||
|
||||
return { true, std::string() };
|
||||
@@ -1418,24 +1431,19 @@ namespace Win64LceLive
|
||||
SocialActionResult RemoveFriendSync(const std::string &accountId)
|
||||
{
|
||||
EnsureInitialized();
|
||||
std::string accessToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, "Not signed in to LCELive." };
|
||||
|
||||
RequestContext req = {};
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/social/friends/" + accountId;
|
||||
req.method = "DELETE";
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/social/friends/" + accountId;
|
||||
req.method = "DELETE";
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody) || status < 200 || status >= 300)
|
||||
std::string authError;
|
||||
if (!PerformAuthenticatedJsonRequest(req, &status, &responseBody, &authError))
|
||||
return { false, authError };
|
||||
|
||||
if (status < 200 || status >= 300)
|
||||
return { false, ParseErrorMessage(responseBody, "Failed to remove friend.") };
|
||||
|
||||
return { true, std::string() };
|
||||
@@ -1444,23 +1452,18 @@ namespace Win64LceLive
|
||||
GameInvitesResult GetGameInvitesSync()
|
||||
{
|
||||
EnsureInitialized();
|
||||
std::string accessToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, {}, {}, "Not signed in to LCELive." };
|
||||
|
||||
RequestContext req = {};
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/sessions/invites";
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody) || status < 200 || status >= 300)
|
||||
std::string authError;
|
||||
if (!PerformAuthenticatedJsonRequest(req, &status, &responseBody, &authError))
|
||||
return { false, {}, {}, authError };
|
||||
|
||||
if (status < 200 || status >= 300)
|
||||
return { false, {}, {}, ParseErrorMessage(responseBody, "Failed to get game invites.") };
|
||||
|
||||
const Json responseJson = Json::parse(responseBody, nullptr, false);
|
||||
@@ -1505,18 +1508,6 @@ namespace Win64LceLive
|
||||
SocialActionResult SendGameInviteSync(const std::string &recipientAccountId, const std::string &hostIp, int hostPort, const std::string &hostName, const std::string &signalingSessionId)
|
||||
{
|
||||
EnsureInitialized();
|
||||
std::string accessToken;
|
||||
std::string refreshToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
{
|
||||
accessToken = g_state.session.accessToken;
|
||||
refreshToken = g_state.session.refreshToken;
|
||||
}
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, "Not signed in to LCELive." };
|
||||
|
||||
Json bodyJson;
|
||||
bodyJson["recipientAccountId"] = recipientAccountId;
|
||||
@@ -1532,32 +1523,12 @@ namespace Win64LceLive
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/sessions/invites";
|
||||
req.body = bodyJson.dump();
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody))
|
||||
return { false, "Failed to contact LCELive while sending game invite." };
|
||||
|
||||
if (status == 401 && !refreshToken.empty())
|
||||
{
|
||||
std::string refreshError;
|
||||
if (RefreshSessionSync(&refreshError))
|
||||
{
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
responseBody.clear();
|
||||
if (!PerformJsonRequest(req, &status, &responseBody))
|
||||
return { false, "Failed to contact LCELive while sending game invite." };
|
||||
}
|
||||
else
|
||||
{
|
||||
return { false, refreshError };
|
||||
}
|
||||
}
|
||||
std::string authError;
|
||||
if (!PerformAuthenticatedJsonRequest(req, &status, &responseBody, &authError))
|
||||
return { false, authError };
|
||||
|
||||
if (status < 200 || status >= 300)
|
||||
return { false, ParseErrorMessage(responseBody, "Failed to send game invite.") };
|
||||
@@ -1568,24 +1539,19 @@ namespace Win64LceLive
|
||||
GameInviteActionResult AcceptGameInviteSync(const std::string &inviteId)
|
||||
{
|
||||
EnsureInitialized();
|
||||
std::string accessToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, std::string(), std::string(), 0, std::string(), "Not signed in to LCELive." };
|
||||
|
||||
RequestContext req = {};
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/sessions/invites/" + inviteId + "/accept";
|
||||
req.body = "{}";
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody) || status < 200 || status >= 300)
|
||||
std::string authError;
|
||||
if (!PerformAuthenticatedJsonRequest(req, &status, &responseBody, &authError))
|
||||
return { false, std::string(), std::string(), 0, std::string(), authError };
|
||||
|
||||
if (status < 200 || status >= 300)
|
||||
return { false, std::string(), std::string(), 0, std::string(), ParseErrorMessage(responseBody, "Failed to accept game invite.") };
|
||||
|
||||
const Json responseJson = Json::parse(responseBody, nullptr, false);
|
||||
@@ -1605,24 +1571,19 @@ namespace Win64LceLive
|
||||
SocialActionResult DeclineGameInviteSync(const std::string &inviteId)
|
||||
{
|
||||
EnsureInitialized();
|
||||
std::string accessToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, "Not signed in to LCELive." };
|
||||
|
||||
RequestContext req = {};
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/sessions/invites/" + inviteId + "/decline";
|
||||
req.body = "{}";
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody) || status < 200 || status >= 300)
|
||||
std::string authError;
|
||||
if (!PerformAuthenticatedJsonRequest(req, &status, &responseBody, &authError))
|
||||
return { false, authError };
|
||||
|
||||
if (status < 200 || status >= 300)
|
||||
return { false, ParseErrorMessage(responseBody, "Failed to decline game invite.") };
|
||||
|
||||
return { true, std::string() };
|
||||
@@ -1631,24 +1592,19 @@ namespace Win64LceLive
|
||||
SocialActionResult DeactivateGameInvitesSync()
|
||||
{
|
||||
EnsureInitialized();
|
||||
std::string accessToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, "Not signed in to LCELive." };
|
||||
|
||||
RequestContext req = {};
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/sessions/invites/deactivate";
|
||||
req.body = "{}";
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody) || status < 200 || status >= 300)
|
||||
std::string authError;
|
||||
if (!PerformAuthenticatedJsonRequest(req, &status, &responseBody, &authError))
|
||||
return { false, authError };
|
||||
|
||||
if (status < 200 || status >= 300)
|
||||
return { false, ParseErrorMessage(responseBody, "Failed to deactivate game invites.") };
|
||||
|
||||
return { true, std::string() };
|
||||
|
||||
Reference in New Issue
Block a user