Initial commit - realms platform
This commit is contained in:
parent
c590ab6d18
commit
c717c3751c
234 changed files with 74103 additions and 15231 deletions
392
chat-service/src/controllers/ModerationController.cpp
Normal file
392
chat-service/src/controllers/ModerationController.cpp
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
#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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue