v0.4.9 - Working on dm admin

This commit is contained in:
Your Name
2025-10-04 19:04:12 -04:00
parent 64b418a551
commit c63fd04c92
14 changed files with 2331 additions and 56 deletions

View File

@@ -68,6 +68,13 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length);
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
int is_authorized_admin_event(cJSON* event, char* error_message, size_t error_size);
// Forward declarations for NIP-17 admin messaging
int process_nip17_admin_message(cJSON* gift_wrap_event, char* error_message, size_t error_size, struct lws* wsi);
// Forward declarations for DM stats command handling
int process_dm_stats_command(cJSON* dm_event, char* error_message, size_t error_size, struct lws* wsi);
// Forward declarations for NIP-09 deletion request handling
int handle_deletion_request(cJSON* event, char* error_message, size_t error_size);
@@ -314,13 +321,38 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Check if NIP-42 authentication is required for this event kind or globally
int auth_required = is_nip42_auth_globally_required() || is_nip42_auth_required_for_kind(event_kind);
// Special case: allow kind 14 DMs addressed to relay to bypass auth (admin commands)
int bypass_auth = 0;
if (event_kind == 14 && event_obj && cJSON_IsObject(event_obj)) {
cJSON* tags = cJSON_GetObjectItem(event_obj, "tags");
if (tags && cJSON_IsArray(tags)) {
const char* relay_pubkey = get_relay_pubkey_cached();
if (relay_pubkey) {
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
if (tag_name && cJSON_IsString(tag_name) &&
strcmp(cJSON_GetStringValue(tag_name), "p") == 0 &&
tag_value && cJSON_IsString(tag_value) &&
strcmp(cJSON_GetStringValue(tag_value), relay_pubkey) == 0) {
bypass_auth = 1;
break;
}
}
}
}
}
}
char debug_auth_msg[256];
snprintf(debug_auth_msg, sizeof(debug_auth_msg),
"DEBUG AUTH: auth_required=%d, pss->authenticated=%d, event_kind=%d",
auth_required, pss ? pss->authenticated : -1, event_kind);
"DEBUG AUTH: auth_required=%d, bypass_auth=%d, pss->authenticated=%d, event_kind=%d",
auth_required, bypass_auth, pss ? pss->authenticated : -1, event_kind);
log_info(debug_auth_msg);
if (pss && auth_required && !pss->authenticated) {
if (pss && auth_required && !pss->authenticated && !bypass_auth) {
if (!pss->auth_challenge_sent) {
log_info("DEBUG AUTH: Sending NIP-42 authentication challenge");
send_nip42_auth_challenge(wsi, pss);
@@ -606,6 +638,78 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Admin events are processed by the admin API, not broadcast to subscriptions
}
}
} else if (event_kind == 1059) {
// Check for NIP-17 gift wrap admin messages
log_info("DEBUG NIP17: Detected kind 1059 gift wrap event");
char nip17_error[512] = {0};
int nip17_result = process_nip17_admin_message(event, nip17_error, sizeof(nip17_error), wsi);
if (nip17_result != 0) {
log_error("DEBUG NIP17: NIP-17 admin message processing failed");
result = -1;
size_t error_len = strlen(nip17_error);
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
memcpy(error_message, nip17_error, copy_len);
error_message[copy_len] = '\0';
char debug_nip17_error_msg[600];
snprintf(debug_nip17_error_msg, sizeof(debug_nip17_error_msg),
"DEBUG NIP17 ERROR: %.400s", nip17_error);
log_error(debug_nip17_error_msg);
} else {
log_success("DEBUG NIP17: NIP-17 admin message processed successfully");
// Store the gift wrap event in database (unlike kind 23456)
if (store_event(event) != 0) {
log_error("DEBUG NIP17: Failed to store gift wrap event in database");
result = -1;
strncpy(error_message, "error: failed to store gift wrap event", sizeof(error_message) - 1);
} else {
log_info("DEBUG NIP17: Gift wrap event stored successfully in database");
// Broadcast gift wrap event to matching persistent subscriptions
int broadcast_count = broadcast_event_to_subscriptions(event);
char debug_broadcast_msg[128];
snprintf(debug_broadcast_msg, sizeof(debug_broadcast_msg),
"DEBUG NIP17 BROADCAST: Gift wrap event broadcast to %d subscriptions", broadcast_count);
log_info(debug_broadcast_msg);
}
}
} else if (event_kind == 14) {
// Check for DM stats commands addressed to relay
log_info("DEBUG DM: Detected kind 14 DM event");
char dm_error[512] = {0};
int dm_result = process_dm_stats_command(event, dm_error, sizeof(dm_error), wsi);
if (dm_result != 0) {
log_error("DEBUG DM: DM stats command processing failed");
result = -1;
size_t error_len = strlen(dm_error);
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
memcpy(error_message, dm_error, copy_len);
error_message[copy_len] = '\0';
char debug_dm_error_msg[600];
snprintf(debug_dm_error_msg, sizeof(debug_dm_error_msg),
"DEBUG DM ERROR: %.400s", dm_error);
log_error(debug_dm_error_msg);
} else {
log_success("DEBUG DM: DM stats command processed successfully");
// Store the DM event in database
if (store_event(event) != 0) {
log_error("DEBUG DM: Failed to store DM event in database");
result = -1;
strncpy(error_message, "error: failed to store DM event", sizeof(error_message) - 1);
} else {
log_info("DEBUG DM: DM event stored successfully in database");
// Broadcast DM event to matching persistent subscriptions
int broadcast_count = broadcast_event_to_subscriptions(event);
char debug_broadcast_msg[128];
snprintf(debug_broadcast_msg, sizeof(debug_broadcast_msg),
"DEBUG DM BROADCAST: DM event broadcast to %d subscriptions", broadcast_count);
log_info(debug_broadcast_msg);
}
}
} else {
// Regular event - store in database and broadcast
log_info("DEBUG STORAGE: Regular event - storing in database");
@@ -1041,6 +1145,180 @@ int start_websocket_relay(int port_override, int strict_port) {
return 0;
}
// Process DM stats command
int process_dm_stats_command(cJSON* dm_event, char* error_message, size_t error_size, struct lws* wsi) {
// Suppress unused parameter warning
(void)wsi;
if (!dm_event || !error_message) {
return -1;
}
// Check if DM is addressed to relay
cJSON* tags = cJSON_GetObjectItem(dm_event, "tags");
if (!tags || !cJSON_IsArray(tags)) {
strncpy(error_message, "DM missing or invalid tags", error_size - 1);
return -1;
}
const char* relay_pubkey = get_relay_pubkey_cached();
if (!relay_pubkey) {
strncpy(error_message, "Could not get relay pubkey", error_size - 1);
return -1;
}
// Look for "p" tag with relay pubkey
int addressed_to_relay = 0;
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
if (tag_name && cJSON_IsString(tag_name) &&
strcmp(cJSON_GetStringValue(tag_name), "p") == 0 &&
tag_value && cJSON_IsString(tag_value) &&
strcmp(cJSON_GetStringValue(tag_value), relay_pubkey) == 0) {
addressed_to_relay = 1;
break;
}
}
}
if (!addressed_to_relay) {
// Not addressed to relay, allow normal processing
return 0;
}
// Get sender pubkey
cJSON* pubkey_obj = cJSON_GetObjectItem(dm_event, "pubkey");
if (!pubkey_obj || !cJSON_IsString(pubkey_obj)) {
strncpy(error_message, "DM missing sender pubkey", error_size - 1);
return -1;
}
const char* sender_pubkey = cJSON_GetStringValue(pubkey_obj);
// Check if sender is admin
const char* admin_pubkey = get_admin_pubkey_cached();
if (!admin_pubkey || strlen(admin_pubkey) == 0 ||
strcmp(sender_pubkey, admin_pubkey) != 0) {
strncpy(error_message, "Unauthorized: not admin", error_size - 1);
return -1;
}
// Get relay private key for decryption
char* relay_privkey_hex = get_relay_private_key();
if (!relay_privkey_hex) {
strncpy(error_message, "Could not get relay private key", error_size - 1);
return -1;
}
// Convert relay private key to bytes
unsigned char relay_privkey[32];
if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
free(relay_privkey_hex);
strncpy(error_message, "Failed to convert relay private key", error_size - 1);
return -1;
}
free(relay_privkey_hex);
// Convert sender pubkey to bytes
unsigned char sender_pubkey_bytes[32];
if (nostr_hex_to_bytes(sender_pubkey, sender_pubkey_bytes, sizeof(sender_pubkey_bytes)) != 0) {
strncpy(error_message, "Failed to convert sender pubkey", error_size - 1);
return -1;
}
// Get encrypted content
cJSON* content_obj = cJSON_GetObjectItem(dm_event, "content");
if (!content_obj || !cJSON_IsString(content_obj)) {
strncpy(error_message, "DM missing content", error_size - 1);
return -1;
}
const char* encrypted_content = cJSON_GetStringValue(content_obj);
// Decrypt content
char decrypted_content[4096];
int decrypt_result = nostr_nip44_decrypt(relay_privkey, sender_pubkey_bytes,
encrypted_content, decrypted_content, sizeof(decrypted_content));
if (decrypt_result != NOSTR_SUCCESS) {
char decrypt_error[256];
snprintf(decrypt_error, sizeof(decrypt_error), "NIP-44 decryption failed: %d", decrypt_result);
strncpy(error_message, decrypt_error, error_size - 1);
return -1;
}
// Check if content is "stats"
if (strcmp(decrypted_content, "stats") != 0) {
// Not a stats command, allow normal processing
return 0;
}
log_info("Processing DM stats command from admin");
// Generate stats JSON
char* stats_json = generate_stats_json();
if (!stats_json) {
strncpy(error_message, "Failed to generate stats", error_size - 1);
return -1;
}
// Encrypt stats for response
char encrypted_response[4096];
int encrypt_result = nostr_nip44_encrypt(relay_privkey, sender_pubkey_bytes,
stats_json, encrypted_response, sizeof(encrypted_response));
free(stats_json);
if (encrypt_result != NOSTR_SUCCESS) {
char encrypt_error[256];
snprintf(encrypt_error, sizeof(encrypt_error), "NIP-44 encryption failed: %d", encrypt_result);
strncpy(error_message, encrypt_error, error_size - 1);
return -1;
}
// Create DM response event
cJSON* dm_response = cJSON_CreateObject();
cJSON_AddStringToObject(dm_response, "id", ""); // Will be set by event creation
cJSON_AddStringToObject(dm_response, "pubkey", relay_pubkey);
cJSON_AddNumberToObject(dm_response, "created_at", (double)time(NULL));
cJSON_AddNumberToObject(dm_response, "kind", 14);
cJSON_AddStringToObject(dm_response, "content", encrypted_response);
// Add tags: p tag for recipient (admin)
cJSON* response_tags = cJSON_CreateArray();
cJSON* p_tag = cJSON_CreateArray();
cJSON_AddItemToArray(p_tag, cJSON_CreateString("p"));
cJSON_AddItemToArray(p_tag, cJSON_CreateString(sender_pubkey));
cJSON_AddItemToArray(response_tags, p_tag);
cJSON_AddItemToObject(dm_response, "tags", response_tags);
// Add signature placeholder
cJSON_AddStringToObject(dm_response, "sig", ""); // Will be set by event creation/signing
// Store and broadcast the DM response
int store_result = store_event(dm_response);
if (store_result != 0) {
cJSON_Delete(dm_response);
strncpy(error_message, "Failed to store DM response", error_size - 1);
return -1;
}
// Broadcast to subscriptions
int broadcast_count = broadcast_event_to_subscriptions(dm_response);
char broadcast_msg[128];
snprintf(broadcast_msg, sizeof(broadcast_msg),
"DM stats response broadcast to %d subscriptions", broadcast_count);
log_info(broadcast_msg);
cJSON_Delete(dm_response);
log_success("DM stats command processed successfully");
return 0;
}
// Handle NIP-45 COUNT message
int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, struct per_session_data *pss) {
(void)pss; // Suppress unused parameter warning