Compare commits

..

3 Commits

Author SHA1 Message Date
Your Name
c1c05991cf v0.3.14 - I think the admin api is finally working 2025-09-27 14:08:45 -04:00
Your Name
ab378e14d1 v0.3.13 - Working on admin system 2025-09-27 13:32:21 -04:00
Your Name
c0f9bf9ef5 v0.3.12 - Working through auth still 2025-09-25 17:33:38 -04:00
6 changed files with 1665 additions and 553 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
285781
652192

View File

@@ -2066,21 +2066,42 @@ extern int is_authorized_admin_event(cJSON* event);
// Process admin events (updated for new Kind 23455/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()");
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
if (!kind_obj || !cJSON_IsNumber(kind_obj)) {
log_error("DEBUG: Missing or invalid kind in admin event");
snprintf(error_message, error_size, "invalid: missing or invalid kind");
return -1;
}
int kind = (int)cJSON_GetNumberValue(kind_obj);
log_info("DEBUG: Processing admin event");
printf(" Event kind: %d\n", kind);
// Extract and log event details for debugging
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
cJSON* content_obj = cJSON_GetObjectItem(event, "content");
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
const char* event_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : "unknown";
const char* event_content = content_obj ? cJSON_GetStringValue(content_obj) : "unknown";
log_info("DEBUG: Event details");
printf(" Pubkey: %.16s...\n", event_pubkey ? event_pubkey : "null");
printf(" Content length: %zu\n", event_content ? strlen(event_content) : 0);
printf(" Has tags: %s\n", tags_obj ? "yes" : "no");
if (tags_obj && cJSON_IsArray(tags_obj)) {
printf(" Tags count: %d\n", cJSON_GetArraySize(tags_obj));
}
// DEFENSE-IN-DEPTH: Use comprehensive admin authorization validation
log_info("DEBUG: Checking admin authorization");
if (!is_authorized_admin_event(event)) {
// Log the unauthorized attempt for security monitoring
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
const char* event_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : "unknown";
char log_msg[256];
snprintf(log_msg, sizeof(log_msg),
"Unauthorized admin event attempt in config processing - pubkey: %.16s...",
"DEBUG: Unauthorized admin event attempt in config processing - pubkey: %.16s...",
event_pubkey ? event_pubkey : "null");
log_warning(log_msg);
@@ -2089,20 +2110,26 @@ int process_admin_event_in_config(cJSON* event, char* error_message, size_t erro
}
// Log successful admin authorization for audit trail
log_info("Admin event authorized successfully in config processing");
int kind = (int)cJSON_GetNumberValue(kind_obj);
log_info("DEBUG: Admin event authorized successfully in config processing");
// 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);
case 33334: // Legacy addressable config events (backward compatibility)
log_info("DEBUG: Routing to process_admin_config_event (legacy kind 33334)");
return process_admin_config_event(event, error_message, error_size);
case 33335: // Legacy addressable auth events (backward compatibility)
log_info("DEBUG: Routing to process_admin_auth_event (legacy kind 33335)");
return process_admin_auth_event(event, error_message, error_size, wsi);
default:
log_error("DEBUG: Unsupported admin event kind");
printf(" Unsupported kind: %d\n", kind);
snprintf(error_message, error_size, "invalid: unsupported admin event kind %d", kind);
return -1;
}
@@ -2213,24 +2240,43 @@ int process_admin_config_event(cJSON* event, char* error_message, size_t error_s
// Handle Kind 23456 auth rules management and legacy Kind 33335
int process_admin_auth_event(cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
log_info("DEBUG: Entering process_admin_auth_event()");
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
int kind = kind_obj ? (int)cJSON_GetNumberValue(kind_obj) : 0;
log_info("Processing admin auth rule event through unified handler");
log_info("DEBUG: Processing admin auth rule event through unified handler");
printf(" Kind: %d\n", kind);
// Extract and log additional event details for debugging
cJSON* content_obj = cJSON_GetObjectItem(event, "content");
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
const char* event_content = content_obj ? cJSON_GetStringValue(content_obj) : "unknown";
log_info("DEBUG: Auth event details");
printf(" Content length: %zu\n", event_content ? strlen(event_content) : 0);
printf(" Has tags: %s\n", tags_obj ? "yes" : "no");
if (tags_obj && cJSON_IsArray(tags_obj)) {
printf(" Tags count: %d\n", cJSON_GetArraySize(tags_obj));
}
// Route all Kind 23456 events through the unified handler
if (kind == 23456) {
log_info("DEBUG: Routing Kind 23456 to unified handler");
return handle_kind_23456_unified(event, error_message, error_size, wsi);
}
// Legacy Kind 33335 events use the unified handler as well
if (kind == 33335) {
log_info("DEBUG: Routing legacy Kind 33335 to unified handler");
// For legacy events, we still use the unified handler but may need special processing
// The unified handler already supports all the functionality
return handle_kind_23456_unified(event, error_message, error_size, wsi);
}
log_error("DEBUG: Unsupported auth event kind in process_admin_auth_event");
printf(" Unsupported kind: %d\n", kind);
snprintf(error_message, error_size, "invalid: unsupported auth event kind %d", kind);
return -1;
}
@@ -2363,63 +2409,223 @@ int parse_auth_query_parameters(cJSON* event, char** query_type, char** pattern_
}
// ================================
// WEBSOCKET RESPONSE SYSTEM
// ADMIN RESPONSE EVENT SYSTEM
// ================================
// Send WebSocket response data back to client
int send_websocket_response_data(cJSON* event, cJSON* response_data, struct lws* wsi) {
if (!event || !response_data || !wsi) return -1;
log_info("Sending WebSocket response data to client");
// Serialize response data to JSON string
char* json_string = cJSON_Print(response_data);
if (!json_string) {
log_error("Failed to serialize response data to JSON");
return -1;
// Create signed kind 23457 admin response event
cJSON* create_admin_response_event(const char* encrypted_content, const char* recipient_pubkey) {
if (!encrypted_content || !recipient_pubkey) {
log_error("Invalid parameters for admin response event creation");
return NULL;
}
printf("WebSocket Response Data: %s\n", json_string);
log_info("Creating signed kind 23457 admin response event");
printf(" Recipient pubkey: %.16s...\n", recipient_pubkey);
printf(" Encrypted content length: %zu\n", strlen(encrypted_content));
// Calculate buffer size needed (LWS_PRE + JSON length)
size_t json_len = strlen(json_string);
size_t buf_size = 512 + json_len; // LWS_PRE is typically ~512 bytes
// Allocate buffer with LWS_PRE space
unsigned char* buf = malloc(buf_size);
if (!buf) {
log_error("Failed to allocate WebSocket transmission buffer");
free(json_string);
return -1;
// Get relay private key for signing
char* relay_privkey = get_relay_private_key();
if (!relay_privkey) {
log_error("Relay private key not available for admin response signing");
return NULL;
}
// Copy JSON data to buffer at LWS_PRE offset
memcpy(buf + 512, json_string, json_len); // Using 512 as LWS_PRE equivalent
// Implement actual WebSocket transmission using LibWebSockets
int write_result = lws_write(wsi, buf + LWS_PRE, json_len, LWS_WRITE_TEXT);
if (write_result < 0) {
log_error("Failed to write WebSocket response data");
free(buf);
free(json_string);
return -1;
} else if ((size_t)write_result != json_len) {
log_warning("Partial WebSocket write - not all data transmitted");
printf(" Expected: %zu bytes, Written: %d bytes\n", json_len, write_result);
} else {
log_success("WebSocket response data transmitted successfully");
printf(" JSON length: %zu bytes\n", json_len);
printf(" Bytes written: %d\n", write_result);
// Convert relay private key from hex to bytes
unsigned char relay_privkey_bytes[32];
if (nostr_hex_to_bytes(relay_privkey, relay_privkey_bytes, 32) != NOSTR_SUCCESS) {
log_error("Failed to convert relay private key from hex for admin response");
free(relay_privkey);
return NULL;
}
// Clean up
free(buf);
free(json_string);
// Clean up private key string immediately
free(relay_privkey);
return 0;
// Create tags array for kind 23457 event
cJSON* tags = cJSON_CreateArray();
if (!tags) {
log_error("Failed to create tags array for admin response event");
memset(relay_privkey_bytes, 0, 32);
return NULL;
}
// Add p tag with recipient pubkey (admin who sent the query)
cJSON* p_tag = cJSON_CreateArray();
cJSON_AddItemToArray(p_tag, cJSON_CreateString("p"));
cJSON_AddItemToArray(p_tag, cJSON_CreateString(recipient_pubkey));
cJSON_AddItemToArray(tags, p_tag);
// Create and sign the kind 23457 event using nostr_core_lib
cJSON* response_event = nostr_create_and_sign_event(
23457, // kind: admin response
encrypted_content, // content: NIP-44 encrypted response
tags, // tags: p tag with recipient
relay_privkey_bytes, // private key bytes for signing
time(NULL) // created_at timestamp
);
// Clean up private key bytes immediately after use
memset(relay_privkey_bytes, 0, 32);
cJSON_Delete(tags); // Clean up tags as they were duplicated in nostr_create_and_sign_event
if (!response_event) {
log_error("Failed to create and sign kind 23457 admin response event");
return NULL;
}
// Log success information
cJSON* id_obj = cJSON_GetObjectItem(response_event, "id");
cJSON* pubkey_obj = cJSON_GetObjectItem(response_event, "pubkey");
if (id_obj && pubkey_obj) {
log_success("Kind 23457 admin response event created and signed successfully");
printf(" Event ID: %s\n", cJSON_GetStringValue(id_obj));
printf(" Relay pubkey: %.16s...\n", cJSON_GetStringValue(pubkey_obj));
}
return response_event;
}
// Encrypt admin response content using NIP-44
char* encrypt_admin_response_content(const cJSON* response_data, const char* recipient_pubkey) {
if (!response_data || !recipient_pubkey) {
log_error("Invalid parameters for admin response encryption");
return NULL;
}
log_info("Encrypting admin response content with NIP-44");
printf(" Recipient pubkey: %.16s...\n", recipient_pubkey);
// Convert response data to JSON string
char* response_json = cJSON_Print(response_data);
if (!response_json) {
log_error("Failed to serialize response data for encryption");
return NULL;
}
log_info("Response data serialized for encryption");
printf(" JSON length: %zu\n", strlen(response_json));
printf(" JSON preview: %.100s%s\n", response_json,
strlen(response_json) > 100 ? "..." : "");
// Get relay private key for encryption
char* relay_privkey = get_relay_private_key();
if (!relay_privkey) {
log_error("Relay private key not available for admin response encryption");
free(response_json);
return NULL;
}
// Convert relay private key from hex to bytes
unsigned char relay_privkey_bytes[32];
if (nostr_hex_to_bytes(relay_privkey, relay_privkey_bytes, 32) != NOSTR_SUCCESS) {
log_error("Failed to convert relay private key from hex for encryption");
free(relay_privkey);
free(response_json);
return NULL;
}
// Clean up private key string immediately
free(relay_privkey);
// Convert recipient public key from hex to bytes
unsigned char recipient_pubkey_bytes[32];
if (nostr_hex_to_bytes(recipient_pubkey, recipient_pubkey_bytes, 32) != NOSTR_SUCCESS) {
log_error("Failed to convert recipient public key from hex for encryption");
memset(relay_privkey_bytes, 0, 32);
free(response_json);
return NULL;
}
// Perform NIP-44 encryption (relay as sender, admin as recipient)
char encrypted_content[8192]; // Buffer for encrypted content
int encrypt_result = nostr_nip44_encrypt(relay_privkey_bytes, recipient_pubkey_bytes,
response_json, encrypted_content, sizeof(encrypted_content));
// Clean up sensitive data immediately after use
memset(relay_privkey_bytes, 0, 32);
free(response_json);
if (encrypt_result != NOSTR_SUCCESS) {
log_error("NIP-44 encryption failed for admin response");
printf(" Encryption result code: %d\n", encrypt_result);
return NULL;
}
log_success("Admin response content encrypted successfully with NIP-44");
printf(" Encrypted content length: %zu\n", strlen(encrypted_content));
printf(" Encrypted preview: %.50s...\n", encrypted_content);
// Return encrypted content as allocated string
return strdup(encrypted_content);
}
// Send admin response event using relay's standard event distribution system
int send_admin_response_event(const cJSON* response_data, const char* recipient_pubkey, struct lws* wsi) {
// Suppress unused parameter warning
(void)wsi;
if (!response_data || !recipient_pubkey) {
log_error("Invalid parameters for admin response event transmission");
return -1;
}
log_info("Sending admin response as signed kind 23457 event through relay distribution system");
printf(" Recipient pubkey: %.16s...\n", recipient_pubkey);
// Step 1: Encrypt response data using NIP-44
char* encrypted_content = encrypt_admin_response_content(response_data, recipient_pubkey);
if (!encrypted_content) {
log_error("Failed to encrypt admin response content");
return -1;
}
// Step 2: Create signed kind 23457 event
cJSON* response_event = create_admin_response_event(encrypted_content, recipient_pubkey);
free(encrypted_content); // Clean up encrypted content after use
if (!response_event) {
log_error("Failed to create admin response event");
return -1;
}
log_info("Admin response event created successfully");
cJSON* id_obj = cJSON_GetObjectItem(response_event, "id");
if (id_obj) {
printf(" Event ID: %s\n", cJSON_GetStringValue(id_obj));
}
// Step 3: Store event in database for persistence
extern int store_event(cJSON* event);
if (store_event(response_event) != 0) {
log_warning("Failed to store admin response event in database (continuing with broadcast)");
} else {
log_info("Admin response event stored in database successfully");
}
// Step 4: Broadcast event to all matching subscriptions using relay's standard system
extern int broadcast_event_to_subscriptions(cJSON* event);
int broadcast_count = broadcast_event_to_subscriptions(response_event);
if (broadcast_count >= 0) {
log_success("Admin response event distributed through relay subscription system");
printf(" Event kind: 23457 (admin response)\n");
printf(" Subscriptions notified: %d\n", broadcast_count);
// Clean up and return success - event creation succeeded regardless of broadcast count
cJSON_Delete(response_event);
return 0;
} else {
log_error("Failed to broadcast admin response event to subscriptions");
cJSON_Delete(response_event);
return -1;
}
}
// ================================
// WEBSOCKET RESPONSE SYSTEM (LEGACY)
// ================================
// 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;
@@ -2441,44 +2647,239 @@ cJSON* build_query_response(const char* query_type, cJSON* results_array, int to
// Single unified handler for all Kind 23456 requests
int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
// Suppress unused parameter warning
(void)wsi;
if (!event) {
log_error("DEBUG: Null event passed to handle_kind_23456_unified");
snprintf(error_message, error_size, "invalid: null event");
return -1;
}
log_info("Processing Kind 23456 event through unified handler");
log_info("DEBUG: Processing Kind 23456 event through unified handler");
// Parse first tag to determine action type
// Check if content is encrypted (NIP-44)
cJSON* content_obj = cJSON_GetObjectItem(event, "content");
if (!content_obj || !cJSON_IsString(content_obj)) {
log_error("DEBUG: Missing or invalid content in Kind 23456 event");
snprintf(error_message, error_size, "invalid: missing or invalid content");
return -1;
}
const char* content = cJSON_GetStringValue(content_obj);
log_info("DEBUG: Event content analysis");
printf(" Content length: %zu\n", content ? strlen(content) : 0);
printf(" Content preview: %.50s%s\n", content ? content : "null",
(content && strlen(content) > 50) ? "..." : "");
cJSON* decrypted_content = NULL;
// Check if content looks like NIP-44 encrypted content (base64 string, not JSON)
if (content && strlen(content) > 10 && content[0] != '[' && content[0] != '{') {
log_info("DEBUG: Detected NIP-44 encrypted content, attempting decryption");
printf(" Content appears to be base64 encrypted\n");
// Get relay private key for decryption
log_info("DEBUG: Retrieving relay private key for decryption");
char* relay_privkey = get_relay_private_key();
if (!relay_privkey) {
log_error("DEBUG: Relay private key not available for decryption");
snprintf(error_message, error_size, "error: relay private key not available for decryption");
return -1;
}
log_info("DEBUG: Relay private key retrieved successfully");
printf(" Relay privkey length: %zu\n", strlen(relay_privkey));
// Get sender's pubkey from the event for NIP-44 decryption
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
if (!pubkey_obj || !cJSON_IsString(pubkey_obj)) {
log_error("DEBUG: Missing sender pubkey in event");
free(relay_privkey);
snprintf(error_message, error_size, "invalid: missing sender pubkey in event");
return -1;
}
const char* sender_pubkey = cJSON_GetStringValue(pubkey_obj);
if (!sender_pubkey || strlen(sender_pubkey) != 64) {
log_error("DEBUG: Invalid sender pubkey format");
printf(" Sender pubkey: %s\n", sender_pubkey ? sender_pubkey : "null");
printf(" Sender pubkey length: %zu\n", sender_pubkey ? strlen(sender_pubkey) : 0);
free(relay_privkey);
snprintf(error_message, error_size, "invalid: invalid sender pubkey format");
return -1;
}
log_info("DEBUG: Sender pubkey validated");
printf(" Sender pubkey: %.16s...\n", sender_pubkey);
// Convert relay private key from hex to bytes
log_info("DEBUG: Converting relay private key from hex to bytes");
unsigned char relay_privkey_bytes[32];
if (nostr_hex_to_bytes(relay_privkey, relay_privkey_bytes, 32) != NOSTR_SUCCESS) {
log_error("DEBUG: Failed to convert relay private key from hex");
free(relay_privkey);
snprintf(error_message, error_size, "error: failed to convert relay private key");
return -1;
}
log_info("DEBUG: Relay private key converted successfully");
// Convert sender public key from hex to bytes
log_info("DEBUG: Converting sender public key from hex to bytes");
unsigned char sender_pubkey_bytes[32];
if (nostr_hex_to_bytes(sender_pubkey, sender_pubkey_bytes, 32) != NOSTR_SUCCESS) {
log_error("DEBUG: Failed to convert sender public key from hex");
free(relay_privkey);
snprintf(error_message, error_size, "error: failed to convert sender public key");
return -1;
}
log_info("DEBUG: Sender public key converted successfully");
// Perform NIP-44 decryption (relay as recipient, admin as sender)
log_info("DEBUG: Performing NIP-44 decryption");
printf(" Encrypted content length: %zu\n", strlen(content));
char decrypted_text[4096]; // Buffer for decrypted content
int decrypt_result = nostr_nip44_decrypt(relay_privkey_bytes, sender_pubkey_bytes, content, decrypted_text, sizeof(decrypted_text));
// Clean up private key immediately after use
memset(relay_privkey_bytes, 0, 32);
free(relay_privkey);
if (decrypt_result != NOSTR_SUCCESS) {
log_error("DEBUG: NIP-44 decryption failed");
printf(" Decryption result code: %d\n", decrypt_result);
snprintf(error_message, error_size, "error: NIP-44 decryption failed");
return -1;
}
log_info("DEBUG: NIP-44 decryption successful");
printf(" Decrypted content: %s\n", decrypted_text);
printf(" Decrypted length: %zu\n", strlen(decrypted_text));
// Parse decrypted content as JSON array
log_info("DEBUG: Parsing decrypted content as JSON");
decrypted_content = cJSON_Parse(decrypted_text);
if (!decrypted_content || !cJSON_IsArray(decrypted_content)) {
log_error("DEBUG: Decrypted content is not valid JSON array");
printf(" Decrypted content type: %s\n",
decrypted_content ? (cJSON_IsArray(decrypted_content) ? "array" : "other") : "null");
snprintf(error_message, error_size, "error: decrypted content is not valid JSON array");
return -1;
}
log_info("DEBUG: Decrypted content parsed successfully as JSON array");
printf(" Array size: %d\n", cJSON_GetArraySize(decrypted_content));
// Replace event content with decrypted command array for processing
log_info("DEBUG: Replacing event content with decrypted marker");
cJSON_DeleteItemFromObject(event, "content");
cJSON_AddStringToObject(event, "content", "decrypted");
// Create synthetic tags from decrypted command array
log_info("DEBUG: Creating synthetic tags from decrypted command array");
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
if (!tags_obj) {
log_info("DEBUG: No existing tags, creating new tags array");
tags_obj = cJSON_CreateArray();
cJSON_AddItemToObject(event, "tags", tags_obj);
} else {
log_info("DEBUG: Using existing tags array");
printf(" Existing tags count: %d\n", cJSON_GetArraySize(tags_obj));
}
// Add decrypted command as first tag
if (cJSON_GetArraySize(decrypted_content) > 0) {
log_info("DEBUG: Adding decrypted command as synthetic tag");
cJSON* first_item = cJSON_GetArrayItem(decrypted_content, 0);
if (cJSON_IsString(first_item)) {
const char* command_name = cJSON_GetStringValue(first_item);
log_info("DEBUG: Creating command tag");
printf(" Command: %s\n", command_name ? command_name : "null");
cJSON* command_tag = cJSON_CreateArray();
cJSON_AddItemToArray(command_tag, cJSON_Duplicate(first_item, 1));
// Add remaining items as tag values
for (int i = 1; i < cJSON_GetArraySize(decrypted_content); i++) {
cJSON* item = cJSON_GetArrayItem(decrypted_content, i);
if (item) {
if (cJSON_IsString(item)) {
printf(" Arg %d: %s\n", i, cJSON_GetStringValue(item));
} else {
printf(" Arg %d: (non-string)\n", i);
}
cJSON_AddItemToArray(command_tag, cJSON_Duplicate(item, 1));
}
}
// Insert at beginning of tags array
cJSON_InsertItemInArray(tags_obj, 0, command_tag);
log_info("DEBUG: Synthetic command tag created and inserted");
printf(" Final tag array size: %d\n", cJSON_GetArraySize(tags_obj));
} else {
log_error("DEBUG: First item in decrypted array is not a string");
}
} else {
log_error("DEBUG: Decrypted array is empty");
}
cJSON_Delete(decrypted_content);
} else {
log_info("DEBUG: Content does not appear to be NIP-44 encrypted");
printf(" Content starts with: %c\n", content ? content[0] : '?');
printf(" Content length: %zu\n", content ? strlen(content) : 0);
}
// Parse first tag to determine action type (now from decrypted content if applicable)
log_info("DEBUG: Parsing first tag to determine action type");
const char* action_type = get_first_tag_name(event);
if (!action_type) {
log_error("DEBUG: Missing or invalid first tag after processing");
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
if (tags_obj && cJSON_IsArray(tags_obj)) {
printf(" Tags array size: %d\n", cJSON_GetArraySize(tags_obj));
} else {
printf(" No tags array found\n");
}
snprintf(error_message, error_size, "invalid: missing or invalid first tag");
return -1;
}
log_info("DEBUG: Action type determined");
printf(" Action type: %s\n", action_type);
// Route to appropriate handler based on action type
log_info("DEBUG: Routing to action-specific handler");
if (strcmp(action_type, "auth_query") == 0) {
const char* query_type = get_tag_value(event, "auth_query", 1);
log_info("DEBUG: Routing to auth_query handler");
const char* query_type = get_tag_value(event, action_type, 1);
if (!query_type) {
log_error("DEBUG: Missing auth_query type parameter");
snprintf(error_message, error_size, "invalid: missing auth_query type");
return -1;
}
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, "system_command") == 0) {
const char* command = get_tag_value(event, "system_command", 1);
log_info("DEBUG: Routing to system_command handler");
const char* command = get_tag_value(event, action_type, 1);
if (!command) {
log_error("DEBUG: Missing system_command type parameter");
snprintf(error_message, error_size, "invalid: missing system_command type");
return -1;
}
printf(" Command: %s\n", command);
return handle_system_command_unified(event, command, error_message, error_size, wsi);
}
else if (strcmp(action_type, "whitelist") == 0 || strcmp(action_type, "blacklist") == 0) {
log_info("DEBUG: Routing to auth rule modification handler");
printf(" Rule type: %s\n", action_type);
// Handle auth rule modifications (existing logic from process_admin_auth_event)
return handle_auth_rule_modification_unified(event, error_message, error_size);
return handle_auth_rule_modification_unified(event, error_message, error_size, wsi);
}
else {
log_error("DEBUG: Unknown Kind 23456 action type");
printf(" Unknown action: %s\n", action_type);
snprintf(error_message, error_size, "invalid: unknown Kind 23456 action type '%s'", action_type);
return -1;
}
@@ -2486,6 +2887,8 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
// Unified auth query handler
int handle_auth_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;
@@ -2574,10 +2977,21 @@ int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_
// Build and send response
cJSON* response = build_query_response(query_type, results_array, rule_count);
if (response) {
// Send response data via WebSocket
if (send_websocket_response_data(event, response, wsi) == 0) {
// 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", rule_count);
log_success("Auth query completed successfully with WebSocket response");
log_success("Auth query completed successfully with signed response");
cJSON_Delete(response);
cJSON_Delete(results_array);
return 0;
@@ -2592,6 +3006,8 @@ int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_
// 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
(void)wsi;
if (!g_db) {
snprintf(error_message, error_size, "database not available");
return -1;
@@ -2635,9 +3051,19 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
printf("Cleared %d auth rules from database\n", rule_count);
// Send response via WebSocket
if (send_websocket_response_data(event, response, wsi) == 0) {
log_success("Clear auth rules command completed successfully");
// 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("Clear auth rules command completed successfully with signed response");
cJSON_Delete(response);
return 0;
}
@@ -2687,9 +3113,19 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
printf("Database: %s\n", g_db ? "Connected" : "Not available");
printf("Cache status: %s\n", g_unified_cache.cache_valid ? "Valid" : "Invalid");
// Send response via WebSocket
if (send_websocket_response_data(event, response, wsi) == 0) {
log_success("System status query completed successfully");
// 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("System status query completed successfully with signed response");
cJSON_Delete(response);
return 0;
}
@@ -2705,7 +3141,9 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
}
// Handle auth rule modifications (extracted from process_admin_auth_event)
int handle_auth_rule_modification_unified(cJSON* event, char* error_message, size_t error_size) {
int handle_auth_rule_modification_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
// Suppress unused parameter warning
(void)wsi;
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
if (!tags_obj || !cJSON_IsArray(tags_obj)) {
snprintf(error_message, error_size, "invalid: auth rule event must have tags");
@@ -2720,8 +3158,15 @@ int handle_auth_rule_modification_unified(cJSON* event, char* error_message, siz
}
int rules_processed = 0;
cJSON* processed_rules = cJSON_CreateArray();
if (!processed_rules) {
sqlite3_exec(g_db, "ROLLBACK", NULL, NULL, NULL);
snprintf(error_message, error_size, "failed to create response array");
return -1;
}
// Process each tag as an auth rule specification
// For Kind 23456 events, only process synthetic tags created from decrypted content
// Skip original unencrypted tags (except p tag validation which is done elsewhere)
cJSON* auth_tag = NULL;
cJSON_ArrayForEach(auth_tag, tags_obj) {
if (!cJSON_IsArray(auth_tag) || cJSON_GetArraySize(auth_tag) < 3) {
@@ -2742,10 +3187,24 @@ int handle_auth_rule_modification_unified(cJSON* event, char* error_message, siz
const char* pattern_type = cJSON_GetStringValue(pattern_type_obj);
const char* pattern_value = cJSON_GetStringValue(pattern_value_obj);
// Skip p tags - they are for routing, not auth rules
if (strcmp(rule_type, "p") == 0) {
continue;
}
// Process auth rule: ["blacklist"|"whitelist", "pubkey"|"hash", "value"]
if (strcmp(rule_type, "blacklist") == 0 || strcmp(rule_type, "whitelist") == 0) {
if (add_auth_rule_from_config(rule_type, pattern_type, pattern_value, "allow") == 0) {
rules_processed++;
// Add processed rule to response array
cJSON* rule_obj = cJSON_CreateObject();
cJSON_AddStringToObject(rule_obj, "rule_type", rule_type);
cJSON_AddStringToObject(rule_obj, "pattern_type", pattern_type);
cJSON_AddStringToObject(rule_obj, "pattern_value", pattern_value);
cJSON_AddStringToObject(rule_obj, "action", "allow");
cJSON_AddStringToObject(rule_obj, "status", "added");
cJSON_AddItemToArray(processed_rules, rule_obj);
}
}
}
@@ -2757,9 +3216,39 @@ int handle_auth_rule_modification_unified(cJSON* event, char* error_message, siz
snprintf(success_msg, sizeof(success_msg), "Processed %d auth rule updates", rules_processed);
log_success(success_msg);
return 0;
// Build and send response
cJSON* response = cJSON_CreateObject();
cJSON_AddStringToObject(response, "command", "auth_rule_modification");
cJSON_AddNumberToObject(response, "rules_processed", rules_processed);
cJSON_AddStringToObject(response, "status", "success");
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
cJSON_AddItemToObject(response, "processed_rules", processed_rules);
printf("Processed %d auth rule modifications\n", rules_processed);
// 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("Auth rule modification completed successfully with signed response");
cJSON_Delete(response);
return 0;
}
cJSON_Delete(response);
snprintf(error_message, error_size, "failed to send auth rule modification response");
return -1;
} else {
sqlite3_exec(g_db, "ROLLBACK", NULL, NULL, NULL);
cJSON_Delete(processed_rules);
snprintf(error_message, error_size, "no valid auth rules found");
return -1;
}

View File

@@ -172,10 +172,10 @@ int process_admin_auth_event(cJSON* event, char* error_message, size_t error_siz
int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi);
int handle_system_command_unified(cJSON* event, const char* command, char* error_message, size_t error_size, struct lws* wsi);
int handle_auth_rule_modification_unified(cJSON* event, char* error_message, size_t error_size);
int handle_auth_rule_modification_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
// WebSocket response functions
int send_websocket_response_data(cJSON* event, cJSON* response_data, struct lws* wsi);
// Admin response functions
int send_admin_response_event(const cJSON* response_data, const char* recipient_pubkey, struct lws* wsi);
cJSON* build_query_response(const char* query_type, cJSON* results_array, int total_count);
// Auth rules management functions

View File

@@ -3203,11 +3203,18 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
memcpy(message, in, len);
message[len] = '\0';
log_info("Received WebSocket message");
// Parse JSON message
// Parse JSON message (this is the normal program flow)
cJSON* json = cJSON_Parse(message);
if (json && cJSON_IsArray(json)) {
// Log the complete parsed JSON message once
char* complete_message = cJSON_Print(json);
if (complete_message) {
char debug_msg[2048];
snprintf(debug_msg, sizeof(debug_msg),
"Received complete WebSocket message: %s", complete_message);
log_info(debug_msg);
free(complete_message);
}
// Get message type
cJSON* type = cJSON_GetArrayItem(json, 0);
if (type && cJSON_IsString(type)) {

View File

@@ -34,12 +34,9 @@ RELAY_URL="ws://${RELAY_HOST}:${RELAY_PORT}"
TIMEOUT=5
TEMP_DIR="/tmp/c_relay_test_$$"
# WebSocket connection state
WS_PID=""
WS_INPUT_FIFO=""
WS_OUTPUT_FIFO=""
# WebSocket connection state (simplified - no persistent connections)
# These variables are kept for compatibility but not used
WS_CONNECTED=0
WS_RESPONSE_LOG=""
# Color codes for output
RED='\033[0;31m'
@@ -120,24 +117,99 @@ generate_test_keypair() {
echo "$pubkey" > "$pubkey_file"
log_info "Generated keypair for $name: pubkey=${pubkey:0:16}..."
log_info "Generated keypair for $name: pubkey=$pubkey"
# Export for use in calling functions
eval "${name}_PRIVKEY=\"$privkey\""
eval "${name}_PUBKEY=\"$pubkey\""
}
# Send WebSocket message and capture response
# NIP-44 encryption helper function using nak
encrypt_nip44_content() {
local content="$1"
local sender_privkey="$2"
local receiver_pubkey="$3"
if [ -z "$content" ] || [ -z "$sender_privkey" ] || [ -z "$receiver_pubkey" ]; then
log_error "encrypt_nip44_content: missing required parameters"
return 1
fi
log_info "DEBUG: About to encrypt content: '$content'" >&2
log_info "DEBUG: Sender privkey: $sender_privkey" >&2
log_info "DEBUG: Receiver pubkey: $receiver_pubkey" >&2
# Use nak to perform NIP-44 encryption with correct syntax:
# nak encrypt --recipient-pubkey <pubkey> --sec <private_key> [plaintext]
local encrypted_content
encrypted_content=$(nak encrypt --recipient-pubkey "$receiver_pubkey" --sec "$sender_privkey" "$content" 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$encrypted_content" ]; then
log_error "Failed to encrypt content with NIP-44"
log_error "Content: $content"
log_error "Sender privkey: $sender_privkey"
log_error "Receiver pubkey: $receiver_pubkey"
return 1
fi
# Validate that encrypted content is valid base64 and doesn't contain problematic characters
if ! echo "$encrypted_content" | grep -q '^[A-Za-z0-9+/]*=*$'; then
log_error "Encrypted content contains invalid characters for JSON: $encrypted_content"
return 1
fi
# Check if encrypted content is valid UTF-8/base64
if ! echo "$encrypted_content" | base64 -d >/dev/null 2>&1; then
log_warning "Encrypted content may not be valid base64: $encrypted_content"
fi
log_info "DEBUG: Encrypted content: $encrypted_content" >&2
log_info "Successfully encrypted content with NIP-44" >&2
echo "$encrypted_content"
return 0
}
# NIP-44 decryption helper function using nak
decrypt_nip44_content() {
local encrypted_content="$1"
local receiver_privkey="$2"
local sender_pubkey="$3"
if [ -z "$encrypted_content" ] || [ -z "$receiver_privkey" ] || [ -z "$sender_pubkey" ]; then
log_error "decrypt_nip44_content: missing required parameters"
return 1
fi
log_info "DEBUG: Decrypting content: $encrypted_content"
# Use nak to perform NIP-44 decryption with correct syntax:
# nak decrypt --sender-pubkey <pubkey> --sec <private_key> [encrypted_content]
local decrypted_content
decrypted_content=$(nak decrypt --sender-pubkey "$sender_pubkey" --sec "$receiver_privkey" "$encrypted_content" 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$decrypted_content" ]; then
log_error "Failed to decrypt content with NIP-44"
log_error "Encrypted content: $encrypted_content"
log_error "Receiver privkey: $receiver_privkey"
log_error "Sender pubkey: $sender_pubkey"
return 1
fi
log_info "DEBUG: Decrypted content: $decrypted_content"
log_info "Successfully decrypted content with NIP-44"
echo "$decrypted_content"
return 0
}
# Send WebSocket message and capture response (simplified pattern from 1_nip_test.sh)
send_websocket_message() {
local message="$1"
local expected_response="$2"
local timeout="${3:-$TIMEOUT}"
local timeout="${2:-$TIMEOUT}"
# Use websocat to send message and capture response (following pattern from tests/1_nip_test.sh)
local response=""
if command -v websocat &> /dev/null; then
# Capture output from websocat (following working pattern from 1_nip_test.sh)
response=$(echo "$message" | timeout "$timeout" websocat "$RELAY_URL" 2>&1 || echo "Connection failed")
response=$(printf '%s\n' "$message" | timeout "$timeout" websocat "$RELAY_URL" 2>&1 || echo "Connection failed")
# Check if connection failed
if [[ "$response" == *"Connection failed"* ]]; then
@@ -154,143 +226,137 @@ send_websocket_message() {
echo "$response"
}
# =======================================================================
# PERSISTENT WEBSOCKET CONNECTION MANAGEMENT
# =======================================================================
# Open persistent WebSocket connection
open_websocket_connection() {
log_info "Opening persistent WebSocket connection to $RELAY_URL..."
# Send admin event and capture response (simplified pattern from 1_nip_test.sh)
send_admin_event() {
local event_json="$1"
local description="$2"
local timeout_seconds="${3:-10}"
# Create unique named pipes for this test session
WS_INPUT_FIFO="${TEMP_DIR}/ws_input_$$"
WS_OUTPUT_FIFO="${TEMP_DIR}/ws_output_$$"
WS_RESPONSE_LOG="${TEMP_DIR}/ws_responses_$$"
log_info "Sending admin event: $description"
# Create named pipes
mkfifo "$WS_INPUT_FIFO" "$WS_OUTPUT_FIFO"
# Create EVENT message using jq to properly handle special characters
local event_message
event_message=$(jq -n --argjson event "$event_json" '["EVENT", $event]')
# Start websocat in background with bidirectional pipes
# Input: we write to WS_INPUT_FIFO, websocat reads and sends to relay
# Output: websocat receives from relay and writes to WS_OUTPUT_FIFO
websocat "$RELAY_URL" < "$WS_INPUT_FIFO" > "$WS_OUTPUT_FIFO" &
WS_PID=$!
# Validate that the event message is valid UTF-8 (temporarily disabled for debugging)
# if ! echo "$event_message" | iconv -f utf-8 -t utf-8 >/dev/null 2>&1; then
# log_error "Event message contains invalid UTF-8 characters"
# return 1
# fi
# Start background response logger
tail -f "$WS_OUTPUT_FIFO" >> "$WS_RESPONSE_LOG" &
local logger_pid=$!
# Keep input pipe open by redirecting from /dev/null in background
exec {ws_fd}> "$WS_INPUT_FIFO"
# Test connection with a simple REQ message
sleep 1
echo '["REQ","test_conn",{}]' >&${ws_fd}
# Wait for response to confirm connection
local connection_timeout=5
local start_time=$(date +%s)
while [ $(($(date +%s) - start_time)) -lt $connection_timeout ]; do
if [ -s "$WS_RESPONSE_LOG" ]; then
WS_CONNECTED=1
log_success "Persistent WebSocket connection established"
log_info "WebSocket PID: $WS_PID"
return 0
fi
sleep 0.1
done
# Connection failed
log_error "Failed to establish persistent WebSocket connection"
close_websocket_connection
return 1
}
# Close persistent WebSocket connection
close_websocket_connection() {
log_info "Closing persistent WebSocket connection..."
if [ -n "$WS_PID" ] && kill -0 "$WS_PID" 2>/dev/null; then
# Close input pipe first
if [ -n "${ws_fd}" ]; then
exec {ws_fd}>&-
# Use websocat to send event and capture OK response
local response=""
if command -v websocat &> /dev/null; then
log_info "Sending event using websocat..."
# Debug: Show what we're sending
log_info "DEBUG: Event message being sent: $event_message"
# Write to temporary file to avoid shell interpretation issues
local temp_file="${TEMP_DIR}/event_message_$$"
printf '%s\n' "$event_message" > "$temp_file"
# Send via websocat using file input with delay to receive response
response=$(timeout "$timeout_seconds" sh -c "cat '$temp_file'; sleep 0.5" | websocat "$RELAY_URL" 2>&1)
local websocat_exit_code=$?
# Clean up temp file
rm -f "$temp_file"
log_info "DEBUG: Websocat exit code: $websocat_exit_code"
log_info "DEBUG: Websocat response: $response"
# Check for specific websocat errors
if [[ "$response" == *"UTF-8 failure"* ]]; then
log_error "UTF-8 encoding error in event data for $description"
log_error "Event message: $event_message"
return 1
elif [[ "$response" == *"Connection failed"* ]] || [[ "$response" == *"Connection refused"* ]] || [[ "$response" == *"timeout"* ]]; then
log_error "Failed to connect to relay for $description"
return 1
elif [[ "$response" == *"error running"* ]]; then
log_error "Websocat error for $description: $response"
return 1
elif [ $websocat_exit_code -eq 0 ]; then
log_info "Event sent successfully via websocat"
else
log_warning "Websocat returned exit code $websocat_exit_code"
fi
# Send close frame and terminate websocat
kill "$WS_PID" 2>/dev/null
wait "$WS_PID" 2>/dev/null
fi
# Kill any remaining background processes
pkill -f "tail -f.*$WS_OUTPUT_FIFO" 2>/dev/null || true
# Clean up pipes
[ -p "$WS_INPUT_FIFO" ] && rm -f "$WS_INPUT_FIFO"
[ -p "$WS_OUTPUT_FIFO" ] && rm -f "$WS_OUTPUT_FIFO"
WS_PID=""
WS_CONNECTED=0
log_info "WebSocket connection closed"
}
# Send event through persistent WebSocket connection
send_websocket_event() {
local event_json="$1"
local timeout_seconds="${2:-10}"
if [ "$WS_CONNECTED" != "1" ]; then
log_error "WebSocket connection not established"
else
log_error "websocat not found - required for WebSocket testing"
return 1
fi
# Clear previous responses
> "$WS_RESPONSE_LOG"
# Create EVENT message
local event_message="[\"EVENT\",$event_json]"
# Send through persistent connection
echo "$event_message" >&${ws_fd}
# Wait for OK response
local start_time=$(date +%s)
while [ $(($(date +%s) - start_time)) -lt $timeout_seconds ]; do
if grep -q '"OK"' "$WS_RESPONSE_LOG" 2>/dev/null; then
local response=$(tail -1 "$WS_RESPONSE_LOG")
echo "$response"
return 0
fi
sleep 0.1
done
log_error "Timeout waiting for WebSocket response"
return 1
echo "$response"
}
# Wait for query response data from relay
wait_for_query_response() {
local timeout_seconds="${1:-10}"
local start_time=$(date +%s)
# Send admin query and wait for encrypted response
send_admin_query() {
local event_json="$1"
local description="$2"
local timeout_seconds="${3:-15}"
log_info "Waiting for query response data..."
log_info "Sending admin query: $description"
# Clear any OK responses and wait for JSON data
sleep 0.5 # Brief delay to ensure OK response is processed first
# Create EVENT message using jq to properly handle special characters
local event_message
event_message=$(jq -n --argjson event "$event_json" '["EVENT", $event]')
while [ $(($(date +%s) - start_time)) -lt $timeout_seconds ]; do
# Look for JSON response with query data (not just OK responses)
if grep -q '"query_type"' "$WS_RESPONSE_LOG" 2>/dev/null; then
local response=$(grep '"query_type"' "$WS_RESPONSE_LOG" | tail -1)
echo "$response"
return 0
# For queries, we need to also send a REQ to get the response
local sub_id="admin_query_$(date +%s)"
local req_message="[\"REQ\",\"$sub_id\",{\"kinds\":[23456],\"authors\":[\"$RELAY_PUBKEY\"],\"#p\":[\"$ADMIN_PUBKEY\"]}]"
local close_message="[\"CLOSE\",\"$sub_id\"]"
# Send query event and subscription in sequence
local response=""
if command -v websocat &> /dev/null; then
response=$(printf '%s\n%s\n%s\n' "$event_message" "$req_message" "$close_message" | timeout "$timeout_seconds" websocat "$RELAY_URL" 2>&1 || echo "Connection failed")
# Check if connection failed
if [[ "$response" == *"Connection failed"* ]]; then
log_error "Failed to connect to relay for $description"
return 1
fi
sleep 0.1
done
# Look for EVENT responses that might contain encrypted query data
local event_response=$(echo "$response" | grep '"EVENT"' | tail -1)
if [ -n "$event_response" ]; then
# Extract the event JSON from the EVENT message
local event_json=$(echo "$event_response" | jq -r '.[2]' 2>/dev/null)
if [ -n "$event_json" ] && [ "$event_json" != "null" ]; then
# Check if this is a kind 23456 response event
local event_kind=$(echo "$event_json" | jq -r '.kind' 2>/dev/null)
if [ "$event_kind" = "23456" ]; then
# Extract encrypted content and decrypt it
local encrypted_content=$(echo "$event_json" | jq -r '.content' 2>/dev/null)
if [ -n "$encrypted_content" ] && [ "$encrypted_content" != "null" ]; then
# Decrypt the response using NIP-44
local decrypted_content
decrypted_content=$(decrypt_nip44_content "$encrypted_content" "$ADMIN_PRIVKEY" "$RELAY_PUBKEY")
if [ $? -eq 0 ] && [ -n "$decrypted_content" ]; then
log_info "Successfully decrypted query response"
echo "$decrypted_content"
return 0
else
log_warning "Failed to decrypt query response content"
fi
fi
fi
fi
fi
else
log_error "websocat not found - required for WebSocket testing"
return 1
fi
log_error "Timeout waiting for query response data"
return 1
# Return the raw response if no encrypted content found
echo "$response"
}
# Create and send auth rule event
@@ -301,15 +367,26 @@ send_auth_rule_event() {
local pattern_value="$4" # actual pubkey or hash value
local description="$5" # optional description
log_info "Creating auth rule event: $action $rule_type $pattern_type ${pattern_value:0:16}..."
log_info "Creating auth rule event: $action $rule_type $pattern_type $pattern_value"
# Create the auth rule event using nak with correct tag format for the actual implementation
# Server expects tags like ["whitelist", "pubkey", "abc123..."] or ["blacklist", "pubkey", "def456..."]
# Using Kind 23456 (ephemeral auth rules management) with proper relay targeting
# Create command array according to README.md API specification
# Format: ["blacklist", "pubkey", "abc123..."] or ["whitelist", "pubkey", "def456..."]
local command_array="[\"$rule_type\", \"$pattern_type\", \"$pattern_value\"]"
# Encrypt the command content using NIP-44
local encrypted_content
encrypted_content=$(encrypt_nip44_content "$command_array" "$ADMIN_PRIVKEY" "$RELAY_PUBKEY")
if [ $? -ne 0 ] || [ -z "$encrypted_content" ]; then
log_error "Failed to encrypt auth rule command content"
return 1
fi
# Create the auth rule event using nak with NIP-44 encrypted content
# Using Kind 23456 (admin commands) with proper relay targeting and encrypted content
local event_json
event_json=$(nak event -k 23456 --content "" \
event_json=$(nak event -k 23456 --content "$encrypted_content" \
-t "p=$RELAY_PUBKEY" \
-t "$rule_type=$pattern_type=$pattern_value" \
--sec "$ADMIN_PRIVKEY" 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$event_json" ]; then
@@ -317,38 +394,23 @@ send_auth_rule_event() {
return 1
fi
# Send the event through persistent WebSocket connection
log_info "DEBUG: Created event JSON: $event_json"
# Send the event using simplified WebSocket pattern
log_info "Publishing auth rule event to relay..."
local result
if [ "$WS_CONNECTED" = "1" ]; then
result=$(send_websocket_event "$event_json")
local exit_code=$?
log_info "Auth rule event result: $result"
# Check if response indicates success
if [ $exit_code -eq 0 ] && echo "$result" | grep -q -i '"OK".*true'; then
log_success "Auth rule $action successful"
return 0
else
log_error "Auth rule $action failed: $result (exit code: $exit_code)"
return 1
fi
result=$(send_admin_event "$event_json" "auth rule $action")
local exit_code=$?
log_info "Auth rule event result: $result"
# Check if response indicates success
if [ $exit_code -eq 0 ] && echo "$result" | grep -q -i '"OK".*true'; then
log_success "Auth rule $action successful"
return 0
else
# Fallback to one-shot connection if persistent connection not available
result=$(echo "$event_json" | timeout 10s nak event "$RELAY_URL" 2>&1)
local exit_code=$?
log_info "Auth rule event result: $result"
# Check if response indicates success
if [ $exit_code -eq 0 ] && echo "$result" | grep -q -i "success\|OK.*true\|published"; then
log_success "Auth rule $action successful"
return 0
else
log_error "Auth rule $action failed: $result (exit code: $exit_code)"
return 1
fi
log_error "Auth rule $action failed: $result (exit code: $exit_code)"
return 1
fi
}
@@ -356,12 +418,27 @@ send_auth_rule_event() {
clear_all_auth_rules() {
log_info "Clearing all existing auth rules..."
# Create command array according to README.md API specification
# Format: ["system_command", "clear_all_auth_rules"]
local command_array="[\"system_command\", \"clear_all_auth_rules\"]"
log_info "DEBUG: Command array: $command_array"
# Encrypt the command content using NIP-44
local encrypted_content
encrypted_content=$(encrypt_nip44_content "$command_array" "$ADMIN_PRIVKEY" "$RELAY_PUBKEY")
if [ $? -ne 0 ] || [ -z "$encrypted_content" ]; then
log_error "Failed to encrypt system command content"
return 1
fi
log_info "DEBUG: Encrypted content: $encrypted_content"
# Create system command event to clear all auth rules
# Using Kind 23456 (ephemeral auth rules management) with proper relay targeting
# Using Kind 23456 (admin commands) with proper relay targeting and encrypted content
local event_json
event_json=$(nak event -k 23456 --content "" \
event_json=$(nak event -k 23456 --content "$encrypted_content" \
-t "p=$RELAY_PUBKEY" \
-t "system_command=clear_all_auth_rules" \
--sec "$ADMIN_PRIVKEY" 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$event_json" ]; then
@@ -369,38 +446,23 @@ clear_all_auth_rules() {
return 1
fi
# Send the event through persistent WebSocket connection
log_info "DEBUG: Created event JSON: $event_json"
# Send the event using simplified WebSocket pattern
log_info "Sending clear all auth rules command..."
local result
if [ "$WS_CONNECTED" = "1" ]; then
result=$(send_websocket_event "$event_json")
local exit_code=$?
log_info "Clear auth rules result: $result"
# Check if response indicates success
if [ $exit_code -eq 0 ] && echo "$result" | grep -q -i '"OK".*true'; then
log_success "All auth rules cleared successfully"
return 0
else
log_error "Failed to clear auth rules: $result (exit code: $exit_code)"
return 1
fi
result=$(send_admin_event "$event_json" "clear all auth rules")
local exit_code=$?
log_info "Clear auth rules result: $result"
# Check if response indicates success
if [ $exit_code -eq 0 ] && echo "$result" | grep -q -i '"OK".*true'; then
log_success "All auth rules cleared successfully"
return 0
else
# Fallback to one-shot connection if persistent connection not available
result=$(echo "$event_json" | timeout 10s nak event "$RELAY_URL" 2>&1)
local exit_code=$?
log_info "Clear auth rules result: $result"
# Check if response indicates success
if [ $exit_code -eq 0 ] && echo "$result" | grep -q -i "success\|OK.*true\|published"; then
log_success "All auth rules cleared successfully"
return 0
else
log_error "Failed to clear auth rules: $result (exit code: $exit_code)"
return 1
fi
log_error "Failed to clear auth rules: $result (exit code: $exit_code)"
return 1
fi
}
@@ -414,7 +476,7 @@ test_event_publishing() {
log_info "Testing event publishing: $description"
# Create a simple test event (kind 1 - text note) using nak like NIP-42 test
local test_content="Test message from ${test_pubkey:0:16}... at $(date)"
local test_content="Test message from $test_pubkey at $(date)"
local test_event
test_event=$(nak event -k 1 --content "$test_content" --sec "$test_privkey" 2>/dev/null)
@@ -426,7 +488,7 @@ test_event_publishing() {
# Send the event using nak directly (more reliable than websocat)
log_info "Publishing test event to relay..."
local result
result=$(echo "$test_event" | timeout 10s nak event "$RELAY_URL" 2>&1)
result=$(printf '%s\n' "$test_event" | timeout 10s nak event "$RELAY_URL" 2>&1)
local exit_code=$?
log_info "Event publishing result: $result"
@@ -506,11 +568,22 @@ test_admin_authentication() {
log "Test $TESTS_RUN: Admin Authentication"
# Create a simple configuration event to test admin authentication
# Using Kind 23456 (admin commands) with proper relay targeting
# Using Kind 23456 (admin commands) with NIP-44 encrypted content
# Format: ["system_command", "system_status"]
local command_array="[\"system_command\", \"system_status\"]"
# Encrypt the command content using NIP-44
local encrypted_content
encrypted_content=$(encrypt_nip44_content "$command_array" "$ADMIN_PRIVKEY" "$RELAY_PUBKEY")
if [ $? -ne 0 ] || [ -z "$encrypted_content" ]; then
fail_test "Failed to encrypt admin authentication test content"
return
fi
local config_event
config_event=$(nak event -k 23456 --content "" \
config_event=$(nak event -k 23456 --content "$encrypted_content" \
-t "p=$RELAY_PUBKEY" \
-t "system_command=system_status" \
--sec "$ADMIN_PRIVKEY" 2>/dev/null)
if [ $? -ne 0 ]; then
@@ -518,15 +591,17 @@ test_admin_authentication() {
return
fi
# Send admin event
local message="[\"EVENT\",$config_event]"
# Send admin event using the proper admin event function
local response
response=$(send_websocket_message "$message" "OK" 10)
response=$(send_admin_event "$config_event" "admin authentication test")
local exit_code=$?
if echo "$response" | grep -q '"OK".*true'; then
log_info "Admin authentication result: $response"
if [ $exit_code -eq 0 ] && echo "$response" | grep -q '"OK".*true'; then
pass_test "Admin authentication successful"
else
fail_test "Admin authentication failed: $response"
fail_test "Admin authentication failed: $response (exit code: $exit_code)"
fi
}
@@ -548,10 +623,22 @@ test_auth_rules_storage_query() {
# Query all auth rules using admin query
log_info "Querying all auth rules..."
# Create command array according to README.md API specification
# Format: ["auth_query", "all"]
local command_array="[\"auth_query\", \"all\"]"
# Encrypt the command content using NIP-44
local encrypted_content
encrypted_content=$(encrypt_nip44_content "$command_array" "$ADMIN_PRIVKEY" "$RELAY_PUBKEY")
if [ $? -ne 0 ] || [ -z "$encrypted_content" ]; then
fail_test "Failed to encrypt auth query content"
return
fi
local query_event
query_event=$(nak event -k 23456 --content "" \
query_event=$(nak event -k 23456 --content "$encrypted_content" \
-t "p=$RELAY_PUBKEY" \
-t "auth_query=all" \
--sec "$ADMIN_PRIVKEY" 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$query_event" ]; then
@@ -559,23 +646,23 @@ test_auth_rules_storage_query() {
return
fi
# Send the query event
# Send the query event using simplified WebSocket pattern
log_info "Sending auth query to relay..."
local query_result
query_result=$(echo "$query_event" | timeout 10s nak event "$RELAY_URL" 2>&1)
local decrypted_response
decrypted_response=$(send_admin_query "$query_event" "auth rules query")
local exit_code=$?
log_info "Auth query result: $query_result"
# Check if we got a response and if it contains our test rule
if [ $exit_code -eq 0 ]; then
if echo "$query_result" | grep -q "$TEST1_PUBKEY"; then
pass_test "Auth rule storage and query working - found test rule in query results"
if [ $exit_code -eq 0 ] && [ -n "$decrypted_response" ]; then
log_info "Decrypted query response: $decrypted_response"
# Check if the decrypted response contains our test rule
if echo "$decrypted_response" | grep -q "$TEST1_PUBKEY"; then
pass_test "Auth rule storage and query working - found test rule in decrypted query results"
else
fail_test "Auth rule not found in query results - rule may not have been stored"
fail_test "Auth rule not found in decrypted query results - rule may not have been stored"
fi
else
fail_test "Auth query failed: $query_result"
fail_test "Failed to receive or decrypt auth query response"
fi
else
fail_test "Failed to add auth rule for storage test"
@@ -747,14 +834,14 @@ test_hash_blacklist() {
return
fi
log_info "Testing hash blacklist with event ID: ${event_id:0:16}..."
log_info "Testing hash blacklist with event ID: $event_id"
# Add the event ID to hash blacklist
if send_auth_rule_event "add" "blacklist" "hash" "$event_id" "Test hash blacklist"; then
# Try to publish the same event using nak - should be blocked
log_info "Attempting to publish blacklisted event..."
local result
result=$(echo "$test_event" | timeout 10s nak event "$RELAY_URL" 2>&1)
result=$(printf '%s\n' "$test_event" | timeout 10s nak event "$RELAY_URL" 2>&1)
local exit_code=$?
if [ $exit_code -ne 0 ] || echo "$result" | grep -q -i "blocked\|denied\|rejected\|blacklist"; then
@@ -786,7 +873,7 @@ test_websocket_behavior() {
if [ $? -eq 0 ]; then
local message="[\"EVENT\",$test_event]"
local response
response=$(send_websocket_message "$message" "OK" 5)
response=$(send_websocket_message "$message" 5)
if echo "$response" | grep -q '"OK"'; then
rapid_success_count=$((rapid_success_count + 1))
@@ -884,7 +971,7 @@ run_all_tests() {
clear_all_auth_rules
test_admin_authentication
test_auth_rules_storage_query
# test_auth_rules_storage_query
# test_basic_whitelist
# test_basic_blacklist
# test_rule_removal
@@ -941,7 +1028,7 @@ main() {
echo ""
# Check if relay is running - using websocat like the working tests
if ! echo '["REQ","connection_test",{}]' | timeout 5 websocat "$RELAY_URL" >/dev/null 2>&1; then
if ! printf '%s\n' '["REQ","connection_test",{}]' | timeout 5 websocat "$RELAY_URL" >/dev/null 2>&1; then
log_error "Cannot connect to relay at $RELAY_URL"
log_error "Please ensure the C-Relay server is running in test mode"
exit 1