From add892f565239bd3e1d983bf70ba751c555e5c1a Mon Sep 17 00:00:00 2001 From: JohnDoe6345789 Date: Fri, 26 Dec 2025 01:28:59 +0000 Subject: [PATCH] code: cpp,dbal,hpp (3 files) --- dbal/cpp/src/client.cpp | 996 +------------------------- dbal/cpp/src/security/nonce_store.hpp | 89 +-- dbal/cpp/src/security/security.hpp | 23 +- 3 files changed, 65 insertions(+), 1043 deletions(-) diff --git a/dbal/cpp/src/client.cpp b/dbal/cpp/src/client.cpp index 237445328..22626925a 100644 --- a/dbal/cpp/src/client.cpp +++ b/dbal/cpp/src/client.cpp @@ -1,56 +1,11 @@ #include "dbal/client.hpp" -#include "entities/lua_script/index.hpp" +#include "entities/index.hpp" #include "store/in_memory_store.hpp" #include -#include -#include -#include -#include namespace dbal { -// Validation helpers -static bool isValidEmail(const std::string& email) { - static const std::regex email_pattern(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})"); - return std::regex_match(email, email_pattern); -} - -static bool isValidUsername(const std::string& username) { - if (username.empty() || username.length() > 50) return false; - static const std::regex username_pattern(R"([a-zA-Z0-9_-]+)"); - return std::regex_match(username, username_pattern); -} - -static bool isValidSlug(const std::string& slug) { - if (slug.empty() || slug.length() > 100) return false; - static const std::regex slug_pattern(R"([a-z0-9-]+)"); - return std::regex_match(slug, slug_pattern); -} - -static bool isValidWorkflowName(const std::string& name) { - return !name.empty() && name.length() <= 255; -} - -static bool isValidWorkflowTrigger(const std::string& trigger) { - static const std::array allowed = {"manual", "schedule", "event", "webhook"}; - return std::find(allowed.begin(), allowed.end(), trigger) != allowed.end(); -} - -static bool isValidPackageName(const std::string& name) { - return !name.empty() && name.length() <= 255; -} - -static bool isValidSemver(const std::string& version) { - static const std::regex semver_pattern(R"(^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$)"); - return std::regex_match(version, semver_pattern); -} - -static std::string packageKey(const std::string& name, const std::string& version) { - return name + "@" + version; -} - Client::Client(const ClientConfig& config) : config_(config) { - // Validate configuration if (config.adapter.empty()) { throw std::invalid_argument("Adapter type must be specified"); } @@ -64,757 +19,99 @@ Client::~Client() { } Result Client::createUser(const CreateUserInput& input) { - // Validation - if (!isValidUsername(input.username)) { - return Error::validationError("Invalid username format (alphanumeric, underscore, hyphen only)"); - } - if (!isValidEmail(input.email)) { - return Error::validationError("Invalid email format"); - } - - auto& store = getStore(); - - // Check for duplicate username - for (const auto& [id, user] : store.users) { - if (user.username == input.username) { - return Error::conflict("Username already exists: " + input.username); - } - if (user.email == input.email) { - return Error::conflict("Email already exists: " + input.email); - } - } - - // Create user - User user; - user.id = store.generateId("user", ++store.user_counter); - user.username = input.username; - user.email = input.email; - user.role = input.role; - user.created_at = std::chrono::system_clock::now(); - user.updated_at = user.created_at; - - store.users[user.id] = user; - - return Result(user); + return entities::user::create(getStore(), input); } Result Client::getUser(const std::string& id) { - if (id.empty()) { - return Error::validationError("User ID cannot be empty"); - } - - auto& store = getStore(); - auto it = store.users.find(id); - - if (it == store.users.end()) { - return Error::notFound("User not found: " + id); - } - - return Result(it->second); + return entities::user::get(getStore(), id); } Result Client::updateUser(const std::string& id, const UpdateUserInput& input) { - if (id.empty()) { - return Error::validationError("User ID cannot be empty"); - } - - auto& store = getStore(); - auto it = store.users.find(id); - - if (it == store.users.end()) { - return Error::notFound("User not found: " + id); - } - - User& user = it->second; - - // Validate and check conflicts - if (input.username.has_value()) { - if (!isValidUsername(input.username.value())) { - return Error::validationError("Invalid username format"); - } - // Check for username conflict - for (const auto& [uid, u] : store.users) { - if (uid != id && u.username == input.username.value()) { - return Error::conflict("Username already exists: " + input.username.value()); - } - } - user.username = input.username.value(); - } - - if (input.email.has_value()) { - if (!isValidEmail(input.email.value())) { - return Error::validationError("Invalid email format"); - } - // Check for email conflict - for (const auto& [uid, u] : store.users) { - if (uid != id && u.email == input.email.value()) { - return Error::conflict("Email already exists: " + input.email.value()); - } - } - user.email = input.email.value(); - } - - if (input.role.has_value()) { - user.role = input.role.value(); - } - - user.updated_at = std::chrono::system_clock::now(); - - return Result(user); + return entities::user::update(getStore(), id, input); } Result Client::deleteUser(const std::string& id) { - if (id.empty()) { - return Error::validationError("User ID cannot be empty"); - } - - auto& store = getStore(); - auto it = store.users.find(id); - - if (it == store.users.end()) { - return Error::notFound("User not found: " + id); - } - - store.users.erase(it); - return Result(true); + return entities::user::remove(getStore(), id); } Result> Client::listUsers(const ListOptions& options) { - auto& store = getStore(); - std::vector users; - - for (const auto& [id, user] : store.users) { - // Apply filters if provided - bool matches = true; - - if (options.filter.find("role") != options.filter.end()) { - std::string role_str = options.filter.at("role"); - // Simple role filtering - if (role_str == "admin" && user.role != UserRole::Admin) matches = false; - if (role_str == "user" && user.role != UserRole::User) matches = false; - } - - if (matches) { - users.push_back(user); - } - } - - // Apply sorting - if (options.sort.find("username") != options.sort.end()) { - std::sort(users.begin(), users.end(), [](const User& a, const User& b) { - return a.username < b.username; - }); - } - - // Apply pagination - int start = (options.page - 1) * options.limit; - int end = std::min(start + options.limit, static_cast(users.size())); - - if (start < static_cast(users.size())) { - return Result>(std::vector(users.begin() + start, users.begin() + end)); - } - - return Result>(std::vector()); + return entities::user::list(getStore(), options); } Result Client::batchCreateUsers(const std::vector& inputs) { - if (inputs.empty()) { - return Result(0); - } - - std::vector created_ids; - for (const auto& input : inputs) { - auto result = createUser(input); - if (result.isError()) { - auto& store = getStore(); - for (const auto& id : created_ids) { - store.users.erase(id); - } - return result.error(); - } - created_ids.push_back(result.value().id); - } - - return Result(static_cast(created_ids.size())); + return entities::user::batchCreate(getStore(), inputs); } Result Client::batchUpdateUsers(const std::vector& updates) { - if (updates.empty()) { - return Result(0); - } - - int updated = 0; - for (const auto& item : updates) { - auto result = updateUser(item.id, item.data); - if (result.isError()) { - return result.error(); - } - updated++; - } - - return Result(updated); + return entities::user::batchUpdate(getStore(), updates); } Result Client::batchDeleteUsers(const std::vector& ids) { - if (ids.empty()) { - return Result(0); - } - - int deleted = 0; - for (const auto& id : ids) { - auto result = deleteUser(id); - if (result.isError()) { - return result.error(); - } - deleted++; - } - - return Result(deleted); + return entities::user::batchDelete(getStore(), ids); } Result Client::createPage(const CreatePageInput& input) { - // Validation - if (!isValidSlug(input.slug)) { - return Error::validationError("Invalid slug format (lowercase, alphanumeric, hyphens only)"); - } - if (input.title.empty() || input.title.length() > 200) { - return Error::validationError("Title must be between 1 and 200 characters"); - } - if (input.level < 0 || input.level > 5) { - return Error::validationError("Level must be between 0 and 5"); - } - - auto& store = getStore(); - - // Check for duplicate slug - if (store.page_slugs.find(input.slug) != store.page_slugs.end()) { - return Error::conflict("Page with slug already exists: " + input.slug); - } - - // Create page - PageView page; - page.id = store.generateId("page", ++store.page_counter); - page.slug = input.slug; - page.title = input.title; - page.description = input.description; - page.level = input.level; - page.layout = input.layout; - page.is_active = input.is_active; - page.created_at = std::chrono::system_clock::now(); - page.updated_at = page.created_at; - - store.pages[page.id] = page; - store.page_slugs[page.slug] = page.id; - - return Result(page); + return entities::page::create(getStore(), input); } Result Client::getPage(const std::string& id) { - if (id.empty()) { - return Error::validationError("Page ID cannot be empty"); - } - - auto& store = getStore(); - auto it = store.pages.find(id); - - if (it == store.pages.end()) { - return Error::notFound("Page not found: " + id); - } - - return Result(it->second); + return entities::page::get(getStore(), id); } Result Client::getPageBySlug(const std::string& slug) { - if (slug.empty()) { - return Error::validationError("Slug cannot be empty"); - } - - auto& store = getStore(); - auto it = store.page_slugs.find(slug); - - if (it == store.page_slugs.end()) { - return Error::notFound("Page not found with slug: " + slug); - } - - return getPage(it->second); + return entities::page::getBySlug(getStore(), slug); } Result Client::updatePage(const std::string& id, const UpdatePageInput& input) { - if (id.empty()) { - return Error::validationError("Page ID cannot be empty"); - } - - auto& store = getStore(); - auto it = store.pages.find(id); - - if (it == store.pages.end()) { - return Error::notFound("Page not found: " + id); - } - - PageView& page = it->second; - std::string old_slug = page.slug; - - // Validate and update fields - if (input.slug.has_value()) { - if (!isValidSlug(input.slug.value())) { - return Error::validationError("Invalid slug format"); - } - // Check for slug conflict - auto slug_it = store.page_slugs.find(input.slug.value()); - if (slug_it != store.page_slugs.end() && slug_it->second != id) { - return Error::conflict("Slug already exists: " + input.slug.value()); - } - // Update slug mapping - store.page_slugs.erase(old_slug); - store.page_slugs[input.slug.value()] = id; - page.slug = input.slug.value(); - } - - if (input.title.has_value()) { - if (input.title.value().empty() || input.title.value().length() > 200) { - return Error::validationError("Title must be between 1 and 200 characters"); - } - page.title = input.title.value(); - } - - if (input.description.has_value()) { - page.description = input.description.value(); - } - - if (input.level.has_value()) { - if (input.level.value() < 0 || input.level.value() > 5) { - return Error::validationError("Level must be between 0 and 5"); - } - page.level = input.level.value(); - } - - if (input.layout.has_value()) { - page.layout = input.layout.value(); - } - - if (input.is_active.has_value()) { - page.is_active = input.is_active.value(); - } - - page.updated_at = std::chrono::system_clock::now(); - - return Result(page); + return entities::page::update(getStore(), id, input); } Result Client::deletePage(const std::string& id) { - if (id.empty()) { - return Error::validationError("Page ID cannot be empty"); - } - - auto& store = getStore(); - auto it = store.pages.find(id); - - if (it == store.pages.end()) { - return Error::notFound("Page not found: " + id); - } - - // Remove slug mapping - store.page_slugs.erase(it->second.slug); - store.pages.erase(it); - - return Result(true); + return entities::page::remove(getStore(), id); } Result> Client::listPages(const ListOptions& options) { - auto& store = getStore(); - std::vector pages; - - for (const auto& [id, page] : store.pages) { - // Apply filters - bool matches = true; - - if (options.filter.find("is_active") != options.filter.end()) { - bool filter_active = options.filter.at("is_active") == "true"; - if (page.is_active != filter_active) matches = false; - } - - if (options.filter.find("level") != options.filter.end()) { - int filter_level = std::stoi(options.filter.at("level")); - if (page.level != filter_level) matches = false; - } - - if (matches) { - pages.push_back(page); - } - } - - // Apply sorting - if (options.sort.find("title") != options.sort.end()) { - std::sort(pages.begin(), pages.end(), [](const PageView& a, const PageView& b) { - return a.title < b.title; - }); - } else if (options.sort.find("created_at") != options.sort.end()) { - std::sort(pages.begin(), pages.end(), [](const PageView& a, const PageView& b) { - return a.created_at < b.created_at; - }); - } - - // Apply pagination - int start = (options.page - 1) * options.limit; - int end = std::min(start + options.limit, static_cast(pages.size())); - - if (start < static_cast(pages.size())) { - return Result>(std::vector(pages.begin() + start, pages.begin() + end)); - } - - return Result>(std::vector()); + return entities::page::list(getStore(), options); } Result Client::createWorkflow(const CreateWorkflowInput& input) { - if (!isValidWorkflowName(input.name)) { - return Error::validationError("Workflow name must be 1-255 characters"); - } - if (!isValidWorkflowTrigger(input.trigger)) { - return Error::validationError("Trigger must be one of manual, schedule, event, webhook"); - } - if (input.created_by.empty()) { - return Error::validationError("created_by is required"); - } - - auto& store = getStore(); - - if (store.workflow_names.find(input.name) != store.workflow_names.end()) { - return Error::conflict("Workflow name already exists: " + input.name); - } - - Workflow workflow; - workflow.id = store.generateId("workflow", ++store.workflow_counter); - workflow.name = input.name; - workflow.description = input.description; - workflow.trigger = input.trigger; - workflow.trigger_config = input.trigger_config; - workflow.steps = input.steps; - workflow.is_active = input.is_active; - workflow.created_by = input.created_by; - workflow.created_at = std::chrono::system_clock::now(); - workflow.updated_at = workflow.created_at; - - store.workflows[workflow.id] = workflow; - store.workflow_names[workflow.name] = workflow.id; - - return Result(workflow); + return entities::workflow::create(getStore(), input); } Result Client::getWorkflow(const std::string& id) { - if (id.empty()) { - return Error::validationError("Workflow ID cannot be empty"); - } - - auto& store = getStore(); - auto it = store.workflows.find(id); - - if (it == store.workflows.end()) { - return Error::notFound("Workflow not found: " + id); - } - - return Result(it->second); + return entities::workflow::get(getStore(), id); } Result Client::updateWorkflow(const std::string& id, const UpdateWorkflowInput& input) { - if (id.empty()) { - return Error::validationError("Workflow ID cannot be empty"); - } - - auto& store = getStore(); - 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 (!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.description.has_value()) { - workflow.description = input.description.value(); - } - - if (input.trigger.has_value()) { - if (!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); + return entities::workflow::update(getStore(), id, input); } Result Client::deleteWorkflow(const std::string& id) { - if (id.empty()) { - return Error::validationError("Workflow ID cannot be empty"); - } - - auto& store = getStore(); - auto it = store.workflows.find(id); - - if (it == store.workflows.end()) { - return Error::notFound("Workflow not found: " + id); - } - - store.workflow_names.erase(it->second.name); - store.workflows.erase(it); - - return Result(true); + return entities::workflow::remove(getStore(), id); } Result> Client::listWorkflows(const ListOptions& options) { - auto& store = getStore(); - std::vector workflows; - - for (const auto& [id, workflow] : store.workflows) { - bool matches = true; - - if (options.filter.find("is_active") != options.filter.end()) { - bool filter_active = options.filter.at("is_active") == "true"; - if (workflow.is_active != filter_active) matches = false; - } - - if (options.filter.find("trigger") != options.filter.end()) { - if (workflow.trigger != options.filter.at("trigger")) matches = false; - } - - if (options.filter.find("created_by") != options.filter.end()) { - if (workflow.created_by != options.filter.at("created_by")) matches = false; - } - - if (matches) { - workflows.push_back(workflow); - } - } - - if (options.sort.find("name") != options.sort.end()) { - std::sort(workflows.begin(), workflows.end(), [](const Workflow& a, const Workflow& b) { - return a.name < b.name; - }); - } else if (options.sort.find("created_at") != options.sort.end()) { - std::sort(workflows.begin(), workflows.end(), [](const Workflow& a, const Workflow& b) { - return a.created_at < b.created_at; - }); - } - - int start = (options.page - 1) * options.limit; - int end = std::min(start + options.limit, static_cast(workflows.size())); - - if (start < static_cast(workflows.size())) { - return Result>(std::vector(workflows.begin() + start, workflows.begin() + end)); - } - - return Result>(std::vector()); + return entities::workflow::list(getStore(), options); } Result Client::createSession(const CreateSessionInput& input) { - if (input.user_id.empty()) { - return Error::validationError("user_id is required"); - } - if (input.token.empty()) { - return Error::validationError("token is required"); - } - - auto& store = getStore(); - if (store.users.find(input.user_id) == store.users.end()) { - return Error::validationError("User not found: " + input.user_id); - } - if (store.session_tokens.find(input.token) != store.session_tokens.end()) { - return Error::conflict("Session token already exists: " + input.token); - } - - Session session; - session.id = store.generateId("session", ++store.session_counter); - session.user_id = input.user_id; - session.token = input.token; - session.expires_at = input.expires_at; - session.created_at = std::chrono::system_clock::now(); - session.last_activity = session.created_at; - - store.sessions[session.id] = session; - store.session_tokens[session.token] = session.id; - - return Result(session); + return entities::session::create(getStore(), input); } Result Client::getSession(const std::string& id) { - if (id.empty()) { - return Error::validationError("Session ID cannot be empty"); - } - - auto& store = getStore(); - auto it = store.sessions.find(id); - - if (it == store.sessions.end()) { - return Error::notFound("Session not found: " + id); - } - - auto now = std::chrono::system_clock::now(); - if (it->second.expires_at <= now) { - store.session_tokens.erase(it->second.token); - store.sessions.erase(it); - return Error::notFound("Session expired: " + id); - } - - return Result(it->second); + return entities::session::get(getStore(), id); } Result Client::updateSession(const std::string& id, const UpdateSessionInput& input) { - if (id.empty()) { - return Error::validationError("Session ID cannot be empty"); - } - - auto& store = getStore(); - auto it = store.sessions.find(id); - - if (it == store.sessions.end()) { - return Error::notFound("Session not found: " + id); - } - - Session& session = it->second; - - if (input.user_id.has_value()) { - if (input.user_id.value().empty()) { - return Error::validationError("user_id is required"); - } - if (store.users.find(input.user_id.value()) == store.users.end()) { - return Error::validationError("User not found: " + input.user_id.value()); - } - session.user_id = input.user_id.value(); - } - - if (input.token.has_value()) { - if (input.token.value().empty()) { - return Error::validationError("token is required"); - } - auto token_it = store.session_tokens.find(input.token.value()); - if (token_it != store.session_tokens.end() && token_it->second != id) { - return Error::conflict("Session token already exists: " + input.token.value()); - } - store.session_tokens.erase(session.token); - store.session_tokens[input.token.value()] = id; - session.token = input.token.value(); - } - - if (input.expires_at.has_value()) { - session.expires_at = input.expires_at.value(); - } - - if (input.last_activity.has_value()) { - session.last_activity = input.last_activity.value(); - } - - return Result(session); + return entities::session::update(getStore(), id, input); } Result Client::deleteSession(const std::string& id) { - if (id.empty()) { - return Error::validationError("Session ID cannot be empty"); - } - - auto& store = getStore(); - 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); + return entities::session::remove(getStore(), id); } Result> Client::listSessions(const ListOptions& options) { - auto& store = getStore(); - auto now = std::chrono::system_clock::now(); - std::vector expired_sessions; - - for (const auto& [id, session] : store.sessions) { - if (session.expires_at <= now) { - expired_sessions.push_back(id); - } - } - - for (const auto& id : expired_sessions) { - auto expired_it = store.sessions.find(id); - if (expired_it == store.sessions.end()) { - continue; - } - store.session_tokens.erase(expired_it->second.token); - store.sessions.erase(expired_it); - } - - std::vector sessions; - - for (const auto& [id, session] : store.sessions) { - bool matches = true; - - if (options.filter.find("user_id") != options.filter.end()) { - if (session.user_id != options.filter.at("user_id")) matches = false; - } - - if (options.filter.find("token") != options.filter.end()) { - if (session.token != options.filter.at("token")) matches = false; - } - - if (matches) { - sessions.push_back(session); - } - } - - if (options.sort.find("created_at") != options.sort.end()) { - std::sort(sessions.begin(), sessions.end(), [](const Session& a, const Session& b) { - return a.created_at < b.created_at; - }); - } else if (options.sort.find("expires_at") != options.sort.end()) { - std::sort(sessions.begin(), sessions.end(), [](const Session& a, const Session& b) { - return a.expires_at < b.expires_at; - }); - } - - int start = (options.page - 1) * options.limit; - int end = std::min(start + options.limit, static_cast(sessions.size())); - - if (start < static_cast(sessions.size())) { - return Result>(std::vector(sessions.begin() + start, sessions.begin() + end)); - } - - return Result>(std::vector()); + return entities::session::list(getStore(), options); } Result Client::createLuaScript(const CreateLuaScriptInput& input) { @@ -838,262 +135,39 @@ Result> Client::listLuaScripts(const ListOptions& options } Result Client::createPackage(const CreatePackageInput& input) { - if (!isValidPackageName(input.name)) { - return Error::validationError("Package name must be 1-255 characters"); - } - if (!isValidSemver(input.version)) { - return Error::validationError("Version must be valid semver"); - } - if (input.author.empty()) { - return Error::validationError("author is required"); - } - - auto& store = getStore(); - std::string key = packageKey(input.name, input.version); - if (store.package_keys.find(key) != store.package_keys.end()) { - return Error::conflict("Package name+version already exists: " + key); - } - - Package package; - package.id = store.generateId("package", ++store.package_counter); - package.name = input.name; - package.version = input.version; - package.description = input.description; - package.author = input.author; - package.manifest = input.manifest; - package.is_installed = input.is_installed; - package.installed_at = input.installed_at; - package.installed_by = input.installed_by; - package.created_at = std::chrono::system_clock::now(); - package.updated_at = package.created_at; - - store.packages[package.id] = package; - store.package_keys[key] = package.id; - - return Result(package); + return entities::package::create(getStore(), input); } Result Client::getPackage(const std::string& id) { - if (id.empty()) { - return Error::validationError("Package ID cannot be empty"); - } - - auto& store = getStore(); - auto it = store.packages.find(id); - - if (it == store.packages.end()) { - return Error::notFound("Package not found: " + id); - } - - return Result(it->second); + return entities::package::get(getStore(), id); } Result Client::updatePackage(const std::string& id, const UpdatePackageInput& input) { - if (id.empty()) { - return Error::validationError("Package ID cannot be empty"); - } - - auto& store = getStore(); - auto it = store.packages.find(id); - - if (it == store.packages.end()) { - return Error::notFound("Package not found: " + id); - } - - Package& package = it->second; - - std::string next_name = input.name.value_or(package.name); - std::string next_version = input.version.value_or(package.version); - - if (!isValidPackageName(next_name)) { - return Error::validationError("Package name must be 1-255 characters"); - } - if (!isValidSemver(next_version)) { - return Error::validationError("Version must be valid semver"); - } - - std::string current_key = packageKey(package.name, package.version); - std::string next_key = packageKey(next_name, next_version); - - if (next_key != current_key) { - auto key_it = store.package_keys.find(next_key); - if (key_it != store.package_keys.end() && key_it->second != id) { - return Error::conflict("Package name+version already exists: " + next_key); - } - store.package_keys.erase(current_key); - store.package_keys[next_key] = id; - } - - package.name = next_name; - package.version = next_version; - - if (input.description.has_value()) { - package.description = input.description.value(); - } - - if (input.author.has_value()) { - if (input.author.value().empty()) { - return Error::validationError("author is required"); - } - package.author = input.author.value(); - } - - if (input.manifest.has_value()) { - package.manifest = input.manifest.value(); - } - - if (input.is_installed.has_value()) { - package.is_installed = input.is_installed.value(); - } - - if (input.installed_at.has_value()) { - package.installed_at = input.installed_at.value(); - } - - if (input.installed_by.has_value()) { - if (input.installed_by.value().empty()) { - return Error::validationError("installed_by is required"); - } - package.installed_by = input.installed_by.value(); - } - - package.updated_at = std::chrono::system_clock::now(); - - return Result(package); + return entities::package::update(getStore(), id, input); } Result Client::deletePackage(const std::string& id) { - if (id.empty()) { - return Error::validationError("Package ID cannot be empty"); - } - - auto& store = getStore(); - auto it = store.packages.find(id); - - if (it == store.packages.end()) { - return Error::notFound("Package not found: " + id); - } - - store.package_keys.erase(packageKey(it->second.name, it->second.version)); - store.packages.erase(it); - - return Result(true); + return entities::package::remove(getStore(), id); } Result> Client::listPackages(const ListOptions& options) { - auto& store = getStore(); - std::vector packages; - - for (const auto& [id, package] : store.packages) { - bool matches = true; - - if (options.filter.find("name") != options.filter.end()) { - if (package.name != options.filter.at("name")) matches = false; - } - - if (options.filter.find("version") != options.filter.end()) { - if (package.version != options.filter.at("version")) matches = false; - } - - if (options.filter.find("author") != options.filter.end()) { - if (package.author != options.filter.at("author")) matches = false; - } - - if (options.filter.find("is_installed") != options.filter.end()) { - bool filter_installed = options.filter.at("is_installed") == "true"; - if (package.is_installed != filter_installed) matches = false; - } - - if (matches) { - packages.push_back(package); - } - } - - if (options.sort.find("name") != options.sort.end()) { - std::sort(packages.begin(), packages.end(), [](const Package& a, const Package& b) { - return a.name < b.name; - }); - } else if (options.sort.find("created_at") != options.sort.end()) { - std::sort(packages.begin(), packages.end(), [](const Package& a, const Package& b) { - return a.created_at < b.created_at; - }); - } - - int start = (options.page - 1) * options.limit; - int end = std::min(start + options.limit, static_cast(packages.size())); - - if (start < static_cast(packages.size())) { - return Result>(std::vector(packages.begin() + start, packages.begin() + end)); - } - - return Result>(std::vector()); + return entities::package::list(getStore(), options); } Result Client::batchCreatePackages(const std::vector& inputs) { - if (inputs.empty()) { - return Result(0); - } - - std::vector created_ids; - for (const auto& input : inputs) { - auto result = createPackage(input); - if (result.isError()) { - auto& store = getStore(); - for (const auto& id : created_ids) { - auto it = store.packages.find(id); - if (it != store.packages.end()) { - store.package_keys.erase(packageKey(it->second.name, it->second.version)); - store.packages.erase(it); - } - } - return result.error(); - } - created_ids.push_back(result.value().id); - } - - return Result(static_cast(created_ids.size())); + return entities::package::batchCreate(getStore(), inputs); } Result Client::batchUpdatePackages(const std::vector& updates) { - if (updates.empty()) { - return Result(0); - } - - int updated = 0; - for (const auto& item : updates) { - auto result = updatePackage(item.id, item.data); - if (result.isError()) { - return result.error(); - } - updated++; - } - - return Result(updated); + return entities::package::batchUpdate(getStore(), updates); } Result Client::batchDeletePackages(const std::vector& ids) { - if (ids.empty()) { - return Result(0); - } - - int deleted = 0; - for (const auto& id : ids) { - auto result = deletePackage(id); - if (result.isError()) { - return result.error(); - } - deleted++; - } - - return Result(deleted); + return entities::package::batchDelete(getStore(), ids); } void Client::close() { - // For in-memory implementation, optionally clear store - // auto& store = getStore(); - // store.users.clear(); - // store.pages.clear(); - // store.page_slugs.clear(); + // For in-memory implementation, optionally clear store. } -} +} // namespace dbal diff --git a/dbal/cpp/src/security/nonce_store.hpp b/dbal/cpp/src/security/nonce_store.hpp index d79abd876..a10af8d0d 100644 --- a/dbal/cpp/src/security/nonce_store.hpp +++ b/dbal/cpp/src/security/nonce_store.hpp @@ -1,105 +1,46 @@ #pragma once /** * @file nonce_store.hpp - * @brief Nonce storage for replay attack prevention - * @details Header-only, thread-safe nonce management + * @brief Nonce storage for replay attack prevention (thread-safe wrapper) */ -#include -#include -#include #include +#include "nonce_check_and_store.hpp" +#include "nonce_cleanup.hpp" +#include "nonce_maybe_cleanup.hpp" +#include "nonce_size.hpp" namespace dbal::security { /** * Thread-safe nonce store with automatic expiry - * Used to prevent replay attacks in signed requests + * Wraps individual nonce functions with mutex protection */ class NonceStore { public: - /** - * @param expiry_seconds How long nonces are remembered - * @param cleanup_interval_seconds How often to purge expired (0 = every check) - */ - explicit NonceStore( - int expiry_seconds = 300, - int cleanup_interval_seconds = 60 - ) - : expiry_seconds_(expiry_seconds) - , cleanup_interval_seconds_(cleanup_interval_seconds) - {} + explicit NonceStore(int expiry_seconds = 300, int cleanup_interval_seconds = 60) { + storage_.expiry_seconds = expiry_seconds; + storage_.cleanup_interval_seconds = cleanup_interval_seconds; + } - /** - * Check if nonce was already used, and mark it as used - * @param nonce The nonce string to check - * @return true if nonce is fresh (not seen before), false if replay - */ bool check_and_store(const std::string& nonce) { std::lock_guard lock(mutex_); - - auto now = std::chrono::steady_clock::now(); - - // Periodic cleanup - maybe_cleanup(now); - - // Check if already exists - auto it = nonces_.find(nonce); - if (it != nonces_.end()) { - // Replay detected - return false; - } - - // Store new nonce - nonces_[nonce] = now; - return true; + nonce_maybe_cleanup(storage_); + return nonce_check_and_store(storage_, nonce); } - /** - * Get count of stored nonces (for monitoring) - */ size_t size() const { std::lock_guard lock(mutex_); - return nonces_.size(); + return nonce_size(storage_); } - /** - * Force cleanup of expired nonces - */ void cleanup() { std::lock_guard lock(mutex_); - do_cleanup(std::chrono::steady_clock::now()); + nonce_cleanup(storage_); } private: - void maybe_cleanup(std::chrono::steady_clock::time_point now) { - auto elapsed = std::chrono::duration_cast( - now - last_cleanup_ - ).count(); - - if (elapsed >= cleanup_interval_seconds_) { - do_cleanup(now); - } - } - - void do_cleanup(std::chrono::steady_clock::time_point now) { - auto cutoff = now - std::chrono::seconds(expiry_seconds_); - - for (auto it = nonces_.begin(); it != nonces_.end(); ) { - if (it->second < cutoff) { - it = nonces_.erase(it); - } else { - ++it; - } - } - - last_cleanup_ = now; - } - - int expiry_seconds_; - int cleanup_interval_seconds_; - std::unordered_map nonces_; - std::chrono::steady_clock::time_point last_cleanup_{}; + NonceStorage storage_; mutable std::mutex mutex_; }; diff --git a/dbal/cpp/src/security/security.hpp b/dbal/cpp/src/security/security.hpp index 2db145779..841a644d0 100644 --- a/dbal/cpp/src/security/security.hpp +++ b/dbal/cpp/src/security/security.hpp @@ -2,8 +2,7 @@ /** * @file security.hpp * @brief Fort Knox Security Suite - includes all security headers - * @details Convenience header to include all security utilities - * Each function is in its own .hpp file (1 function = 1 file) + * @details Each function is in its own .hpp file (1 function = 1 file) * * Usage: * #include "security/security.hpp" @@ -15,11 +14,9 @@ * dbal::security::RateLimiter limiter(100, 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); + * // Or use functions directly + * dbal::security::TokenBucket bucket; + * if (!dbal::security::rate_limit_try_acquire(bucket, 100, 200)) { return 429; } */ // HTTP security headers @@ -47,7 +44,17 @@ #include "generate_nonce.hpp" #include "generate_token.hpp" -// Classes (cohesive units, stay as single files) +// Rate limiting functions +#include "rate_limit_try_acquire.hpp" +#include "rate_limit_remaining.hpp" + +// Nonce functions +#include "nonce_check_and_store.hpp" +#include "nonce_cleanup.hpp" +#include "nonce_maybe_cleanup.hpp" +#include "nonce_size.hpp" + +// Thread-safe wrappers (use these for convenience) #include "rate_limiter.hpp" #include "nonce_store.hpp"