From 016de9e5cf0d31739f56b1fa05db3dba5bbf17df Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 18 Dec 2025 14:10:50 +0100 Subject: [PATCH] Implement access control enhancements in ChatRoom and related classes - Introduced a new overloaded method `accessAllowed` in `ChatRoom` to include user age verification for adult rooms. - Added `falukant_user_id` method in `ChatUser` to retrieve user ID for age checks. - Implemented `getUserAge` method in `ChatUser` to fetch and decrypt user birthdate from the database for age validation. - Updated `roomAllowed` methods in `Server` and `SSLServer` to utilize the new access control logic, ensuring proper user context is considered. - Enhanced debugging output for better traceability during access checks. --- src/core/chat_room.cpp | 35 ++++++++++++++++- src/core/chat_room.h | 1 + src/core/chat_user.cpp | 74 +++++++++++++++++++++++++++++++++++ src/core/chat_user.h | 2 + src/core/server.cpp | 8 +++- src/core/server.h | 1 + src/core/ssl_server.cpp | 8 +++- src/core/ssl_server.h | 1 + src/lib/tools.cpp | 87 +++++++++++++++++++++++++++++++++++++++++ src/lib/tools.h | 1 + 10 files changed, 213 insertions(+), 5 deletions(-) diff --git a/src/core/chat_room.cpp b/src/core/chat_room.cpp index 16ccecf..bd14f70 100755 --- a/src/core/chat_room.cpp +++ b/src/core/chat_room.cpp @@ -559,7 +559,40 @@ namespace Yc bool ChatRoom::accessAllowed(std::string userName, std::string password) { - return (_allowedUsers.size() == 0 || _password == "" || _password == password || std::find(_allowedUsers.begin(), _allowedUsers.end(), userName) != _allowedUsers.end()); + return accessAllowed(userName, password, nullptr); + } + + bool ChatRoom::accessAllowed(std::string userName, std::string password, std::shared_ptr user) + { + // Basis-Zugriffsprüfung (Passwort, erlaubte User) + bool basicAccess = (_allowedUsers.size() == 0 || _password == "" || _password == password || std::find(_allowedUsers.begin(), _allowedUsers.end(), userName) != _allowedUsers.end()); + + if (!basicAccess) { + return false; + } + + // Altersprüfung: Wenn min_age >= 18 (Adult-Mode), muss User mindestens 18 sein + int minAge = _room.min_age(); + if (minAge >= 18 && _database && user) { + int falukantUserId = user->falukant_user_id(); + if (falukantUserId > 0) { + int userAge = ChatUser::getUserAge(_database, falukantUserId); + if (userAge > 0 && userAge < 18) { + #ifdef YC_DEBUG + std::cout << "[Debug] ChatRoom: User " << userName << " (age " << userAge << ") denied access to adult room (min_age: " << minAge << ")" << std::endl; + #endif + return false; + } + } else { + // Wenn kein falukant_user_id vorhanden, verweigere Zugang zu Adult-Räumen + #ifdef YC_DEBUG + std::cout << "[Debug] ChatRoom: User " << userName << " (no falukant_user_id) denied access to adult room (min_age: " << minAge << ")" << std::endl; + #endif + return false; + } + } + + return true; } bool ChatRoom::userIsInRoom(std::string userName) diff --git a/src/core/chat_room.h b/src/core/chat_room.h index 2b1f555..e574042 100755 --- a/src/core/chat_room.h +++ b/src/core/chat_room.h @@ -60,6 +60,7 @@ namespace Yc void sendToAllUsers(ChatUser::MsgType type, Json::Value message); unsigned int addDice(std::shared_ptr user, int diceValue); bool accessAllowed(std::string userName, std::string password); + bool accessAllowed(std::string userName, std::string password, std::shared_ptr user); bool userIsInRoom(std::string userName); std::shared_ptr findUserByName(std::string userName); std::shared_ptr findUserByToken(std::string token); diff --git a/src/core/chat_user.cpp b/src/core/chat_user.cpp index 78ccdd2..94c94c3 100644 --- a/src/core/chat_user.cpp +++ b/src/core/chat_user.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include namespace Yc { @@ -446,6 +448,11 @@ namespace Yc return _token; } + int ChatUser::falukant_user_id() const + { + return _user.falukant_user_id(); + } + bool ChatUser::validateToken(std::string token) { return (token == _token); @@ -461,6 +468,73 @@ namespace Yc return _wsi != nullptr; } + int ChatUser::getUserAge(std::shared_ptr database, int falukantUserId) + { + if (!database || falukantUserId <= 0) { + return 0; // Kein Alter verfügbar + } + + try { + // Hole verschlüsseltes Geburtsdatum aus user_param mit JOIN auf type.user_param + std::string query = R"( + SELECT up.value + FROM community.user_param up + JOIN "type".user_param tp ON up.param_type_id = tp.id + WHERE up.user_id = )" + std::to_string(falukantUserId) + R"( + AND tp.description = 'birthdate' + LIMIT 1; + )"; + auto result = database->exec(query); + + if (result.size() > 0 && !result[0]["value"].is_null()) { + std::string encryptedValue = result[0]["value"].as(); + + // Entschlüssele den Wert + std::string decryptedValue = Yc::Lib::Tools::decryptAES256ECB(encryptedValue); + + if (decryptedValue.empty()) { + #ifdef YC_DEBUG + std::cerr << "[Debug] ChatUser: Failed to decrypt birthdate for user ID " << falukantUserId << std::endl; + #endif + return 0; + } + + // Parse Datum (Format: YYYY-MM-DD) + std::tm tm = {}; + std::istringstream ss(decryptedValue); + ss >> std::get_time(&tm, "%Y-%m-%d"); + + if (ss.fail()) { + #ifdef YC_DEBUG + std::cerr << "[Debug] ChatUser: Failed to parse birthdate: " << decryptedValue << std::endl; + #endif + return 0; + } + + // Berechne Alter + std::time_t now = std::time(nullptr); + std::tm* nowTm = std::localtime(&now); + + int age = nowTm->tm_year + 1900 - (tm.tm_year + 1900); + if (nowTm->tm_mon < tm.tm_mon || + (nowTm->tm_mon == tm.tm_mon && nowTm->tm_mday < tm.tm_mday)) { + age--; + } + + #ifdef YC_DEBUG + std::cout << "[Debug] ChatUser: Loaded age from database for user ID " << falukantUserId << ": " << age << std::endl; + #endif + return age; + } + } catch (const std::exception& e) { + #ifdef YC_DEBUG + std::cerr << "[Debug] ChatUser: Error loading age from database: " << e.what() << std::endl; + #endif + } + + return 0; // Kein Alter verfügbar + } + std::string ChatUser::loadColorFromDatabase(std::shared_ptr database, std::string userName) { if (!database) { diff --git a/src/core/chat_user.h b/src/core/chat_user.h index 65f63a9..d8fb56a 100644 --- a/src/core/chat_user.h +++ b/src/core/chat_user.h @@ -41,10 +41,12 @@ namespace Yc ~ChatUser(); std::string name() const; std::string getToken() const; + int falukant_user_id() const; bool validateToken(std::string token); bool isUser(std::shared_ptr toValidate); bool isWebSocket() const; static std::string loadColorFromDatabase(std::shared_ptr database, std::string userName); + static int getUserAge(std::shared_ptr database, int falukantUserId); void sendMsg(MsgType type, std::string message, std::string userName, std::string color); void sendMsg(MsgType type, const char *message, std::string userName, std::string color); void sendMsg(MsgType type, Json::Value message, std::string userName, std::string color); diff --git a/src/core/server.cpp b/src/core/server.cpp index 1cc7512..23aeb9a 100755 --- a/src/core/server.cpp +++ b/src/core/server.cpp @@ -224,6 +224,10 @@ namespace Yc { } bool Server::roomAllowed(std::string roomName, std::string userName, std::string password){ + return roomAllowed(roomName, userName, password, nullptr); + } + + bool Server::roomAllowed(std::string roomName, std::string userName, std::string password, std::shared_ptr user){ #ifdef YC_DEBUG std::cout << "[Debug] roomAllowed called with roomName: '" << roomName << "', userName: '" << userName << "'" << std::endl; std::cout << "[Debug] Available rooms: "; @@ -237,7 +241,7 @@ namespace Yc { #ifdef YC_DEBUG std::cout << "[Debug] Checking room: '" << room->name() << "' against requested: '" << roomName << "'" << std::endl; #endif - if (room->name() == roomName && room->accessAllowed(userName, password)) { + if (room->name() == roomName && room->accessAllowed(userName, password, user)) { #ifdef YC_DEBUG std::cout << "[Debug] Room found and access allowed!" << std::endl; #endif @@ -251,7 +255,7 @@ namespace Yc { } bool Server::changeRoom(std::shared_ptr user, std::string newRoom, std::string password) { - if (!roomAllowed(newRoom, user->name(), password)) { + if (!roomAllowed(newRoom, user->name(), password, user)) { return false; } diff --git a/src/core/server.h b/src/core/server.h index e499010..ba54428 100755 --- a/src/core/server.h +++ b/src/core/server.h @@ -22,6 +22,7 @@ namespace Yc { std::vector roomList(); Json::Value jsonRoomList(); bool roomAllowed(std::string roomName, std::string userName, std::string password); + bool roomAllowed(std::string roomName, std::string userName, std::string password, std::shared_ptr user); bool changeRoom(std::shared_ptr user, std::string newRoom, std::string password); int _socket; std::shared_ptr _config; diff --git a/src/core/ssl_server.cpp b/src/core/ssl_server.cpp index fc50df2..b1c46b0 100644 --- a/src/core/ssl_server.cpp +++ b/src/core/ssl_server.cpp @@ -892,8 +892,12 @@ std::vector SSLServer::roomList() { } bool SSLServer::roomAllowed(const std::string& roomName, const std::string& userName, const std::string& password) { + return roomAllowed(roomName, userName, password, nullptr); +} + +bool SSLServer::roomAllowed(const std::string& roomName, const std::string& userName, const std::string& password, std::shared_ptr user) { for (auto &room: _rooms) { - if (room->name() == roomName && room->accessAllowed(userName, password)) { + if (room->name() == roomName && room->accessAllowed(userName, password, user)) { return true; } } @@ -901,7 +905,7 @@ bool SSLServer::roomAllowed(const std::string& roomName, const std::string& user } bool SSLServer::changeRoom(std::shared_ptr user, const std::string& newRoom, const std::string& password) { - if (!roomAllowed(newRoom, user->name(), password)) { + if (!roomAllowed(newRoom, user->name(), password, user)) { return false; } diff --git a/src/core/ssl_server.h b/src/core/ssl_server.h index 6f81b47..85a600e 100644 --- a/src/core/ssl_server.h +++ b/src/core/ssl_server.h @@ -44,6 +44,7 @@ public: // Room management std::vector roomList(); bool roomAllowed(const std::string& roomName, const std::string& userName, const std::string& password); + bool roomAllowed(const std::string& roomName, const std::string& userName, const std::string& password, std::shared_ptr user); bool changeRoom(std::shared_ptr user, const std::string& newRoom, const std::string& password); // User management diff --git a/src/lib/tools.cpp b/src/lib/tools.cpp index ec2d5da..1c237ef 100755 --- a/src/lib/tools.cpp +++ b/src/lib/tools.cpp @@ -2,6 +2,14 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include namespace Yc { namespace Lib { @@ -29,5 +37,84 @@ namespace Yc { return result; } + std::string Tools::decryptAES256ECB(const std::string& encryptedHex) { + try { + // Lade SECRET_KEY aus Umgebungsvariable + const char* secretEnv = std::getenv("SECRET_KEY"); + std::string secret = secretEnv ? secretEnv : "DEV_FALLBACK_SECRET"; + + // Generiere 32-Byte Schlüssel mit scrypt (wie in Node.js: scryptSync(secret, 'salt', 32)) + unsigned char key[32]; + const char* salt = "salt"; + + if (EVP_PBE_scrypt(secret.c_str(), secret.length(), + (const unsigned char*)salt, strlen(salt), + 16384, 8, 1, 0, // N=16384, r=8, p=1, maxmem=0 (unbegrenzt) + key, 32) != 1) { + #ifdef YC_DEBUG + std::cerr << "[Debug] Tools: Error generating scrypt key" << std::endl; + #endif + return ""; + } + + // Konvertiere Hex-String zu Bytes + std::vector encryptedBytes; + for (size_t i = 0; i < encryptedHex.length(); i += 2) { + if (i + 1 >= encryptedHex.length()) break; + std::string byteStr = encryptedHex.substr(i, 2); + unsigned char byte = static_cast(std::stoul(byteStr, nullptr, 16)); + encryptedBytes.push_back(byte); + } + + if (encryptedBytes.empty()) { + return ""; + } + + // Entschlüssele mit AES-256-ECB (ECB verwendet keinen IV, daher nullptr) + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { + return ""; + } + + if (EVP_DecryptInit_ex(ctx, EVP_aes_256_ecb(), nullptr, key, nullptr) != 1) { + EVP_CIPHER_CTX_free(ctx); + return ""; + } + + // Padding aktivieren (PKCS7) + EVP_CIPHER_CTX_set_padding(ctx, 1); + + std::vector decryptedBytes(encryptedBytes.size() + EVP_CIPHER_block_size(EVP_aes_256_ecb())); + int outlen = 0; + int finallen = 0; + + if (EVP_DecryptUpdate(ctx, decryptedBytes.data(), &outlen, + encryptedBytes.data(), encryptedBytes.size()) != 1) { + EVP_CIPHER_CTX_free(ctx); + return ""; + } + + if (EVP_DecryptFinal_ex(ctx, decryptedBytes.data() + outlen, &finallen) != 1) { + EVP_CIPHER_CTX_free(ctx); + return ""; + } + + EVP_CIPHER_CTX_free(ctx); + + // Kombiniere Output + int totalLen = outlen + finallen; + if (totalLen > 0 && totalLen <= static_cast(decryptedBytes.size())) { + return std::string(reinterpret_cast(decryptedBytes.data()), totalLen); + } + + return ""; + } catch (const std::exception& e) { + #ifdef YC_DEBUG + std::cerr << "[Debug] Tools: Error decrypting: " << e.what() << std::endl; + #endif + return ""; + } + } + } // namespace Lib } // namespace Yp diff --git a/src/lib/tools.h b/src/lib/tools.h index 45de509..3a4ba20 100755 --- a/src/lib/tools.h +++ b/src/lib/tools.h @@ -11,6 +11,7 @@ namespace Yc { public: Tools(); static std::string generateRandomString(size_t length = 16); + static std::string decryptAES256ECB(const std::string& encryptedHex); }; } // namespace Lib