v0.3.16 - Admin system getting better
This commit is contained in:
676
src/config.c
676
src/config.c
@@ -72,11 +72,14 @@ int remove_auth_rule_from_config(const char* rule_type, const char* pattern_type
|
||||
int is_config_table_ready(void);
|
||||
int migrate_config_from_events_to_table(void);
|
||||
int populate_config_table_from_event(const cJSON* event);
|
||||
int handle_config_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi);
|
||||
int handle_config_set_unified(cJSON* event, const char* config_key, const char* config_value, char* error_message, size_t error_size, struct lws* wsi);
|
||||
|
||||
// Forward declarations for tag parsing utilities
|
||||
const char* get_first_tag_name(cJSON* event);
|
||||
const char* get_tag_value(cJSON* event, const char* tag_name, int value_index);
|
||||
int parse_auth_query_parameters(cJSON* event, char** query_type, char** pattern_value);
|
||||
int handle_config_update_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||
|
||||
|
||||
// Current configuration cache
|
||||
@@ -380,48 +383,9 @@ cJSON* load_config_event_from_database(const char* relay_pubkey) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* sql;
|
||||
sqlite3_stmt* stmt;
|
||||
int rc;
|
||||
|
||||
// Configuration is now managed through config table, not events
|
||||
log_info("Configuration events are no longer stored in events table");
|
||||
return NULL;
|
||||
|
||||
cJSON* event = NULL;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
// Reconstruct the event JSON from database columns
|
||||
event = cJSON_CreateObject();
|
||||
if (event) {
|
||||
const char* event_pubkey = (const char*)sqlite3_column_text(stmt, 1);
|
||||
|
||||
cJSON_AddStringToObject(event, "id", (const char*)sqlite3_column_text(stmt, 0));
|
||||
cJSON_AddStringToObject(event, "pubkey", event_pubkey);
|
||||
cJSON_AddNumberToObject(event, "created_at", sqlite3_column_int64(stmt, 2));
|
||||
cJSON_AddNumberToObject(event, "kind", sqlite3_column_int(stmt, 3));
|
||||
cJSON_AddStringToObject(event, "content", (const char*)sqlite3_column_text(stmt, 4));
|
||||
cJSON_AddStringToObject(event, "sig", (const char*)sqlite3_column_text(stmt, 5));
|
||||
|
||||
// If we didn't have admin pubkey in cache, we should update the cache
|
||||
// Note: This will be handled by the cache refresh mechanism automatically
|
||||
|
||||
// Parse tags JSON
|
||||
const char* tags_str = (const char*)sqlite3_column_text(stmt, 6);
|
||||
if (tags_str) {
|
||||
cJSON* tags = cJSON_Parse(tags_str);
|
||||
if (tags) {
|
||||
cJSON_AddItemToObject(event, "tags", tags);
|
||||
} else {
|
||||
cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
|
||||
}
|
||||
} else {
|
||||
cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
return event;
|
||||
}
|
||||
|
||||
// ================================
|
||||
@@ -920,7 +884,7 @@ cJSON* create_default_config_event(const unsigned char* admin_privkey_bytes,
|
||||
|
||||
// Create and sign event using nostr_core_lib
|
||||
cJSON* event = nostr_create_and_sign_event(
|
||||
23455, // kind
|
||||
33334, // kind
|
||||
"C Nostr Relay Configuration", // content
|
||||
tags, // tags
|
||||
admin_privkey_bytes, // private key bytes for signing
|
||||
@@ -1597,7 +1561,7 @@ int process_configuration_event(const cJSON* event) {
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
|
||||
if (!kind_obj || (cJSON_GetNumberValue(kind_obj) != 23455 && cJSON_GetNumberValue(kind_obj) != 23456)) {
|
||||
if (!kind_obj || cJSON_GetNumberValue(kind_obj) != 33334) {
|
||||
log_error("Invalid event kind for configuration");
|
||||
return -1;
|
||||
}
|
||||
@@ -2047,7 +2011,7 @@ int add_pubkeys_to_config_table(void) {
|
||||
// Forward declaration for admin authorization function from main.c
|
||||
extern int is_authorized_admin_event(cJSON* event);
|
||||
|
||||
// Process admin events (updated for new Kind 23455/23456)
|
||||
// Process admin events (updated for Kind 23456)
|
||||
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
log_info("DEBUG: Entering process_admin_event_in_config()");
|
||||
|
||||
@@ -2098,9 +2062,6 @@ int process_admin_event_in_config(cJSON* event, char* error_message, size_t erro
|
||||
// Route to appropriate handler based on kind
|
||||
log_info("DEBUG: Routing to kind-specific handler");
|
||||
switch (kind) {
|
||||
case 23455: // New ephemeral configuration management
|
||||
log_info("DEBUG: Routing to process_admin_config_event (kind 23455)");
|
||||
return process_admin_config_event(event, error_message, error_size);
|
||||
case 23456: // New ephemeral auth rules management
|
||||
log_info("DEBUG: Routing to process_admin_auth_event (kind 23456)");
|
||||
return process_admin_auth_event(event, error_message, error_size, wsi);
|
||||
@@ -2112,7 +2073,7 @@ int process_admin_event_in_config(cJSON* event, char* error_message, size_t erro
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Kind 23455 configuration management events
|
||||
// Handle legacy Kind 33334 configuration management events
|
||||
int process_admin_config_event(cJSON* event, char* error_message, size_t error_size) {
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
int kind = kind_obj ? (int)cJSON_GetNumberValue(kind_obj) : 0;
|
||||
@@ -2143,9 +2104,8 @@ int process_admin_config_event(cJSON* event, char* error_message, size_t error_s
|
||||
if (strcmp(tag_key, "config_query") == 0) {
|
||||
printf(" Config Query: %s\n", tag_val);
|
||||
|
||||
// For now, config queries are not implemented in the unified handler
|
||||
// They would need to be added to handle_kind_23455_unified similar to auth queries
|
||||
snprintf(error_message, error_size, "config queries not yet implemented in unified handler");
|
||||
// Config queries are not implemented for legacy kind 33334
|
||||
snprintf(error_message, error_size, "config queries not supported for legacy kind 33334");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -2592,6 +2552,38 @@ int send_admin_response_event(const cJSON* response_data, const char* recipient_
|
||||
// ================================
|
||||
|
||||
|
||||
// Map query command types to proper response types for frontend routing
|
||||
static const char* map_auth_query_type_to_response(const char* query_type) {
|
||||
if (!query_type) return "auth_rules_unknown";
|
||||
|
||||
if (strcmp(query_type, "all") == 0) {
|
||||
return "auth_rules_all";
|
||||
} else if (strcmp(query_type, "whitelist") == 0) {
|
||||
return "auth_rules_whitelist";
|
||||
} else if (strcmp(query_type, "blacklist") == 0) {
|
||||
return "auth_rules_blacklist";
|
||||
} else if (strcmp(query_type, "pattern") == 0) {
|
||||
return "auth_rules_pattern";
|
||||
} else {
|
||||
return "auth_rules_unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// Map config query command types to proper response types for frontend routing
|
||||
static const char* map_config_query_type_to_response(const char* query_type) {
|
||||
if (!query_type) return "config_unknown";
|
||||
|
||||
if (strcmp(query_type, "all") == 0) {
|
||||
return "config_all";
|
||||
} else if (strcmp(query_type, "category") == 0) {
|
||||
return "config_category";
|
||||
} else if (strcmp(query_type, "key") == 0) {
|
||||
return "config_key";
|
||||
} else {
|
||||
return "config_unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// Build standardized query response
|
||||
cJSON* build_query_response(const char* query_type, cJSON* results_array, int total_count) {
|
||||
if (!query_type || !results_array) return NULL;
|
||||
@@ -2826,6 +2818,33 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
|
||||
printf(" Query type: %s\n", query_type);
|
||||
return handle_auth_query_unified(event, query_type, error_message, error_size, wsi);
|
||||
}
|
||||
else if (strcmp(action_type, "config_query") == 0) {
|
||||
log_info("DEBUG: Routing to config_query handler");
|
||||
const char* query_type = get_tag_value(event, action_type, 1);
|
||||
if (!query_type) {
|
||||
log_error("DEBUG: Missing config_query type parameter");
|
||||
snprintf(error_message, error_size, "invalid: missing config_query type");
|
||||
return -1;
|
||||
}
|
||||
printf(" Query type: %s\n", query_type);
|
||||
return handle_config_query_unified(event, query_type, error_message, error_size, wsi);
|
||||
}
|
||||
else if (strcmp(action_type, "config_set") == 0) {
|
||||
log_info("DEBUG: Routing to config_set handler");
|
||||
const char* config_key = get_tag_value(event, action_type, 1);
|
||||
const char* config_value = get_tag_value(event, action_type, 2);
|
||||
if (!config_key || !config_value) {
|
||||
log_error("DEBUG: Missing config_set parameters");
|
||||
snprintf(error_message, error_size, "invalid: missing config_set key or value");
|
||||
return -1;
|
||||
}
|
||||
printf(" Key: %s, Value: %s\n", config_key, config_value);
|
||||
return handle_config_set_unified(event, config_key, config_value, error_message, error_size, wsi);
|
||||
}
|
||||
else if (strcmp(action_type, "config_update") == 0) {
|
||||
log_info("DEBUG: Routing to config_update handler");
|
||||
return handle_config_update_unified(event, error_message, error_size, wsi);
|
||||
}
|
||||
else if (strcmp(action_type, "system_command") == 0) {
|
||||
log_info("DEBUG: Routing to system_command handler");
|
||||
const char* command = get_tag_value(event, action_type, 1);
|
||||
@@ -2940,8 +2959,9 @@ int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
// Build and send response
|
||||
cJSON* response = build_query_response(query_type, results_array, rule_count);
|
||||
// Build and send response with mapped query type for frontend routing
|
||||
const char* mapped_query_type = map_auth_query_type_to_response(query_type);
|
||||
cJSON* response = build_query_response(mapped_query_type, results_array, rule_count);
|
||||
if (response) {
|
||||
// Get admin pubkey from event for response
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
@@ -2958,6 +2978,7 @@ int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_
|
||||
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||
printf("Total results: %d\n", rule_count);
|
||||
log_success("Auth query completed successfully with signed response");
|
||||
printf(" Response query_type: %s (mapped from %s)\n", mapped_query_type, query_type);
|
||||
cJSON_Delete(response);
|
||||
cJSON_Delete(results_array);
|
||||
return 0;
|
||||
@@ -2970,6 +2991,219 @@ int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Unified config query handler
|
||||
int handle_config_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
// Suppress unused parameter warning
|
||||
(void)wsi;
|
||||
if (!g_db) {
|
||||
snprintf(error_message, error_size, "database not available");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing unified config query");
|
||||
printf(" Query type: %s\n", query_type);
|
||||
|
||||
const char* sql = NULL;
|
||||
int use_pattern_param = 0;
|
||||
char* pattern_value = NULL;
|
||||
|
||||
// Build appropriate SQL query based on query type
|
||||
if (strcmp(query_type, "all") == 0) {
|
||||
sql = "SELECT key, value, data_type, category, description FROM config ORDER BY category, key";
|
||||
}
|
||||
else if (strcmp(query_type, "category") == 0) {
|
||||
// Get category value from tags
|
||||
pattern_value = (char*)get_tag_value(event, "config_query", 2);
|
||||
if (!pattern_value) {
|
||||
snprintf(error_message, error_size, "invalid: category query requires category value");
|
||||
return -1;
|
||||
}
|
||||
sql = "SELECT key, value, data_type, category, description FROM config WHERE category = ? ORDER BY key";
|
||||
use_pattern_param = 1;
|
||||
}
|
||||
else if (strcmp(query_type, "key") == 0) {
|
||||
// Get key value from tags
|
||||
pattern_value = (char*)get_tag_value(event, "config_query", 2);
|
||||
if (!pattern_value) {
|
||||
snprintf(error_message, error_size, "invalid: key query requires key value");
|
||||
return -1;
|
||||
}
|
||||
sql = "SELECT key, value, data_type, category, description FROM config WHERE key = ? ORDER BY key";
|
||||
use_pattern_param = 1;
|
||||
}
|
||||
else {
|
||||
snprintf(error_message, error_size, "invalid: unknown config query type '%s'", query_type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Execute query
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
snprintf(error_message, error_size, "failed to prepare config query");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (use_pattern_param && pattern_value) {
|
||||
sqlite3_bind_text(stmt, 1, pattern_value, -1, SQLITE_STATIC);
|
||||
}
|
||||
|
||||
// Build results array
|
||||
cJSON* results_array = cJSON_CreateArray();
|
||||
if (!results_array) {
|
||||
sqlite3_finalize(stmt);
|
||||
snprintf(error_message, error_size, "failed to create results array");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int config_count = 0;
|
||||
printf("=== Config Query Results (%s) ===\n", query_type);
|
||||
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* key = (const char*)sqlite3_column_text(stmt, 0);
|
||||
const char* value = (const char*)sqlite3_column_text(stmt, 1);
|
||||
const char* data_type = (const char*)sqlite3_column_text(stmt, 2);
|
||||
const char* category = (const char*)sqlite3_column_text(stmt, 3);
|
||||
const char* description = (const char*)sqlite3_column_text(stmt, 4);
|
||||
|
||||
printf(" %s = %s [%s] (%s)\n",
|
||||
key ? key : "",
|
||||
value ? value : "",
|
||||
data_type ? data_type : "string",
|
||||
category ? category : "general");
|
||||
|
||||
// Add config item to results array
|
||||
cJSON* config_obj = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(config_obj, "key", key ? key : "");
|
||||
cJSON_AddStringToObject(config_obj, "value", value ? value : "");
|
||||
cJSON_AddStringToObject(config_obj, "data_type", data_type ? data_type : "string");
|
||||
cJSON_AddStringToObject(config_obj, "category", category ? category : "general");
|
||||
cJSON_AddStringToObject(config_obj, "description", description ? description : "");
|
||||
cJSON_AddItemToArray(results_array, config_obj);
|
||||
|
||||
config_count++;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
// Build and send response with mapped query type for frontend routing
|
||||
const char* mapped_query_type = map_config_query_type_to_response(query_type);
|
||||
cJSON* response = build_query_response(mapped_query_type, results_array, config_count);
|
||||
if (response) {
|
||||
// Get admin pubkey from event for response
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
|
||||
|
||||
if (!admin_pubkey) {
|
||||
cJSON_Delete(response);
|
||||
cJSON_Delete(results_array);
|
||||
snprintf(error_message, error_size, "missing admin pubkey for response");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Send response as signed kind 23457 event
|
||||
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||
printf("Total results: %d\n", config_count);
|
||||
log_success("Config query completed successfully with signed response");
|
||||
printf(" Response query_type: %s (mapped from %s)\n", mapped_query_type, query_type);
|
||||
cJSON_Delete(response);
|
||||
cJSON_Delete(results_array);
|
||||
return 0;
|
||||
}
|
||||
cJSON_Delete(response);
|
||||
}
|
||||
|
||||
cJSON_Delete(results_array);
|
||||
snprintf(error_message, error_size, "failed to send config query response");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Unified config set handler
|
||||
int handle_config_set_unified(cJSON* event, const char* config_key, const char* config_value, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
// Suppress unused parameter warning
|
||||
(void)wsi;
|
||||
if (!g_db) {
|
||||
snprintf(error_message, error_size, "database not available");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing unified config set command");
|
||||
printf(" Key: %s\n", config_key);
|
||||
printf(" Value: %s\n", config_value);
|
||||
|
||||
// Validate the configuration field before updating
|
||||
char validation_error[512];
|
||||
if (validate_config_field(config_key, config_value, validation_error, sizeof(validation_error)) != 0) {
|
||||
log_error("Config field validation failed");
|
||||
printf(" Validation error: %s\n", validation_error);
|
||||
snprintf(error_message, error_size, "validation failed: %s", validation_error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if the config key exists in the table
|
||||
const char* check_sql = "SELECT COUNT(*) FROM config WHERE key = ?";
|
||||
sqlite3_stmt* check_stmt;
|
||||
|
||||
int check_rc = sqlite3_prepare_v2(g_db, check_sql, -1, &check_stmt, NULL);
|
||||
if (check_rc != SQLITE_OK) {
|
||||
snprintf(error_message, error_size, "failed to prepare config existence check");
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(check_stmt, 1, config_key, -1, SQLITE_STATIC);
|
||||
|
||||
int config_exists = 0;
|
||||
if (sqlite3_step(check_stmt) == SQLITE_ROW) {
|
||||
config_exists = sqlite3_column_int(check_stmt, 0) > 0;
|
||||
}
|
||||
sqlite3_finalize(check_stmt);
|
||||
|
||||
if (!config_exists) {
|
||||
snprintf(error_message, error_size, "error: configuration key '%s' not found", config_key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Update the configuration value
|
||||
if (update_config_in_table(config_key, config_value) != 0) {
|
||||
snprintf(error_message, error_size, "failed to update configuration in database");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Invalidate cache to ensure fresh reads
|
||||
invalidate_config_cache();
|
||||
|
||||
// Build response
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(response, "command", "config_set");
|
||||
cJSON_AddStringToObject(response, "key", config_key);
|
||||
cJSON_AddStringToObject(response, "value", config_value);
|
||||
cJSON_AddStringToObject(response, "status", "success");
|
||||
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
||||
|
||||
printf("Updated config: %s = %s\n", config_key, config_value);
|
||||
|
||||
// Get admin pubkey from event for response
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
|
||||
|
||||
if (!admin_pubkey) {
|
||||
cJSON_Delete(response);
|
||||
snprintf(error_message, error_size, "missing admin pubkey for response");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Send response as signed kind 23457 event
|
||||
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||
log_success("Config set command completed successfully with signed response");
|
||||
cJSON_Delete(response);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON_Delete(response);
|
||||
snprintf(error_message, error_size, "failed to send config set response");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Unified system command handler
|
||||
int handle_system_command_unified(cJSON* event, const char* command, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
// Suppress unused parameter warning
|
||||
@@ -3298,6 +3532,340 @@ int handle_auth_rule_modification_unified(cJSON* event, char* error_message, siz
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// Unified config update handler - handles multiple config objects in single atomic command
|
||||
int handle_config_update_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
// Suppress unused parameter warning
|
||||
(void)wsi;
|
||||
if (!g_db) {
|
||||
snprintf(error_message, error_size, "database not available");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing unified config update command");
|
||||
|
||||
// Extract config objects array from synthetic tags created by NIP-44 decryption
|
||||
// The decryption process creates synthetic tags like: ["config_update", [config_objects]]
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
if (!tags_obj || !cJSON_IsArray(tags_obj)) {
|
||||
snprintf(error_message, error_size, "invalid: config update event must have tags");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Find the config_update tag with config objects array
|
||||
cJSON* config_objects_array = NULL;
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, tags_obj) {
|
||||
if (!cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
||||
if (!tag_name || !cJSON_IsString(tag_name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(cJSON_GetStringValue(tag_name), "config_update") == 0) {
|
||||
// Found config_update tag, get the config objects array
|
||||
cJSON* config_array_item = cJSON_GetArrayItem(tag, 1);
|
||||
if (config_array_item) {
|
||||
// The config objects should be in a JSON string format in the tag
|
||||
if (cJSON_IsString(config_array_item)) {
|
||||
// Parse the JSON string to get the actual array
|
||||
const char* config_json = cJSON_GetStringValue(config_array_item);
|
||||
config_objects_array = cJSON_Parse(config_json);
|
||||
} else if (cJSON_IsArray(config_array_item)) {
|
||||
// Direct array reference
|
||||
config_objects_array = cJSON_Duplicate(config_array_item, 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!config_objects_array || !cJSON_IsArray(config_objects_array)) {
|
||||
snprintf(error_message, error_size, "invalid: config_update command requires config objects array");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int config_count = cJSON_GetArraySize(config_objects_array);
|
||||
log_info("Config update command contains config objects");
|
||||
printf(" Config objects count: %d\n", config_count);
|
||||
|
||||
if (config_count == 0) {
|
||||
cJSON_Delete(config_objects_array);
|
||||
snprintf(error_message, error_size, "invalid: config_update command requires at least one config object");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Begin transaction for atomic config updates
|
||||
int rc = sqlite3_exec(g_db, "BEGIN IMMEDIATE TRANSACTION", NULL, NULL, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
cJSON_Delete(config_objects_array);
|
||||
snprintf(error_message, error_size, "failed to begin config update transaction");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int updates_applied = 0;
|
||||
int validation_errors = 0;
|
||||
char first_validation_error[512] = {0}; // Track first specific validation error
|
||||
char first_error_field[128] = {0}; // Track which field failed first
|
||||
cJSON* processed_configs = cJSON_CreateArray();
|
||||
if (!processed_configs) {
|
||||
sqlite3_exec(g_db, "ROLLBACK", NULL, NULL, NULL);
|
||||
cJSON_Delete(config_objects_array);
|
||||
snprintf(error_message, error_size, "failed to create response array");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Process each config object in the array
|
||||
cJSON* config_obj = NULL;
|
||||
cJSON_ArrayForEach(config_obj, config_objects_array) {
|
||||
if (!cJSON_IsObject(config_obj)) {
|
||||
log_warning("Skipping non-object item in config objects array");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract required fields from config object
|
||||
cJSON* key_obj = cJSON_GetObjectItem(config_obj, "key");
|
||||
cJSON* value_obj = cJSON_GetObjectItem(config_obj, "value");
|
||||
cJSON* data_type_obj = cJSON_GetObjectItem(config_obj, "data_type");
|
||||
cJSON* category_obj = cJSON_GetObjectItem(config_obj, "category");
|
||||
|
||||
if (!key_obj || !cJSON_IsString(key_obj) ||
|
||||
!value_obj || !cJSON_IsString(value_obj)) {
|
||||
log_error("Config object missing required key or value fields");
|
||||
validation_errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* key = cJSON_GetStringValue(key_obj);
|
||||
const char* value = cJSON_GetStringValue(value_obj);
|
||||
const char* data_type = data_type_obj && cJSON_IsString(data_type_obj) ?
|
||||
cJSON_GetStringValue(data_type_obj) : "string";
|
||||
const char* category = category_obj && cJSON_IsString(category_obj) ?
|
||||
cJSON_GetStringValue(category_obj) : "general";
|
||||
|
||||
log_info("Processing config object");
|
||||
printf(" Key: %s\n", key);
|
||||
printf(" Value: %s\n", value);
|
||||
printf(" Data type: %s\n", data_type);
|
||||
printf(" Category: %s\n", category);
|
||||
|
||||
// Validate the configuration field before updating
|
||||
char validation_error[512];
|
||||
if (validate_config_field(key, value, validation_error, sizeof(validation_error)) != 0) {
|
||||
log_error("Config field validation failed");
|
||||
printf(" Validation error: %s\n", validation_error);
|
||||
validation_errors++;
|
||||
|
||||
// Capture first validation error for enhanced error message
|
||||
if (validation_errors == 1) {
|
||||
strncpy(first_validation_error, validation_error, sizeof(first_validation_error) - 1);
|
||||
first_validation_error[sizeof(first_validation_error) - 1] = '\0';
|
||||
strncpy(first_error_field, key, sizeof(first_error_field) - 1);
|
||||
first_error_field[sizeof(first_error_field) - 1] = '\0';
|
||||
}
|
||||
|
||||
// Add failed config to response array
|
||||
cJSON* failed_config = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(failed_config, "key", key);
|
||||
cJSON_AddStringToObject(failed_config, "value", value);
|
||||
cJSON_AddStringToObject(failed_config, "data_type", data_type);
|
||||
cJSON_AddStringToObject(failed_config, "category", category);
|
||||
cJSON_AddStringToObject(failed_config, "status", "validation_failed");
|
||||
cJSON_AddStringToObject(failed_config, "error", validation_error);
|
||||
cJSON_AddItemToArray(processed_configs, failed_config);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the config key exists in the table
|
||||
const char* check_sql = "SELECT COUNT(*) FROM config WHERE key = ?";
|
||||
sqlite3_stmt* check_stmt;
|
||||
|
||||
int check_rc = sqlite3_prepare_v2(g_db, check_sql, -1, &check_stmt, NULL);
|
||||
if (check_rc != SQLITE_OK) {
|
||||
log_error("Failed to prepare config existence check");
|
||||
validation_errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(check_stmt, 1, key, -1, SQLITE_STATIC);
|
||||
|
||||
int config_exists = 0;
|
||||
if (sqlite3_step(check_stmt) == SQLITE_ROW) {
|
||||
config_exists = sqlite3_column_int(check_stmt, 0) > 0;
|
||||
}
|
||||
sqlite3_finalize(check_stmt);
|
||||
|
||||
if (!config_exists) {
|
||||
log_error("Configuration key not found");
|
||||
printf(" Key not found: %s\n", key);
|
||||
validation_errors++;
|
||||
|
||||
// Add failed config to response array
|
||||
cJSON* failed_config = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(failed_config, "key", key);
|
||||
cJSON_AddStringToObject(failed_config, "value", value);
|
||||
cJSON_AddStringToObject(failed_config, "data_type", data_type);
|
||||
cJSON_AddStringToObject(failed_config, "category", category);
|
||||
cJSON_AddStringToObject(failed_config, "status", "key_not_found");
|
||||
cJSON_AddStringToObject(failed_config, "error", "configuration key not found in database");
|
||||
cJSON_AddItemToArray(processed_configs, failed_config);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update the configuration value in the table
|
||||
if (update_config_in_table(key, value) == 0) {
|
||||
updates_applied++;
|
||||
|
||||
// Add successful config to response array
|
||||
cJSON* success_config = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(success_config, "key", key);
|
||||
cJSON_AddStringToObject(success_config, "value", value);
|
||||
cJSON_AddStringToObject(success_config, "data_type", data_type);
|
||||
cJSON_AddStringToObject(success_config, "category", category);
|
||||
cJSON_AddStringToObject(success_config, "status", "updated");
|
||||
cJSON_AddItemToArray(processed_configs, success_config);
|
||||
|
||||
log_success("Config field updated successfully");
|
||||
printf(" Updated: %s = %s\n", key, value);
|
||||
} else {
|
||||
log_error("Failed to update config field in database");
|
||||
printf(" Failed to update: %s = %s\n", key, value);
|
||||
validation_errors++;
|
||||
|
||||
// Add failed config to response array
|
||||
cJSON* failed_config = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(failed_config, "key", key);
|
||||
cJSON_AddStringToObject(failed_config, "value", value);
|
||||
cJSON_AddStringToObject(failed_config, "data_type", data_type);
|
||||
cJSON_AddStringToObject(failed_config, "category", category);
|
||||
cJSON_AddStringToObject(failed_config, "status", "database_error");
|
||||
cJSON_AddStringToObject(failed_config, "error", "failed to update configuration in database");
|
||||
cJSON_AddItemToArray(processed_configs, failed_config);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up config objects array
|
||||
cJSON_Delete(config_objects_array);
|
||||
|
||||
// Determine transaction outcome
|
||||
if (updates_applied > 0 && validation_errors == 0) {
|
||||
// All updates successful
|
||||
sqlite3_exec(g_db, "COMMIT", NULL, NULL, NULL);
|
||||
invalidate_config_cache();
|
||||
|
||||
char success_msg[256];
|
||||
snprintf(success_msg, sizeof(success_msg), "Applied %d configuration updates successfully", updates_applied);
|
||||
log_success(success_msg);
|
||||
} else if (updates_applied > 0 && validation_errors > 0) {
|
||||
// Partial success - rollback for atomic behavior
|
||||
sqlite3_exec(g_db, "ROLLBACK", NULL, NULL, NULL);
|
||||
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg), "Config update failed: %d validation errors (atomic rollback)", validation_errors);
|
||||
log_error(error_msg);
|
||||
|
||||
// Build error response with validation details
|
||||
cJSON* error_response = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(error_response, "query_type", "config_update");
|
||||
cJSON_AddStringToObject(error_response, "status", "error");
|
||||
// Create enhanced error message with specific validation details
|
||||
char enhanced_error_message[1024];
|
||||
if (strlen(first_validation_error) > 0 && strlen(first_error_field) > 0) {
|
||||
snprintf(enhanced_error_message, sizeof(enhanced_error_message),
|
||||
"field validation failed: %s - %s",
|
||||
first_error_field, first_validation_error);
|
||||
} else {
|
||||
snprintf(enhanced_error_message, sizeof(enhanced_error_message),
|
||||
"field validation failed: atomic rollback performed");
|
||||
}
|
||||
cJSON_AddStringToObject(error_response, "error", enhanced_error_message);
|
||||
cJSON_AddNumberToObject(error_response, "validation_errors", validation_errors);
|
||||
cJSON_AddNumberToObject(error_response, "timestamp", (double)time(NULL));
|
||||
cJSON_AddItemToObject(error_response, "data", processed_configs);
|
||||
|
||||
// Get admin pubkey from event for error response
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
|
||||
|
||||
if (admin_pubkey) {
|
||||
// Send error response as signed kind 23457 event
|
||||
if (send_admin_response_event(error_response, admin_pubkey, wsi) == 0) {
|
||||
log_info("Config update validation error response sent successfully");
|
||||
cJSON_Delete(error_response);
|
||||
return 0; // Return success after sending error response
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(error_response);
|
||||
snprintf(error_message, error_size, "validation failed: %d errors, atomic rollback performed", validation_errors);
|
||||
return -1;
|
||||
} else {
|
||||
// No updates applied
|
||||
sqlite3_exec(g_db, "ROLLBACK", NULL, NULL, NULL);
|
||||
|
||||
// Build error response for no valid updates
|
||||
cJSON* error_response = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(error_response, "query_type", "config_update");
|
||||
cJSON_AddStringToObject(error_response, "status", "error");
|
||||
cJSON_AddStringToObject(error_response, "error", "no valid configuration updates found");
|
||||
cJSON_AddNumberToObject(error_response, "timestamp", (double)time(NULL));
|
||||
cJSON_AddItemToObject(error_response, "data", processed_configs);
|
||||
|
||||
// Get admin pubkey from event for error response
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
|
||||
|
||||
if (admin_pubkey) {
|
||||
// Send error response as signed kind 23457 event
|
||||
if (send_admin_response_event(error_response, admin_pubkey, wsi) == 0) {
|
||||
log_info("Config update 'no valid updates' error response sent successfully");
|
||||
cJSON_Delete(error_response);
|
||||
return 0; // Return success after sending error response
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(error_response);
|
||||
snprintf(error_message, error_size, "no valid configuration updates found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Build response with query_type for frontend routing
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(response, "query_type", "config_update");
|
||||
cJSON_AddStringToObject(response, "command", "config_update");
|
||||
cJSON_AddNumberToObject(response, "configs_processed", updates_applied);
|
||||
cJSON_AddNumberToObject(response, "total_configs", config_count);
|
||||
cJSON_AddStringToObject(response, "status", "success");
|
||||
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
||||
cJSON_AddItemToObject(response, "processed_configs", processed_configs);
|
||||
|
||||
printf("Config update completed: %d/%d configs updated successfully\n", updates_applied, config_count);
|
||||
|
||||
// Get admin pubkey from event for response
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
|
||||
|
||||
if (!admin_pubkey) {
|
||||
cJSON_Delete(response);
|
||||
snprintf(error_message, error_size, "missing admin pubkey for response");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Send response as signed kind 23457 event
|
||||
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||
log_success("Config update command completed successfully with signed response");
|
||||
printf(" Response query_type: config_update\n");
|
||||
cJSON_Delete(response);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON_Delete(response);
|
||||
snprintf(error_message, error_size, "failed to send config update response");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3580,7 +4148,7 @@ int process_startup_config_event(const cJSON* event) {
|
||||
|
||||
// Validate event structure first
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
if (!kind_obj || cJSON_GetNumberValue(kind_obj) != 23455) {
|
||||
if (!kind_obj || cJSON_GetNumberValue(kind_obj) != 33334) {
|
||||
log_error("Invalid event kind for startup configuration");
|
||||
return -1;
|
||||
}
|
||||
@@ -3709,7 +4277,7 @@ cJSON* generate_config_event_from_table(void) {
|
||||
cJSON_AddStringToObject(event, "id", "synthetic_config_event_id");
|
||||
cJSON_AddStringToObject(event, "pubkey", relay_pubkey); // Use relay pubkey as event author
|
||||
cJSON_AddNumberToObject(event, "created_at", (double)time(NULL));
|
||||
cJSON_AddNumberToObject(event, "kind", 23455);
|
||||
cJSON_AddNumberToObject(event, "kind", 33334);
|
||||
cJSON_AddStringToObject(event, "content", "C Nostr Relay Configuration");
|
||||
cJSON_AddStringToObject(event, "sig", "synthetic_signature");
|
||||
|
||||
@@ -3790,7 +4358,7 @@ int req_filter_requests_config_events(const cJSON* filter) {
|
||||
cJSON* kind_item = NULL;
|
||||
cJSON_ArrayForEach(kind_item, kinds) {
|
||||
int kind_val = (int)cJSON_GetNumberValue(kind_item);
|
||||
if (cJSON_IsNumber(kind_item) && (kind_val == 23455 || kind_val == 23456)) {
|
||||
if (cJSON_IsNumber(kind_item) && kind_val == 33334) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user