docs: hpp,dbal,cpp (4 files)

This commit is contained in:
2025-12-26 01:24:59 +00:00
parent c915d7a5c9
commit f61a548cf4
4 changed files with 188 additions and 16 deletions

View File

@@ -21,33 +21,60 @@ inline Result<Workflow> 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>(workflow);
}

View File

@@ -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 <string>
#include <array>
#include <cstdint>
#include <stdexcept>
#ifdef _WIN32
#include <windows.h>
#include <bcrypt.h>
#pragma comment(lib, "bcrypt.lib")
#else
#include <fstream>
#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<ULONG>(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<char*>(buffer), static_cast<std::streamsize>(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<unsigned char> 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

View File

@@ -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"

View File

@@ -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=<key|id>` to narrow the response to a single tier when wiring helpers or automation into the UI.
- Provide `?cap=<term>` (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