From bbd8acb296b1e1cf8f30ef41de52bb9b5e4f4178 Mon Sep 17 00:00:00 2001 From: JohnDoe6345789 Date: Fri, 26 Dec 2025 01:26:19 +0000 Subject: [PATCH] code: hpp,dbal,cpp (16 files) --- .../src/entities/session/delete_session.hpp | 8 +-- .../cpp/src/security/contains_sql_keyword.hpp | 39 ++++++++++++ dbal/cpp/src/security/generate_nonce.hpp | 20 ++++++ dbal/cpp/src/security/generate_request_id.hpp | 20 ++++++ dbal/cpp/src/security/generate_token.hpp | 20 ++++++ dbal/cpp/src/security/hmac_sha256.hpp | 47 ++++++++++++++ dbal/cpp/src/security/is_safe_filename.hpp | 33 ++++++++++ dbal/cpp/src/security/is_valid_identifier.hpp | 39 ++++++++++++ dbal/cpp/src/security/is_valid_uuid.hpp | 38 +++++++++++ dbal/cpp/src/security/sanitize_string.hpp | 39 ++++++++++++ dbal/cpp/src/security/secure_random_bytes.hpp | 46 ++++++++++++++ dbal/cpp/src/security/secure_random_hex.hpp | 34 ++++++++++ dbal/cpp/src/security/timing_safe_equal.hpp | 30 +++++++++ dbal/cpp/src/security/validate_length.hpp | 38 +++++++++++ dbal/cpp/src/security/validate_path.hpp | 63 +++++++++++++++++++ frontends/nextjs/src/app/api/health/route.ts | 11 ++++ 16 files changed, 521 insertions(+), 4 deletions(-) create mode 100644 dbal/cpp/src/security/contains_sql_keyword.hpp create mode 100644 dbal/cpp/src/security/generate_nonce.hpp create mode 100644 dbal/cpp/src/security/generate_request_id.hpp create mode 100644 dbal/cpp/src/security/generate_token.hpp create mode 100644 dbal/cpp/src/security/hmac_sha256.hpp create mode 100644 dbal/cpp/src/security/is_safe_filename.hpp create mode 100644 dbal/cpp/src/security/is_valid_identifier.hpp create mode 100644 dbal/cpp/src/security/is_valid_uuid.hpp create mode 100644 dbal/cpp/src/security/sanitize_string.hpp create mode 100644 dbal/cpp/src/security/secure_random_bytes.hpp create mode 100644 dbal/cpp/src/security/secure_random_hex.hpp create mode 100644 dbal/cpp/src/security/timing_safe_equal.hpp create mode 100644 dbal/cpp/src/security/validate_length.hpp create mode 100644 dbal/cpp/src/security/validate_path.hpp create mode 100644 frontends/nextjs/src/app/api/health/route.ts diff --git a/dbal/cpp/src/entities/session/delete_session.hpp b/dbal/cpp/src/entities/session/delete_session.hpp index 07210cc98..cce34172c 100644 --- a/dbal/cpp/src/entities/session/delete_session.hpp +++ b/dbal/cpp/src/entities/session/delete_session.hpp @@ -14,21 +14,21 @@ namespace entities { namespace session { /** - * Delete a session by ID (logout) + * Delete a session by ID */ inline Result remove(InMemoryStore& store, const std::string& id) { if (id.empty()) { return Error::validationError("Session ID cannot be empty"); } - + auto it = store.sessions.find(id); if (it == store.sessions.end()) { return Error::notFound("Session not found: " + id); } - + store.session_tokens.erase(it->second.token); store.sessions.erase(it); - + return Result(true); } diff --git a/dbal/cpp/src/security/contains_sql_keyword.hpp b/dbal/cpp/src/security/contains_sql_keyword.hpp new file mode 100644 index 000000000..c880e2044 --- /dev/null +++ b/dbal/cpp/src/security/contains_sql_keyword.hpp @@ -0,0 +1,39 @@ +#pragma once +/** + * @file contains_sql_keyword.hpp + * @brief SQL keyword detection + */ + +#include +#include +#include + +namespace dbal::security { + +/** + * Check if string matches a SQL keyword (case-insensitive) + * @param value String to check + * @return true if matches SQL keyword + */ +inline bool contains_sql_keyword(const std::string& value) { + static const char* keywords[] = { + "SELECT", "INSERT", "UPDATE", "DELETE", "DROP", "CREATE", "ALTER", + "TRUNCATE", "GRANT", "REVOKE", "UNION", "JOIN", "WHERE", "FROM", + "TABLE", "DATABASE", "INDEX", "VIEW", "PROCEDURE", "FUNCTION", + "TRIGGER", "EXEC", "EXECUTE", "SCHEMA", nullptr + }; + + std::string upper = value; + std::transform(upper.begin(), upper.end(), upper.begin(), + [](unsigned char c) { return std::toupper(c); }); + + for (const char** kw = keywords; *kw != nullptr; ++kw) { + if (upper == *kw) { + return true; + } + } + + return false; +} + +} // namespace dbal::security diff --git a/dbal/cpp/src/security/generate_nonce.hpp b/dbal/cpp/src/security/generate_nonce.hpp new file mode 100644 index 000000000..adda0b17d --- /dev/null +++ b/dbal/cpp/src/security/generate_nonce.hpp @@ -0,0 +1,20 @@ +#pragma once +/** + * @file generate_nonce.hpp + * @brief Secure nonce generation for replay attack prevention + */ + +#include +#include "secure_random_hex.hpp" + +namespace dbal::security { + +/** + * Generate a secure nonce (32 hex chars = 128 bits) + * @return Cryptographically random nonce + */ +inline std::string generate_nonce() { + return secure_random_hex(16); +} + +} // namespace dbal::security diff --git a/dbal/cpp/src/security/generate_request_id.hpp b/dbal/cpp/src/security/generate_request_id.hpp new file mode 100644 index 000000000..984c7fe58 --- /dev/null +++ b/dbal/cpp/src/security/generate_request_id.hpp @@ -0,0 +1,20 @@ +#pragma once +/** + * @file generate_request_id.hpp + * @brief Secure request ID generation + */ + +#include +#include "secure_random_hex.hpp" + +namespace dbal::security { + +/** + * Generate a secure request ID (32 hex chars = 128 bits) + * @return Cryptographically random request ID + */ +inline std::string generate_request_id() { + return secure_random_hex(16); +} + +} // namespace dbal::security diff --git a/dbal/cpp/src/security/generate_token.hpp b/dbal/cpp/src/security/generate_token.hpp new file mode 100644 index 000000000..0d67ba63f --- /dev/null +++ b/dbal/cpp/src/security/generate_token.hpp @@ -0,0 +1,20 @@ +#pragma once +/** + * @file generate_token.hpp + * @brief Secure token generation + */ + +#include +#include "secure_random_hex.hpp" + +namespace dbal::security { + +/** + * Generate a secure token (64 hex chars = 256 bits) + * @return Cryptographically random token + */ +inline std::string generate_token() { + return secure_random_hex(32); +} + +} // namespace dbal::security diff --git a/dbal/cpp/src/security/hmac_sha256.hpp b/dbal/cpp/src/security/hmac_sha256.hpp new file mode 100644 index 000000000..6cf6a4d43 --- /dev/null +++ b/dbal/cpp/src/security/hmac_sha256.hpp @@ -0,0 +1,47 @@ +#pragma once +/** + * @file hmac_sha256.hpp + * @brief HMAC-SHA256 signature computation + */ + +#include +#include +#include + +namespace dbal::security { + +/** + * Compute HMAC-SHA256 signature + * @param key Secret key bytes + * @param key_len Length of key + * @param data Data to sign + * @return Hex-encoded signature + */ +inline std::string hmac_sha256( + const unsigned char* key, + size_t key_len, + const std::string& data +) { + unsigned char result[EVP_MAX_MD_SIZE]; + unsigned int result_len = 0; + + HMAC( + EVP_sha256(), + key, static_cast(key_len), + reinterpret_cast(data.c_str()), data.size(), + result, &result_len + ); + + std::string hex; + hex.reserve(result_len * 2); + + static const char hex_chars[] = "0123456789abcdef"; + for (unsigned int i = 0; i < result_len; ++i) { + hex += hex_chars[(result[i] >> 4) & 0x0F]; + hex += hex_chars[result[i] & 0x0F]; + } + + return hex; +} + +} // namespace dbal::security diff --git a/dbal/cpp/src/security/is_safe_filename.hpp b/dbal/cpp/src/security/is_safe_filename.hpp new file mode 100644 index 000000000..17113d53c --- /dev/null +++ b/dbal/cpp/src/security/is_safe_filename.hpp @@ -0,0 +1,33 @@ +#pragma once +/** + * @file is_safe_filename.hpp + * @brief Filename safety validation + */ + +#include + +namespace dbal::security { + +/** + * Check if a filename is safe (no path separators, no special names) + * @param filename Filename to validate + * @return true if safe + */ +inline bool is_safe_filename(const std::string& filename) { + if (filename.empty()) return false; + + if (filename.find('/') != std::string::npos) return false; + if (filename.find('\\') != std::string::npos) return false; + + if (filename == "." || filename == "..") return false; + + if (filename.find('\0') != std::string::npos) return false; + + for (char c : filename) { + if (static_cast(c) < 32) return false; + } + + return true; +} + +} // namespace dbal::security diff --git a/dbal/cpp/src/security/is_valid_identifier.hpp b/dbal/cpp/src/security/is_valid_identifier.hpp new file mode 100644 index 000000000..37250c4e1 --- /dev/null +++ b/dbal/cpp/src/security/is_valid_identifier.hpp @@ -0,0 +1,39 @@ +#pragma once +/** + * @file is_valid_identifier.hpp + * @brief Database identifier validation + */ + +#include +#include + +namespace dbal::security { + +/** + * Validate a string identifier (table names, column names, etc.) + * Only allows: a-z, A-Z, 0-9, underscore. Must start with letter/underscore. + * + * @param identifier String to validate + * @param max_length Maximum allowed length (default 64) + * @return true if valid + */ +inline bool is_valid_identifier(const std::string& identifier, size_t max_length = 64) { + if (identifier.empty() || identifier.size() > max_length) { + return false; + } + + char first = identifier[0]; + if (!std::isalpha(static_cast(first)) && first != '_') { + return false; + } + + for (char c : identifier) { + if (!std::isalnum(static_cast(c)) && c != '_') { + return false; + } + } + + return true; +} + +} // namespace dbal::security diff --git a/dbal/cpp/src/security/is_valid_uuid.hpp b/dbal/cpp/src/security/is_valid_uuid.hpp new file mode 100644 index 000000000..33edc8c9c --- /dev/null +++ b/dbal/cpp/src/security/is_valid_uuid.hpp @@ -0,0 +1,38 @@ +#pragma once +/** + * @file is_valid_uuid.hpp + * @brief UUID v4 format validation + */ + +#include +#include + +namespace dbal::security { + +/** + * Validate UUID v4 format + * @param uuid String to validate + * @return true if valid UUID v4 + */ +inline bool is_valid_uuid(const std::string& uuid) { + if (uuid.size() != 36) return false; + + for (size_t i = 0; i < uuid.size(); ++i) { + char c = uuid[i]; + + if (i == 8 || i == 13 || i == 18 || i == 23) { + if (c != '-') return false; + } else if (i == 14) { + if (c != '4') return false; + } else if (i == 19) { + c = std::tolower(static_cast(c)); + if (c != '8' && c != '9' && c != 'a' && c != 'b') return false; + } else { + if (!std::isxdigit(static_cast(c))) return false; + } + } + + return true; +} + +} // namespace dbal::security diff --git a/dbal/cpp/src/security/sanitize_string.hpp b/dbal/cpp/src/security/sanitize_string.hpp new file mode 100644 index 000000000..79259b316 --- /dev/null +++ b/dbal/cpp/src/security/sanitize_string.hpp @@ -0,0 +1,39 @@ +#pragma once +/** + * @file sanitize_string.hpp + * @brief String sanitization utility + */ + +#include + +namespace dbal::security { + +/** + * Sanitize string by removing/replacing dangerous characters + * @param input Input string + * @param allow_newlines Whether to allow newlines + * @return Sanitized string + */ +inline std::string sanitize_string(const std::string& input, bool allow_newlines = false) { + std::string result; + result.reserve(input.size()); + + for (char c : input) { + unsigned char uc = static_cast(c); + + if (c == '\0') continue; + + if (uc < 32) { + if (allow_newlines && (c == '\n' || c == '\r' || c == '\t')) { + result += c; + } + continue; + } + + result += c; + } + + return result; +} + +} // namespace dbal::security diff --git a/dbal/cpp/src/security/secure_random_bytes.hpp b/dbal/cpp/src/security/secure_random_bytes.hpp new file mode 100644 index 000000000..afce64f05 --- /dev/null +++ b/dbal/cpp/src/security/secure_random_bytes.hpp @@ -0,0 +1,46 @@ +#pragma once +/** + * @file secure_random_bytes.hpp + * @brief Cryptographically secure random byte generation + */ + +#include +#include + +#ifdef _WIN32 +#include +#include +#pragma comment(lib, "bcrypt.lib") +#else +#include +#endif + +namespace dbal::security { + +/** + * Generate cryptographically secure random bytes + * @param buffer Destination buffer + * @param size Number of bytes to generate + * @throws std::runtime_error on failure + */ +inline void secure_random_bytes(unsigned char* buffer, size_t size) { +#ifdef _WIN32 + NTSTATUS status = BCryptGenRandom( + nullptr, buffer, static_cast(size), BCRYPT_USE_SYSTEM_PREFERRED_RNG + ); + if (!BCRYPT_SUCCESS(status)) { + throw std::runtime_error("BCryptGenRandom failed"); + } +#else + std::ifstream urandom("/dev/urandom", std::ios::binary); + if (!urandom) { + throw std::runtime_error("Failed to open /dev/urandom"); + } + urandom.read(reinterpret_cast(buffer), static_cast(size)); + if (!urandom) { + throw std::runtime_error("Failed to read from /dev/urandom"); + } +#endif +} + +} // namespace dbal::security diff --git a/dbal/cpp/src/security/secure_random_hex.hpp b/dbal/cpp/src/security/secure_random_hex.hpp new file mode 100644 index 000000000..0a8bc3f2b --- /dev/null +++ b/dbal/cpp/src/security/secure_random_hex.hpp @@ -0,0 +1,34 @@ +#pragma once +/** + * @file secure_random_hex.hpp + * @brief Secure random hex string generation + */ + +#include +#include +#include "secure_random_bytes.hpp" + +namespace dbal::security { + +/** + * Generate a secure random hex string + * @param bytes Number of random bytes (output will be 2x this length) + * @return Hex-encoded random string + */ +inline std::string secure_random_hex(size_t bytes) { + std::vector buffer(bytes); + secure_random_bytes(buffer.data(), bytes); + + static const char hex_chars[] = "0123456789abcdef"; + std::string result; + result.reserve(bytes * 2); + + for (unsigned char b : buffer) { + result += hex_chars[(b >> 4) & 0x0F]; + result += hex_chars[b & 0x0F]; + } + + return result; +} + +} // namespace dbal::security diff --git a/dbal/cpp/src/security/timing_safe_equal.hpp b/dbal/cpp/src/security/timing_safe_equal.hpp new file mode 100644 index 000000000..bc1ec5df8 --- /dev/null +++ b/dbal/cpp/src/security/timing_safe_equal.hpp @@ -0,0 +1,30 @@ +#pragma once +/** + * @file timing_safe_equal.hpp + * @brief Timing-safe string comparison to prevent timing attacks + */ + +#include + +namespace dbal::security { + +/** + * Timing-safe string comparison (prevents timing attacks) + * @param a First string + * @param b Second string + * @return true if equal + */ +inline bool timing_safe_equal(const std::string& a, const std::string& b) { + if (a.size() != b.size()) { + return false; + } + + volatile unsigned char result = 0; + for (size_t i = 0; i < a.size(); ++i) { + result |= static_cast(a[i]) ^ static_cast(b[i]); + } + + return result == 0; +} + +} // namespace dbal::security diff --git a/dbal/cpp/src/security/validate_length.hpp b/dbal/cpp/src/security/validate_length.hpp new file mode 100644 index 000000000..0421e6685 --- /dev/null +++ b/dbal/cpp/src/security/validate_length.hpp @@ -0,0 +1,38 @@ +#pragma once +/** + * @file validate_length.hpp + * @brief String length validation + */ + +#include +#include + +namespace dbal::security { + +/** + * Validate string length within bounds + * @param value String to validate + * @param min_len Minimum length + * @param max_len Maximum length + * @param field_name Field name for error messages + * @throws std::runtime_error if validation fails + */ +inline void validate_length( + const std::string& value, + size_t min_len, + size_t max_len, + const char* field_name = "value" +) { + if (value.size() < min_len) { + throw std::runtime_error( + std::string(field_name) + " too short (min " + std::to_string(min_len) + ")" + ); + } + if (value.size() > max_len) { + throw std::runtime_error( + std::string(field_name) + " too long (max " + std::to_string(max_len) + ")" + ); + } +} + +} // namespace dbal::security diff --git a/dbal/cpp/src/security/validate_path.hpp b/dbal/cpp/src/security/validate_path.hpp new file mode 100644 index 000000000..70caea0eb --- /dev/null +++ b/dbal/cpp/src/security/validate_path.hpp @@ -0,0 +1,63 @@ +#pragma once +/** + * @file validate_path.hpp + * @brief Secure path validation to prevent directory traversal + */ + +#include +#include +#include + +namespace dbal::security { + +/** + * Validate and resolve a path safely within a base directory + * Prevents: ../, encoded traversal, symlink escapes, null bytes + * + * @param base_path Allowed base directory (must be absolute) + * @param user_path User-supplied relative path + * @return Resolved safe absolute path + * @throws std::runtime_error if path is invalid or escapes base + */ +inline std::string validate_path( + const std::string& base_path, + const std::string& user_path +) { + namespace fs = std::filesystem; + + if (user_path.find('\0') != std::string::npos) { + throw std::runtime_error("Path contains null byte"); + } + + if (user_path.find("..") != std::string::npos) { + throw std::runtime_error("Path contains traversal sequence"); + } + + if (!user_path.empty() && (user_path[0] == '/' || user_path[0] == '\\')) { + throw std::runtime_error("Absolute paths not allowed"); + } + + if (user_path.find('%') != std::string::npos) { + throw std::runtime_error("Encoded characters not allowed"); + } + + fs::path base = fs::canonical(base_path); + fs::path combined = base / user_path; + fs::path resolved = fs::weakly_canonical(combined); + + std::string base_str = base.string(); + std::string resolved_str = resolved.string(); + + if (!base_str.empty() && base_str.back() != fs::path::preferred_separator) { + base_str += fs::path::preferred_separator; + } + + if (resolved_str.compare(0, base_str.size(), base_str) != 0 && + resolved_str != base.string()) { + throw std::runtime_error("Path escapes allowed directory"); + } + + return resolved_str; +} + +} // namespace dbal::security diff --git a/frontends/nextjs/src/app/api/health/route.ts b/frontends/nextjs/src/app/api/health/route.ts new file mode 100644 index 000000000..9383db3d7 --- /dev/null +++ b/frontends/nextjs/src/app/api/health/route.ts @@ -0,0 +1,11 @@ +import { NextResponse } from 'next/server' + +import { PERMISSION_LEVELS } from '@/app/levels/levels-data' + +export async function GET() { + return NextResponse.json({ + status: 'ok', + levelCount: PERMISSION_LEVELS.length, + timestamp: new Date().toISOString(), + }) +}