fixes lol
All checks were successful
Build and Push / build-all (push) Successful in 12m8s

This commit is contained in:
doomtube 2026-01-11 19:06:08 -05:00
parent cef4707307
commit 58392b7d6a
10 changed files with 80 additions and 122 deletions

View file

@ -268,13 +268,19 @@ void PyramidController::getPixelInfo(const HttpRequestPtr &req,
}
auto dbClient = app().getDbClient();
// Create local int variables for proper PostgreSQL binding
int faceIdInt = static_cast<int>(faceId);
int xInt = static_cast<int>(x);
int yInt = static_cast<int>(y);
*dbClient << R"(
SELECT p.color, p.placed_at, u.id as user_id, u.username, u.user_color, u.avatar_url
FROM pyramid_pixels p
JOIN users u ON p.placed_by = u.id
WHERE p.face_id = $1 AND p.x = $2 AND p.y = $3
)"
<< static_cast<int>(faceId) << static_cast<int>(x) << static_cast<int>(y)
<< faceIdInt << xInt << yInt
>> [callback, faceId, x, y](const Result& r) {
Json::Value resp;
resp["success"] = true;
@ -484,6 +490,12 @@ void PyramidController::getPixelHistory(const HttpRequestPtr &req,
}
auto dbClient = app().getDbClient();
// Create local int variables for proper PostgreSQL binding
int faceIdInt = static_cast<int>(faceId);
int xInt = static_cast<int>(x);
int yInt = static_cast<int>(y);
*dbClient << R"(
SELECT h.id, h.color, h.placed_at, h.rolled_back, h.rolled_back_at,
u.id as user_id, u.username, u.user_color,
@ -495,7 +507,7 @@ void PyramidController::getPixelHistory(const HttpRequestPtr &req,
ORDER BY h.placed_at DESC
LIMIT 50
)"
<< static_cast<int>(faceId) << static_cast<int>(x) << static_cast<int>(y)
<< faceIdInt << xInt << yInt
>> [callback, faceId, x, y](const Result& r) {
Json::Value resp;
resp["success"] = true;
@ -676,7 +688,7 @@ void PyramidWebSocketController::handlePlacePixel(const WebSocketConnectionPtr &
WHERE pyramid_daily_limits.pixels_placed < 1000
RETURNING pixels_placed
)"
<< connInfo.userId << 1000
<< connInfo.userId
>> [wsConnPtr, dbClient, connInfo, faceId, x, y, color](const Result& r) {
if (r.empty()) {
Json::Value error;

View file

@ -348,66 +348,3 @@ void RestreamController::deleteDestination(const HttpRequestPtr &req,
>> DB_ERROR(callback, "get stream key for delete");
});
}
void RestreamController::testDestination(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &realmId,
const std::string &destinationId) {
int64_t rid = std::stoll(realmId);
int64_t did = std::stoll(destinationId);
verifyRestreamPermission(req, rid, [callback, rid, did](bool authorized, const UserInfo& user) {
if (!authorized) {
callback(jsonError("Unauthorized", k403Forbidden));
return;
}
auto dbClient = app().getDbClient();
// Get the destination and realm info
*dbClient << "SELECT rd.id, rd.name, rd.rtmp_url, rd.stream_key, rd.enabled, "
"r.stream_key as realm_stream_key, r.is_live "
"FROM restream_destinations rd "
"JOIN realms r ON rd.realm_id = r.id "
"WHERE rd.id = $1 AND rd.realm_id = $2"
<< did << rid
>> [callback, did](const Result& r) {
if (r.empty()) {
callback(jsonError("Destination not found", k404NotFound));
return;
}
bool isLive = r[0]["is_live"].as<bool>();
if (!isLive) {
callback(jsonError("Stream must be live to test restream connection"));
return;
}
RestreamDestination dest;
dest.id = r[0]["id"].as<int64_t>();
dest.name = r[0]["name"].as<std::string>();
dest.rtmpUrl = r[0]["rtmp_url"].as<std::string>();
dest.streamKey = r[0]["stream_key"].as<std::string>();
dest.enabled = r[0]["enabled"].as<bool>();
std::string realmStreamKey = r[0]["realm_stream_key"].as<std::string>();
// Try to start the push
RestreamService::getInstance().startPush(realmStreamKey, dest,
[callback, dest](bool success, const std::string& error) {
Json::Value resp;
resp["success"] = success;
if (success) {
resp["message"] = "Restream connection successful";
resp["isConnected"] = true;
} else {
resp["message"] = "Restream connection failed";
resp["error"] = error;
resp["isConnected"] = false;
}
callback(jsonResp(resp, success ? k200OK : k400BadRequest));
});
}
>> DB_ERROR(callback, "test restream destination");
});
}

View file

@ -11,7 +11,6 @@ public:
ADD_METHOD_TO(RestreamController::addDestination, "/api/realms/{1}/restream", Post);
ADD_METHOD_TO(RestreamController::updateDestination, "/api/realms/{1}/restream/{2}", Put);
ADD_METHOD_TO(RestreamController::deleteDestination, "/api/realms/{1}/restream/{2}", Delete);
ADD_METHOD_TO(RestreamController::testDestination, "/api/realms/{1}/restream/{2}/test", Post);
METHOD_LIST_END
void getDestinations(const HttpRequestPtr &req,
@ -32,11 +31,6 @@ public:
const std::string &realmId,
const std::string &destinationId);
void testDestination(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &realmId,
const std::string &destinationId);
private:
// Verify user has restream permission for a realm (owner + restreamer role)
void verifyRestreamPermission(const HttpRequestPtr &req, int64_t realmId,

View file

@ -294,10 +294,19 @@ void UserController::refresh(const HttpRequestPtr &req,
} else {
LOG_DEBUG << "Token refresh failed: " << result.error;
// Clear cookies on failure
// Only clear cookies for definitive auth failures (revoked/disabled)
// Don't clear for transient errors (race conditions, database issues)
// to give user a chance to retry
bool shouldClearCookies =
result.error == "Session has been revoked" ||
result.error == "Account is disabled";
auto response = jsonError(result.error, k401Unauthorized);
clearAuthCookie(response);
clearRefreshCookie(response);
if (shouldClearCookies) {
clearAuthCookie(response);
clearRefreshCookie(response);
LOG_INFO << "Cleared auth cookies due to: " << result.error;
}
callback(response);
}
});

View file

@ -1077,14 +1077,17 @@ void AuthService::validateAndRotateRefreshToken(const std::string& refreshToken,
std::string tokenHash = hashToken(refreshToken);
// Find the token family with this hash
// Find the token family with this hash (check both current and previous hash for multi-tab support)
// The previous hash is valid for a grace period (60 seconds) to handle race conditions
*dbClient << "SELECT rtf.id, rtf.user_id, rtf.family_id, rtf.expires_at, rtf.revoked, "
"u.username, u.is_admin, u.is_moderator, u.is_streamer, u.is_restreamer, "
"u.is_bot, u.is_texter, u.is_pgp_only, u.is_disabled, u.user_color, u.avatar_url, "
"u.token_version, u.screensaver_enabled, u.screensaver_timeout_minutes, u.screensaver_type "
"u.token_version, u.screensaver_enabled, u.screensaver_timeout_minutes, u.screensaver_type, "
"(rtf.current_token_hash = $1) AS is_current_token "
"FROM refresh_token_families rtf "
"JOIN users u ON rtf.user_id = u.id "
"WHERE rtf.current_token_hash = $1"
"WHERE rtf.current_token_hash = $1 "
" OR (rtf.previous_token_hash = $1 AND rtf.previous_hash_expires_at > NOW())"
<< tokenHash
>> [this, dbClient, tokenHash, refreshToken, callback](const Result& r) {
try {
@ -1104,6 +1107,7 @@ void AuthService::validateAndRotateRefreshToken(const std::string& refreshToken,
std::string familyUuid = row["family_id"].as<std::string>();
bool revoked = row["revoked"].as<bool>();
bool isDisabled = row["is_disabled"].isNull() ? false : row["is_disabled"].as<bool>();
bool isCurrentToken = row["is_current_token"].isNull() ? false : row["is_current_token"].as<bool>();
// Check if family is revoked
if (revoked) {
@ -1152,11 +1156,21 @@ void AuthService::validateAndRotateRefreshToken(const std::string& refreshToken,
std::string newAccessToken = generateToken(user);
// Update the family with new token hash
*dbClient << "UPDATE refresh_token_families SET current_token_hash = $1, last_used_at = NOW() "
// Move current hash to previous with 60-second grace period for multi-tab support
// This prevents race conditions where multiple tabs try to refresh simultaneously
*dbClient << "UPDATE refresh_token_families SET "
"previous_token_hash = current_token_hash, "
"previous_hash_expires_at = NOW() + INTERVAL '60 seconds', "
"current_token_hash = $1, "
"last_used_at = NOW() "
"WHERE id = $2"
<< newTokenHash << familyId
>> [callback, newAccessToken, newRefreshToken, familyUuid, user](const Result&) {
LOG_DEBUG << "Rotated refresh token for family: " << familyUuid;
>> [callback, newAccessToken, newRefreshToken, familyUuid, user, isCurrentToken](const Result&) {
if (!isCurrentToken) {
LOG_INFO << "Rotated refresh token for family (from previous token): " << familyUuid;
} else {
LOG_DEBUG << "Rotated refresh token for family: " << familyUuid;
}
RefreshTokenResult result;
result.success = true;
result.accessToken = newAccessToken;