#include "ModerationController.h" #include "ChatWebSocketController.h" #include "../services/ModerationService.h" #include "../services/AuthService.h" #include // Helper to check if user can perform uberban (admins + site moderators only) static bool canUberban(const services::UserClaims& claims) { return claims.isAdmin || claims.isModerator; } // Helper to check if user can perform realm moderation (admin, site mod, or has mod flag) static bool canModerate(const services::UserClaims& claims) { return claims.isAdmin || claims.isModerator; // TODO: Add realm owner and per-realm moderator checks via backend API } void ModerationController::uberbanUser(const HttpRequestPtr& req, std::function&& callback) { auto jsonPtr = req->getJsonObject(); if (!jsonPtr) { Json::Value error; error["error"] = "Invalid JSON"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k400BadRequest); callback(resp); return; } auto& json = *jsonPtr; std::string fingerprint = json.get("fingerprint", "").asString(); std::string reason = json.get("reason", "").asString(); if (fingerprint.empty()) { Json::Value error; error["error"] = "Fingerprint required"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k400BadRequest); callback(resp); return; } // Verify admin or site moderator auto& authService = services::AuthService::getInstance(); auto token = req->getHeader("Authorization"); if (token.find("Bearer ") == 0) token = token.substr(7); auto claims = authService.verifyToken(token); if (!claims.has_value() || !canUberban(*claims)) { Json::Value error; error["error"] = "Unauthorized - only admins and site moderators can uberban"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k401Unauthorized); callback(resp); return; } auto& modService = services::ModerationService::getInstance(); bool success = modService.uberbanUser(fingerprint, claims->userId, reason); Json::Value response; response["success"] = success; auto resp = HttpResponse::newHttpJsonResponse(response); callback(resp); } void ModerationController::unUberbanUser(const HttpRequestPtr& req, std::function&& callback) { auto jsonPtr = req->getJsonObject(); if (!jsonPtr) { Json::Value error; error["error"] = "Invalid JSON"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k400BadRequest); callback(resp); return; } auto& json = *jsonPtr; std::string fingerprint = json.get("fingerprint", "").asString(); if (fingerprint.empty()) { Json::Value error; error["error"] = "Fingerprint required"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k400BadRequest); callback(resp); return; } auto& authService = services::AuthService::getInstance(); auto token = req->getHeader("Authorization"); if (token.find("Bearer ") == 0) token = token.substr(7); auto claims = authService.verifyToken(token); if (!claims.has_value() || !canUberban(*claims)) { Json::Value error; error["error"] = "Unauthorized - only admins and site moderators can remove uberbans"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k401Unauthorized); callback(resp); return; } auto& modService = services::ModerationService::getInstance(); bool success = modService.unUberbanUser(fingerprint, claims->userId); Json::Value response; response["success"] = success; auto resp = HttpResponse::newHttpJsonResponse(response); callback(resp); } void ModerationController::getUberbannedUsers(const HttpRequestPtr& req, std::function&& callback) { auto& authService = services::AuthService::getInstance(); auto token = req->getHeader("Authorization"); if (token.find("Bearer ") == 0) token = token.substr(7); auto claims = authService.verifyToken(token); if (!claims.has_value() || !canUberban(*claims)) { Json::Value error; error["error"] = "Unauthorized"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k401Unauthorized); callback(resp); return; } auto& modService = services::ModerationService::getInstance(); auto uberbannedFingerprints = modService.getUberbannedFingerprints(); Json::Value response; response["uberbannedFingerprints"] = Json::arrayValue; for (const auto& fp : uberbannedFingerprints) { response["uberbannedFingerprints"].append(fp); } auto resp = HttpResponse::newHttpJsonResponse(response); callback(resp); } void ModerationController::banUser(const HttpRequestPtr& req, std::function&& callback) { auto jsonPtr = req->getJsonObject(); if (!jsonPtr) { Json::Value error; error["error"] = "Invalid JSON"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k400BadRequest); callback(resp); return; } auto& json = *jsonPtr; std::string realmId = json.get("realmId", "").asString(); std::string targetUserId = json.get("targetUserId", "").asString(); std::string guestFingerprint = json.get("fingerprint", "").asString(); std::string reason = json.get("reason", "").asString(); // Verify moderator auto& authService = services::AuthService::getInstance(); auto token = req->getHeader("Authorization"); if (token.find("Bearer ") == 0) token = token.substr(7); auto claims = authService.verifyToken(token); if (!claims.has_value() || !canModerate(*claims)) { Json::Value error; error["error"] = "Unauthorized"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k401Unauthorized); callback(resp); return; } auto& modService = services::ModerationService::getInstance(); bool success = modService.banUserFromRealm(realmId, targetUserId, claims->userId, reason, guestFingerprint); Json::Value response; response["success"] = success; auto resp = HttpResponse::newHttpJsonResponse(response); callback(resp); } void ModerationController::unbanUser(const HttpRequestPtr& req, std::function&& callback) { auto jsonPtr = req->getJsonObject(); if (!jsonPtr) { Json::Value error; error["error"] = "Invalid JSON"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k400BadRequest); callback(resp); return; } auto& json = *jsonPtr; std::string realmId = json.get("realmId", "").asString(); std::string targetUserId = json.get("targetUserId", "").asString(); std::string guestFingerprint = json.get("fingerprint", "").asString(); auto& authService = services::AuthService::getInstance(); auto token = req->getHeader("Authorization"); if (token.find("Bearer ") == 0) token = token.substr(7); auto claims = authService.verifyToken(token); if (!claims.has_value() || !canModerate(*claims)) { Json::Value error; error["error"] = "Unauthorized"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k401Unauthorized); callback(resp); return; } auto& modService = services::ModerationService::getInstance(); bool success = modService.unbanUserFromRealm(realmId, targetUserId, claims->userId, guestFingerprint); Json::Value response; response["success"] = success; auto resp = HttpResponse::newHttpJsonResponse(response); callback(resp); } void ModerationController::getBannedUsers(const HttpRequestPtr& req, std::function&& callback, const std::string& realmId) { auto& modService = services::ModerationService::getInstance(); auto bannedIdentifiers = modService.getRealmBannedIdentifiers(realmId); Json::Value response; response["bannedUsers"] = Json::arrayValue; for (const auto& identifier : bannedIdentifiers) { response["bannedUsers"].append(identifier); } auto resp = HttpResponse::newHttpJsonResponse(response); callback(resp); } void ModerationController::kickUser(const HttpRequestPtr& req, std::function&& callback) { auto jsonPtr = req->getJsonObject(); if (!jsonPtr) { Json::Value error; error["error"] = "Invalid JSON"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k400BadRequest); callback(resp); return; } auto& json = *jsonPtr; std::string realmId = json.get("realmId", "").asString(); std::string targetUserId = json.get("targetUserId", "").asString(); std::string reason = json.get("reason", "").asString(); int duration = json.get("duration", 60).asInt(); // Default 1 minute auto& authService = services::AuthService::getInstance(); auto token = req->getHeader("Authorization"); if (token.find("Bearer ") == 0) token = token.substr(7); auto claims = authService.verifyToken(token); if (!claims.has_value() || !canModerate(*claims)) { Json::Value error; error["error"] = "Unauthorized"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k401Unauthorized); callback(resp); return; } auto& modService = services::ModerationService::getInstance(); bool success = modService.kickUser(realmId, targetUserId, claims->userId, reason, duration); Json::Value response; response["success"] = success; auto resp = HttpResponse::newHttpJsonResponse(response); callback(resp); } void ModerationController::muteUser(const HttpRequestPtr& req, std::function&& callback) { auto jsonPtr = req->getJsonObject(); if (!jsonPtr) { Json::Value error; error["error"] = "Invalid JSON"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k400BadRequest); callback(resp); return; } auto& json = *jsonPtr; std::string realmId = json.get("realmId", "").asString(); std::string targetUserId = json.get("targetUserId", "").asString(); int duration = json.get("duration", 0).asInt(); // 0 = permanent (default) std::string reason = json.get("reason", "").asString(); auto& authService = services::AuthService::getInstance(); auto token = req->getHeader("Authorization"); if (token.find("Bearer ") == 0) token = token.substr(7); auto claims = authService.verifyToken(token); if (!claims.has_value() || !canModerate(*claims)) { Json::Value error; error["error"] = "Unauthorized"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k401Unauthorized); callback(resp); return; } auto& modService = services::ModerationService::getInstance(); bool success = modService.muteUser(realmId, targetUserId, claims->userId, duration, reason); Json::Value response; response["success"] = success; auto resp = HttpResponse::newHttpJsonResponse(response); callback(resp); } void ModerationController::unmuteUser(const HttpRequestPtr& req, std::function&& callback) { auto jsonPtr = req->getJsonObject(); if (!jsonPtr) { Json::Value error; error["error"] = "Invalid JSON"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k400BadRequest); callback(resp); return; } auto& json = *jsonPtr; std::string realmId = json.get("realmId", "").asString(); std::string targetUserId = json.get("targetUserId", "").asString(); auto& authService = services::AuthService::getInstance(); auto token = req->getHeader("Authorization"); if (token.find("Bearer ") == 0) token = token.substr(7); auto claims = authService.verifyToken(token); if (!claims.has_value() || !canModerate(*claims)) { Json::Value error; error["error"] = "Unauthorized"; auto resp = HttpResponse::newHttpJsonResponse(error); resp->setStatusCode(k401Unauthorized); callback(resp); return; } auto& modService = services::ModerationService::getInstance(); bool success = modService.unmuteUser(realmId, targetUserId, claims->userId); Json::Value response; response["success"] = success; auto resp = HttpResponse::newHttpJsonResponse(response); callback(resp); } // Internal API: Called by backend to uberban a user // Option C: All registered user uberbans are deferred - fingerprint captured on reconnect // If user is connected: disconnect them + set pending_uberban (fingerprint captured on reconnect) // If not connected: backend should set pending_uberban // Returns { disconnected: true/false } void ModerationController::internalUberbanUser(const HttpRequestPtr& req, std::function&& callback, const std::string& userId) { // This is an internal API - no auth check needed (only accessible from within Docker network) // The backend AdminController handles authentication before calling this // Try to disconnect the user if they're connected (sets pending_uberban + disconnects) std::string result = ChatWebSocketController::tryUberbanConnectedUser(userId); Json::Value response; if (result == "disconnected") { // User was connected - disconnected and pending_uberban set // Fingerprint will be captured on reconnect response["disconnected"] = true; response["immediate"] = false; // Fingerprint captured on reconnect LOG_INFO << "Internal uberban: User " << userId << " was connected, disconnected (pending uberban)"; } else { // User not connected - caller should set pending_uberban response["disconnected"] = false; response["immediate"] = false; LOG_INFO << "Internal uberban: User " << userId << " not connected, pending uberban needed"; } auto resp = HttpResponse::newHttpJsonResponse(response); callback(resp); }