beeta/chat-service/src/controllers/ModerationController.cpp

393 lines
15 KiB
C++
Raw Normal View History

2026-01-05 22:54:27 -05:00
#include "ModerationController.h"
#include "ChatWebSocketController.h"
#include "../services/ModerationService.h"
#include "../services/AuthService.h"
#include <json/json.h>
// 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<void(const HttpResponsePtr&)>&& 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<void(const HttpResponsePtr&)>&& 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<void(const HttpResponsePtr&)>&& 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<void(const HttpResponsePtr&)>&& 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<void(const HttpResponsePtr&)>&& 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<void(const HttpResponsePtr&)>&& 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<void(const HttpResponsePtr&)>&& 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<void(const HttpResponsePtr&)>&& 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<void(const HttpResponsePtr&)>&& 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<void(const HttpResponsePtr&)>&& 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);
}