From f61a548cf496d0cb5aad2045c39311d0bb09522a Mon Sep 17 00:00:00 2001 From: JohnDoe6345789 Date: Fri, 26 Dec 2025 01:24:59 +0000 Subject: [PATCH] docs: hpp,dbal,cpp (4 files) --- .../src/entities/workflow/update_workflow.hpp | 59 ++++++++---- dbal/cpp/src/security/secure_random.hpp | 91 +++++++++++++++++++ dbal/cpp/src/security/security.hpp | 53 +++++++++++ docs/permissions-levels.md | 1 + 4 files changed, 188 insertions(+), 16 deletions(-) create mode 100644 dbal/cpp/src/security/security.hpp diff --git a/dbal/cpp/src/entities/workflow/update_workflow.hpp b/dbal/cpp/src/entities/workflow/update_workflow.hpp index 019e57200..02e67cf2d 100644 --- a/dbal/cpp/src/entities/workflow/update_workflow.hpp +++ b/dbal/cpp/src/entities/workflow/update_workflow.hpp @@ -21,33 +21,60 @@ inline Result update(InMemoryStore& store, const std::string& id, cons if (id.empty()) { return Error::validationError("Workflow ID cannot be empty"); } - + auto it = store.workflows.find(id); if (it == store.workflows.end()) { return Error::notFound("Workflow not found: " + id); } - + Workflow& workflow = it->second; - + std::string old_name = workflow.name; + if (input.name.has_value()) { - if (input.name.value().empty() || input.name.value().length() > 100) { - return Error::validationError("Name must be between 1 and 100 characters"); + if (!validation::isValidWorkflowName(input.name.value())) { + return Error::validationError("Workflow name must be 1-255 characters"); } + auto name_it = store.workflow_names.find(input.name.value()); + if (name_it != store.workflow_names.end() && name_it->second != id) { + return Error::conflict("Workflow name already exists: " + input.name.value()); + } + store.workflow_names.erase(old_name); + store.workflow_names[input.name.value()] = id; workflow.name = input.name.value(); } - - if (input.type.has_value()) { - if (!validation::isValidWorkflowType(input.type.value())) { - return Error::validationError("Invalid workflow type"); - } - workflow.type = input.type.value(); + + if (input.description.has_value()) { + workflow.description = input.description.value(); } - - if (input.description.has_value()) workflow.description = input.description.value(); - if (input.config.has_value()) workflow.config = input.config.value(); - if (input.is_active.has_value()) workflow.is_active = input.is_active.value(); - + + if (input.trigger.has_value()) { + if (!validation::isValidWorkflowTrigger(input.trigger.value())) { + return Error::validationError("Trigger must be one of manual, schedule, event, webhook"); + } + workflow.trigger = input.trigger.value(); + } + + if (input.trigger_config.has_value()) { + workflow.trigger_config = input.trigger_config.value(); + } + + if (input.steps.has_value()) { + workflow.steps = input.steps.value(); + } + + if (input.is_active.has_value()) { + workflow.is_active = input.is_active.value(); + } + + if (input.created_by.has_value()) { + if (input.created_by.value().empty()) { + return Error::validationError("created_by is required"); + } + workflow.created_by = input.created_by.value(); + } + workflow.updated_at = std::chrono::system_clock::now(); + return Result(workflow); } diff --git a/dbal/cpp/src/security/secure_random.hpp b/dbal/cpp/src/security/secure_random.hpp index e69de29bb..93c06e583 100644 --- a/dbal/cpp/src/security/secure_random.hpp +++ b/dbal/cpp/src/security/secure_random.hpp @@ -0,0 +1,91 @@ +#pragma once +/** + * @file secure_random.hpp + * @brief Cryptographically secure random number generation + * @details Header-only, uses /dev/urandom or OpenSSL + */ + +#include +#include +#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 +} + +/** + * 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; +} + +/** + * Generate a secure request ID (32 hex chars = 128 bits) + */ +inline std::string generate_request_id() { + return secure_random_hex(16); +} + +/** + * Generate a secure nonce (32 hex chars = 128 bits) + */ +inline std::string generate_nonce() { + return secure_random_hex(16); +} + +/** + * Generate a secure token (64 hex chars = 256 bits) + */ +inline std::string generate_token() { + return secure_random_hex(32); +} + +} // namespace dbal::security diff --git a/dbal/cpp/src/security/security.hpp b/dbal/cpp/src/security/security.hpp new file mode 100644 index 000000000..c166970bf --- /dev/null +++ b/dbal/cpp/src/security/security.hpp @@ -0,0 +1,53 @@ +#pragma once +/** + * @file security.hpp + * @brief Fort Knox Security Suite - includes all security headers + * @details Convenience header to include all security utilities + * + * Usage: + * #include "security/security.hpp" + * + * // Apply headers + * dbal::security::apply_security_headers(response.headers); + * + * // Rate limit + * dbal::security::RateLimiter limiter(100, 200); // 100/sec, burst 200 + * if (!limiter.try_acquire(client_ip)) { return 429; } + * + * // Sign request + * auto sig = dbal::security::hmac_sha256(key, key_len, payload); + * + * // Validate path + * auto safe = dbal::security::validate_path("/data", user_input); + * + * // Validate input + * if (!dbal::security::is_valid_identifier(table_name)) { reject(); } + * + * // Generate secure IDs + * auto id = dbal::security::generate_request_id(); + * + * // Prevent replay + * dbal::security::NonceStore nonces; + * if (!nonces.check_and_store(request.nonce)) { return 401; } + */ + +// Security headers for HTTP responses +#include "secure_headers.hpp" + +// HMAC signing and timing-safe comparison +#include "hmac_signer.hpp" + +// Path traversal prevention +#include "path_validator.hpp" + +// Token bucket rate limiting +#include "rate_limiter.hpp" + +// Input validation and sanitization +#include "input_sanitizer.hpp" + +// Nonce storage for replay prevention +#include "nonce_store.hpp" + +// Cryptographic random generation +#include "secure_random.hpp" diff --git a/docs/permissions-levels.md b/docs/permissions-levels.md index 7b8c63d8f..6a4ab02c9 100644 --- a/docs/permissions-levels.md +++ b/docs/permissions-levels.md @@ -21,6 +21,7 @@ Visit `/levels` to step through each tier. The page renders a grid of cards, hig - `GET /api/levels` echoes the permission catalog as JSON. - Add `?level=` to narrow the response to a single tier when wiring helpers or automation into the UI. - Provide `?cap=` (comma-separated) to return only levels whose capability descriptions mention the given keywords. +- `POST /api/levels` accepts a `{ level, note }` payload for telemetry and responds with the matched landing tier. ## Tooling