392 lines
15 KiB
C++
392 lines
15 KiB
C++
#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);
|
|
}
|