#include #include #include "controllers/StreamController.h" #include "controllers/UserController.h" #include "controllers/AdminController.h" #include "controllers/RealmController.h" #include "controllers/RestreamController.h" #include "services/DatabaseService.h" #include "services/StatsService.h" #include "services/AuthService.h" #include "services/CensorService.h" #include "services/TreasuryService.h" #include #include #include using namespace drogon; int main() { // Simplified signal handlers signal(SIGSEGV, [](int s){ LOG_ERROR << "Signal " << s; exit(s); }); signal(SIGABRT, [](int s){ LOG_ERROR << "Signal " << s; exit(s); }); try { LOG_INFO << "Starting streaming backend server..."; // Create upload directories mkdir("./uploads", 0755); mkdir("./uploads/avatars", 0755); // Initialize DatabaseService LOG_INFO << "Initializing DatabaseService..."; DatabaseService::getInstance().initialize(); // Load config LOG_INFO << "Loading configuration..."; app().loadConfigFile("config.json"); // Initialize StatsService BEFORE registering callbacks LOG_INFO << "Initializing StatsService..."; StatsService::getInstance().initialize(); // Register a pre-routing advice to handle CORS and CSRF protection app().registerPreRoutingAdvice([](const HttpRequestPtr &req, AdviceCallback &&acb, AdviceChainCallback &&accb) { // Handle CORS preflight requests if (req->getMethod() == Options) { auto resp = HttpResponse::newHttpResponse(); resp->setStatusCode(k204NoContent); // Get origin from request std::string origin = req->getHeader("Origin"); if (origin.empty()) { origin = "*"; } resp->addHeader("Access-Control-Allow-Origin", origin); resp->addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); resp->addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); resp->addHeader("Access-Control-Allow-Credentials", "true"); resp->addHeader("Access-Control-Max-Age", "86400"); acb(resp); return; } // SECURITY FIX #18: CSRF protection for state-changing requests // Require Origin or Referer header for POST/PUT/DELETE requests if (req->getMethod() == Post || req->getMethod() == Put || req->getMethod() == Delete) { std::string origin = req->getHeader("Origin"); std::string referer = req->getHeader("Referer"); std::string path = req->getPath(); // Skip CSRF check for API endpoints that use Bearer token auth // (Bearer tokens are not automatically sent by browsers, so CSRF is not a concern) std::string authHeader = req->getHeader("Authorization"); bool hasBearerToken = !authHeader.empty() && authHeader.substr(0, 7) == "Bearer "; // Skip CSRF check for internal endpoints (server-to-server calls) bool isInternalEndpoint = path.find("/api/webhook/") == 0 || path.find("/api/internal/") == 0; // If not using Bearer auth and not an internal endpoint, require Origin or Referer header if (!hasBearerToken && !isInternalEndpoint && origin.empty() && referer.empty()) { LOG_WARN << "CSRF protection: Blocked request without Origin/Referer to " << req->getPath() << " from " << req->getPeerAddr().toIpPort(); auto resp = HttpResponse::newHttpResponse(); resp->setStatusCode(k403Forbidden); resp->setBody("Missing Origin or Referer header"); acb(resp); return; } } accb(); }); // Register post-handling advice to add CORS headers to all responses app().registerPostHandlingAdvice([](const HttpRequestPtr &req, const HttpResponsePtr &resp) { // Get origin from request std::string origin = req->getHeader("Origin"); if (origin.empty()) { origin = "*"; } resp->addHeader("Access-Control-Allow-Origin", origin); resp->addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); resp->addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); resp->addHeader("Access-Control-Allow-Credentials", "true"); }); // Register beginning advice to start the stats timer app().registerBeginningAdvice([]() { LOG_INFO << "Application started successfully"; // Clean up stuck audio processing jobs on startup LOG_INFO << "Cleaning up stuck audio processing jobs..."; auto dbClient = app().getDbClient(); *dbClient << "UPDATE audio_files SET status = 'failed' " "WHERE status = 'processing' AND created_at < NOW() - INTERVAL '5 minutes'" >> [](const drogon::orm::Result& r) { if (r.affectedRows() > 0) { LOG_INFO << "Marked " << r.affectedRows() << " stuck audio jobs as failed"; } } >> [](const drogon::orm::DrogonDbException& e) { LOG_WARN << "Failed to clean up stuck audio jobs: " << e.base().what(); }; // Start the stats polling timer LOG_INFO << "Starting stats polling..."; StatsService::getInstance().startPolling(); // Load censored words from database LOG_INFO << "Loading censored words..."; CensorService::getInstance().loadCensoredWords(); // Start treasury scheduler (hourly check for growth/distribution) LOG_INFO << "Starting treasury scheduler..."; TreasuryService::getInstance().initialize(); TreasuryService::getInstance().startScheduler(); }); app().setTermSignalHandler([]() { LOG_INFO << "Received termination signal, shutting down..."; StatsService::getInstance().shutdown(); TreasuryService::getInstance().shutdown(); app().quit(); }); // Start the application LOG_INFO << "Starting Drogon framework..."; app().run(); } catch (const std::exception& e) { LOG_ERROR << "Exception caught in main: " << e.what(); return 1; } catch (...) { LOG_ERROR << "Unknown exception caught in main"; return 1; } return 0; }