v0.0.4 - nip09 implemented
This commit is contained in:
560
src/main.c
560
src/main.c
@@ -133,6 +133,24 @@ void log_subscription_disconnected(const char* client_ip);
|
||||
void log_event_broadcast(const char* event_id, const char* sub_id, const char* client_ip);
|
||||
void update_subscription_events_sent(const char* sub_id, int events_sent);
|
||||
|
||||
// Forward declarations for NIP-01 event handling
|
||||
const char* extract_d_tag_value(cJSON* tags);
|
||||
int check_and_handle_replaceable_event(int kind, const char* pubkey, long created_at);
|
||||
int check_and_handle_addressable_event(int kind, const char* pubkey, const char* d_tag_value, long created_at);
|
||||
int handle_event_message(cJSON* event, char* error_message, size_t error_size);
|
||||
|
||||
// Forward declaration for NOTICE message support
|
||||
void send_notice_message(struct lws* wsi, const char* message);
|
||||
|
||||
// Forward declarations for NIP-09 deletion request handling
|
||||
int handle_deletion_request(cJSON* event, char* error_message, size_t error_size);
|
||||
int delete_events_by_id(const char* requester_pubkey, cJSON* event_ids);
|
||||
int delete_events_by_address(const char* requester_pubkey, cJSON* addresses, long deletion_timestamp);
|
||||
int mark_event_as_deleted(const char* event_id, const char* deletion_event_id, const char* reason);
|
||||
|
||||
// Forward declaration for database functions
|
||||
int store_event(cJSON* event);
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -842,6 +860,322 @@ void signal_handler(int sig) {
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// NOTICE MESSAGE SUPPORT
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Send NOTICE message to client (NIP-01)
|
||||
void send_notice_message(struct lws* wsi, const char* message) {
|
||||
if (!wsi || !message) return;
|
||||
|
||||
cJSON* notice_msg = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(notice_msg, cJSON_CreateString("NOTICE"));
|
||||
cJSON_AddItemToArray(notice_msg, cJSON_CreateString(message));
|
||||
|
||||
char* msg_str = cJSON_Print(notice_msg);
|
||||
if (msg_str) {
|
||||
size_t msg_len = strlen(msg_str);
|
||||
unsigned char* buf = malloc(LWS_PRE + msg_len);
|
||||
if (buf) {
|
||||
memcpy(buf + LWS_PRE, msg_str, msg_len);
|
||||
lws_write(wsi, buf + LWS_PRE, msg_len, LWS_WRITE_TEXT);
|
||||
free(buf);
|
||||
}
|
||||
free(msg_str);
|
||||
}
|
||||
|
||||
cJSON_Delete(notice_msg);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// NIP-09 EVENT DELETION REQUEST HANDLING
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Handle NIP-09 deletion request event (kind 5)
|
||||
int handle_deletion_request(cJSON* event, char* error_message, size_t error_size) {
|
||||
if (!event) {
|
||||
snprintf(error_message, error_size, "invalid: null deletion request");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Extract event details
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
cJSON* created_at_obj = cJSON_GetObjectItem(event, "created_at");
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
cJSON* content_obj = cJSON_GetObjectItem(event, "content");
|
||||
cJSON* event_id_obj = cJSON_GetObjectItem(event, "id");
|
||||
|
||||
if (!kind_obj || !pubkey_obj || !created_at_obj || !tags_obj || !event_id_obj) {
|
||||
snprintf(error_message, error_size, "invalid: incomplete deletion request");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int kind = (int)cJSON_GetNumberValue(kind_obj);
|
||||
if (kind != 5) {
|
||||
snprintf(error_message, error_size, "invalid: not a deletion request");
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* requester_pubkey = cJSON_GetStringValue(pubkey_obj);
|
||||
const char* deletion_event_id = cJSON_GetStringValue(event_id_obj);
|
||||
const char* reason = content_obj ? cJSON_GetStringValue(content_obj) : "";
|
||||
long deletion_timestamp = (long)cJSON_GetNumberValue(created_at_obj);
|
||||
|
||||
if (!cJSON_IsArray(tags_obj)) {
|
||||
snprintf(error_message, error_size, "invalid: deletion request tags must be an array");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Collect event IDs and addresses from tags
|
||||
cJSON* event_ids = cJSON_CreateArray();
|
||||
cJSON* addresses = cJSON_CreateArray();
|
||||
cJSON* kinds_to_delete = cJSON_CreateArray();
|
||||
|
||||
int deletion_targets_found = 0;
|
||||
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, tags_obj) {
|
||||
if (!cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
||||
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
|
||||
|
||||
if (!cJSON_IsString(tag_name) || !cJSON_IsString(tag_value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* name = cJSON_GetStringValue(tag_name);
|
||||
const char* value = cJSON_GetStringValue(tag_value);
|
||||
|
||||
if (strcmp(name, "e") == 0) {
|
||||
// Event ID reference
|
||||
cJSON_AddItemToArray(event_ids, cJSON_CreateString(value));
|
||||
deletion_targets_found++;
|
||||
} else if (strcmp(name, "a") == 0) {
|
||||
// Addressable event reference (kind:pubkey:d-identifier)
|
||||
cJSON_AddItemToArray(addresses, cJSON_CreateString(value));
|
||||
deletion_targets_found++;
|
||||
} else if (strcmp(name, "k") == 0) {
|
||||
// Kind hint - store for validation but not required
|
||||
int kind_hint = atoi(value);
|
||||
if (kind_hint > 0) {
|
||||
cJSON_AddItemToArray(kinds_to_delete, cJSON_CreateNumber(kind_hint));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deletion_targets_found == 0) {
|
||||
cJSON_Delete(event_ids);
|
||||
cJSON_Delete(addresses);
|
||||
cJSON_Delete(kinds_to_delete);
|
||||
snprintf(error_message, error_size, "invalid: deletion request must contain 'e' or 'a' tags");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int deleted_count = 0;
|
||||
|
||||
// Process event ID deletions
|
||||
if (cJSON_GetArraySize(event_ids) > 0) {
|
||||
int result = delete_events_by_id(requester_pubkey, event_ids);
|
||||
if (result > 0) {
|
||||
deleted_count += result;
|
||||
}
|
||||
}
|
||||
|
||||
// Process addressable event deletions
|
||||
if (cJSON_GetArraySize(addresses) > 0) {
|
||||
int result = delete_events_by_address(requester_pubkey, addresses, deletion_timestamp);
|
||||
if (result > 0) {
|
||||
deleted_count += result;
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
cJSON_Delete(event_ids);
|
||||
cJSON_Delete(addresses);
|
||||
cJSON_Delete(kinds_to_delete);
|
||||
|
||||
// Store the deletion request itself (it should be kept according to NIP-09)
|
||||
if (store_event(event) != 0) {
|
||||
log_warning("Failed to store deletion request event");
|
||||
}
|
||||
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Deletion request processed: %d events deleted", deleted_count);
|
||||
log_info(debug_msg);
|
||||
|
||||
snprintf(error_message, error_size, ""); // Success
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Delete events by ID (with pubkey authorization)
|
||||
int delete_events_by_id(const char* requester_pubkey, cJSON* event_ids) {
|
||||
if (!g_db || !requester_pubkey || !event_ids || !cJSON_IsArray(event_ids)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int deleted_count = 0;
|
||||
|
||||
cJSON* event_id = NULL;
|
||||
cJSON_ArrayForEach(event_id, event_ids) {
|
||||
if (!cJSON_IsString(event_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* id = cJSON_GetStringValue(event_id);
|
||||
|
||||
// First check if event exists and if requester is authorized
|
||||
const char* check_sql = "SELECT pubkey FROM events WHERE id = ?";
|
||||
sqlite3_stmt* check_stmt;
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_db, check_sql, -1, &check_stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(check_stmt, 1, id, -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(check_stmt) == SQLITE_ROW) {
|
||||
const char* event_pubkey = (char*)sqlite3_column_text(check_stmt, 0);
|
||||
|
||||
// Only delete if the requester is the author
|
||||
if (event_pubkey && strcmp(event_pubkey, requester_pubkey) == 0) {
|
||||
sqlite3_finalize(check_stmt);
|
||||
|
||||
// Delete the event
|
||||
const char* delete_sql = "DELETE FROM events WHERE id = ? AND pubkey = ?";
|
||||
sqlite3_stmt* delete_stmt;
|
||||
|
||||
rc = sqlite3_prepare_v2(g_db, delete_sql, -1, &delete_stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(delete_stmt, 1, id, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(delete_stmt, 2, requester_pubkey, -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(delete_stmt) == SQLITE_DONE && sqlite3_changes(g_db) > 0) {
|
||||
deleted_count++;
|
||||
|
||||
char debug_msg[128];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Deleted event by ID: %.16s...", id);
|
||||
log_info(debug_msg);
|
||||
}
|
||||
sqlite3_finalize(delete_stmt);
|
||||
}
|
||||
} else {
|
||||
sqlite3_finalize(check_stmt);
|
||||
char warning_msg[128];
|
||||
snprintf(warning_msg, sizeof(warning_msg), "Unauthorized deletion attempt for event: %.16s...", id);
|
||||
log_warning(warning_msg);
|
||||
}
|
||||
} else {
|
||||
sqlite3_finalize(check_stmt);
|
||||
char debug_msg[128];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Event not found for deletion: %.16s...", id);
|
||||
log_info(debug_msg);
|
||||
}
|
||||
}
|
||||
|
||||
return deleted_count;
|
||||
}
|
||||
|
||||
// Delete events by addressable reference (kind:pubkey:d-identifier)
|
||||
int delete_events_by_address(const char* requester_pubkey, cJSON* addresses, long deletion_timestamp) {
|
||||
if (!g_db || !requester_pubkey || !addresses || !cJSON_IsArray(addresses)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int deleted_count = 0;
|
||||
|
||||
cJSON* address = NULL;
|
||||
cJSON_ArrayForEach(address, addresses) {
|
||||
if (!cJSON_IsString(address)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* addr = cJSON_GetStringValue(address);
|
||||
|
||||
// Parse address format: kind:pubkey:d-identifier
|
||||
char* addr_copy = strdup(addr);
|
||||
if (!addr_copy) continue;
|
||||
|
||||
char* kind_str = strtok(addr_copy, ":");
|
||||
char* pubkey_str = strtok(NULL, ":");
|
||||
char* d_identifier = strtok(NULL, ":");
|
||||
|
||||
if (!kind_str || !pubkey_str) {
|
||||
free(addr_copy);
|
||||
continue;
|
||||
}
|
||||
|
||||
int kind = atoi(kind_str);
|
||||
|
||||
// Only delete if the requester is the author
|
||||
if (strcmp(pubkey_str, requester_pubkey) != 0) {
|
||||
free(addr_copy);
|
||||
char warning_msg[128];
|
||||
snprintf(warning_msg, sizeof(warning_msg), "Unauthorized deletion attempt for address: %.32s...", addr);
|
||||
log_warning(warning_msg);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Build deletion query based on whether we have d-identifier
|
||||
const char* delete_sql;
|
||||
sqlite3_stmt* delete_stmt;
|
||||
|
||||
if (d_identifier && strlen(d_identifier) > 0) {
|
||||
// Delete specific addressable event with d-tag
|
||||
delete_sql = "DELETE FROM events WHERE kind = ? AND pubkey = ? AND created_at <= ? "
|
||||
"AND json_extract(tags, '$[*]') LIKE '%[\"d\",\"' || ? || '\"]%'";
|
||||
} else {
|
||||
// Delete all events of this kind by this author up to deletion timestamp
|
||||
delete_sql = "DELETE FROM events WHERE kind = ? AND pubkey = ? AND created_at <= ?";
|
||||
}
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_db, delete_sql, -1, &delete_stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_int(delete_stmt, 1, kind);
|
||||
sqlite3_bind_text(delete_stmt, 2, requester_pubkey, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_int64(delete_stmt, 3, deletion_timestamp);
|
||||
|
||||
if (d_identifier && strlen(d_identifier) > 0) {
|
||||
sqlite3_bind_text(delete_stmt, 4, d_identifier, -1, SQLITE_STATIC);
|
||||
}
|
||||
|
||||
if (sqlite3_step(delete_stmt) == SQLITE_DONE) {
|
||||
int changes = sqlite3_changes(g_db);
|
||||
if (changes > 0) {
|
||||
deleted_count += changes;
|
||||
|
||||
char debug_msg[128];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Deleted %d events by address: %.32s...", changes, addr);
|
||||
log_info(debug_msg);
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(delete_stmt);
|
||||
}
|
||||
|
||||
free(addr_copy);
|
||||
}
|
||||
|
||||
return deleted_count;
|
||||
}
|
||||
|
||||
// Mark event as deleted (alternative to hard deletion - not used in current implementation)
|
||||
int mark_event_as_deleted(const char* event_id, const char* deletion_event_id, const char* reason) {
|
||||
(void)event_id; (void)deletion_event_id; (void)reason; // Suppress unused warnings
|
||||
|
||||
// This function could be used if we wanted to implement soft deletion
|
||||
// For now, NIP-09 implementation uses hard deletion as specified
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DATABASE FUNCTIONS
|
||||
@@ -907,6 +1241,118 @@ const char* event_type_to_string(event_type_t type) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to extract d tag value from tags array
|
||||
const char* extract_d_tag_value(cJSON* tags) {
|
||||
if (!tags || !cJSON_IsArray(tags)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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 (cJSON_IsString(tag_name) && cJSON_IsString(tag_value)) {
|
||||
const char* name = cJSON_GetStringValue(tag_name);
|
||||
if (name && strcmp(name, "d") == 0) {
|
||||
return cJSON_GetStringValue(tag_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Check and handle replaceable events according to NIP-01
|
||||
int check_and_handle_replaceable_event(int kind, const char* pubkey, long created_at) {
|
||||
if (!g_db || !pubkey) return 0;
|
||||
|
||||
const char* sql =
|
||||
"SELECT created_at FROM events WHERE kind = ? AND pubkey = ? ORDER BY created_at DESC LIMIT 1";
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
return 0; // Allow storage on DB error
|
||||
}
|
||||
|
||||
sqlite3_bind_int(stmt, 1, kind);
|
||||
sqlite3_bind_text(stmt, 2, pubkey, -1, SQLITE_STATIC);
|
||||
|
||||
int result = 0;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
long existing_created_at = sqlite3_column_int64(stmt, 0);
|
||||
if (created_at <= existing_created_at) {
|
||||
result = -1; // Older or same timestamp, reject
|
||||
} else {
|
||||
// Delete older versions
|
||||
const char* delete_sql = "DELETE FROM events WHERE kind = ? AND pubkey = ? AND created_at < ?";
|
||||
sqlite3_stmt* delete_stmt;
|
||||
if (sqlite3_prepare_v2(g_db, delete_sql, -1, &delete_stmt, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_int(delete_stmt, 1, kind);
|
||||
sqlite3_bind_text(delete_stmt, 2, pubkey, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_int64(delete_stmt, 3, created_at);
|
||||
sqlite3_step(delete_stmt);
|
||||
sqlite3_finalize(delete_stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check and handle addressable events according to NIP-01
|
||||
int check_and_handle_addressable_event(int kind, const char* pubkey, const char* d_tag_value, long created_at) {
|
||||
if (!g_db || !pubkey) return 0;
|
||||
|
||||
// If no d tag, treat as regular replaceable
|
||||
if (!d_tag_value) {
|
||||
return check_and_handle_replaceable_event(kind, pubkey, created_at);
|
||||
}
|
||||
|
||||
const char* sql =
|
||||
"SELECT created_at FROM events WHERE kind = ? AND pubkey = ? AND json_extract(tags, '$[*][1]') = ? "
|
||||
"AND json_extract(tags, '$[*][0]') = 'd' ORDER BY created_at DESC LIMIT 1";
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
return 0; // Allow storage on DB error
|
||||
}
|
||||
|
||||
sqlite3_bind_int(stmt, 1, kind);
|
||||
sqlite3_bind_text(stmt, 2, pubkey, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 3, d_tag_value, -1, SQLITE_STATIC);
|
||||
|
||||
int result = 0;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
long existing_created_at = sqlite3_column_int64(stmt, 0);
|
||||
if (created_at <= existing_created_at) {
|
||||
result = -1; // Older or same timestamp, reject
|
||||
} else {
|
||||
// Delete older versions with same kind, pubkey, and d tag
|
||||
const char* delete_sql =
|
||||
"DELETE FROM events WHERE kind = ? AND pubkey = ? AND created_at < ? "
|
||||
"AND json_extract(tags, '$[*][1]') = ? AND json_extract(tags, '$[*][0]') = 'd'";
|
||||
sqlite3_stmt* delete_stmt;
|
||||
if (sqlite3_prepare_v2(g_db, delete_sql, -1, &delete_stmt, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_int(delete_stmt, 1, kind);
|
||||
sqlite3_bind_text(delete_stmt, 2, pubkey, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_int64(delete_stmt, 3, created_at);
|
||||
sqlite3_bind_text(delete_stmt, 4, d_tag_value, -1, SQLITE_STATIC);
|
||||
sqlite3_step(delete_stmt);
|
||||
sqlite3_finalize(delete_stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Store event in database
|
||||
int store_event(cJSON* event) {
|
||||
if (!g_db || !event) {
|
||||
@@ -1303,22 +1749,113 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
||||
}
|
||||
|
||||
// Handle EVENT message (publish)
|
||||
int handle_event_message(cJSON* event) {
|
||||
log_info("Handling EVENT message");
|
||||
int handle_event_message(cJSON* event, char* error_message, size_t error_size) {
|
||||
log_info("Handling EVENT message with full NIP-01 validation");
|
||||
|
||||
// Validate event structure (basic check)
|
||||
cJSON* id = cJSON_GetObjectItem(event, "id");
|
||||
if (!id || !cJSON_IsString(id)) {
|
||||
log_error("Invalid event - no ID");
|
||||
return -1;
|
||||
if (!event) {
|
||||
snprintf(error_message, error_size, "invalid: null event");
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
// Store event in database
|
||||
// Step 1: Validate event structure
|
||||
int structure_result = nostr_validate_event_structure(event);
|
||||
if (structure_result != NOSTR_SUCCESS) {
|
||||
switch (structure_result) {
|
||||
case NOSTR_ERROR_EVENT_INVALID_STRUCTURE:
|
||||
snprintf(error_message, error_size, "invalid: malformed event structure");
|
||||
break;
|
||||
case NOSTR_ERROR_EVENT_INVALID_ID:
|
||||
snprintf(error_message, error_size, "invalid: invalid event id format");
|
||||
break;
|
||||
case NOSTR_ERROR_EVENT_INVALID_PUBKEY:
|
||||
snprintf(error_message, error_size, "invalid: invalid pubkey format");
|
||||
break;
|
||||
case NOSTR_ERROR_EVENT_INVALID_CREATED_AT:
|
||||
snprintf(error_message, error_size, "invalid: invalid created_at timestamp");
|
||||
break;
|
||||
case NOSTR_ERROR_EVENT_INVALID_KIND:
|
||||
snprintf(error_message, error_size, "invalid: invalid event kind");
|
||||
break;
|
||||
case NOSTR_ERROR_EVENT_INVALID_TAGS:
|
||||
snprintf(error_message, error_size, "invalid: invalid tags format");
|
||||
break;
|
||||
case NOSTR_ERROR_EVENT_INVALID_CONTENT:
|
||||
snprintf(error_message, error_size, "invalid: invalid content");
|
||||
break;
|
||||
default:
|
||||
snprintf(error_message, error_size, "invalid: event structure validation failed");
|
||||
}
|
||||
return structure_result;
|
||||
}
|
||||
|
||||
// Step 2: Verify event signature
|
||||
int signature_result = nostr_verify_event_signature(event);
|
||||
if (signature_result != NOSTR_SUCCESS) {
|
||||
if (signature_result == NOSTR_ERROR_EVENT_INVALID_SIGNATURE) {
|
||||
snprintf(error_message, error_size, "invalid: event signature verification failed");
|
||||
} else if (signature_result == NOSTR_ERROR_EVENT_INVALID_ID) {
|
||||
snprintf(error_message, error_size, "invalid: event id does not match computed hash");
|
||||
} else {
|
||||
snprintf(error_message, error_size, "invalid: cryptographic validation failed");
|
||||
}
|
||||
return signature_result;
|
||||
}
|
||||
|
||||
// Step 3: Complete event validation (combines structure + signature + additional checks)
|
||||
int validation_result = nostr_validate_event(event);
|
||||
if (validation_result != NOSTR_SUCCESS) {
|
||||
snprintf(error_message, error_size, "invalid: complete event validation failed");
|
||||
return validation_result;
|
||||
}
|
||||
|
||||
// Step 4: Check for special event types and handle accordingly
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
cJSON* created_at_obj = cJSON_GetObjectItem(event, "created_at");
|
||||
|
||||
if (kind_obj && pubkey_obj && created_at_obj) {
|
||||
int kind = (int)cJSON_GetNumberValue(kind_obj);
|
||||
const char* pubkey = cJSON_GetStringValue(pubkey_obj);
|
||||
long created_at = (long)cJSON_GetNumberValue(created_at_obj);
|
||||
|
||||
// NIP-09: Handle deletion requests (kind 5)
|
||||
if (kind == 5) {
|
||||
return handle_deletion_request(event, error_message, error_size);
|
||||
}
|
||||
|
||||
// Handle replaceable events (NIP-01)
|
||||
event_type_t event_type = classify_event_kind(kind);
|
||||
if (event_type == EVENT_TYPE_REPLACEABLE) {
|
||||
// For replaceable events, check if we have a newer version
|
||||
if (check_and_handle_replaceable_event(kind, pubkey, created_at) < 0) {
|
||||
snprintf(error_message, error_size, "duplicate: older replaceable event ignored");
|
||||
return -2; // Special code for duplicate/older event
|
||||
}
|
||||
} else if (event_type == EVENT_TYPE_ADDRESSABLE) {
|
||||
// For addressable events, check d tag
|
||||
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||
if (tags && cJSON_IsArray(tags)) {
|
||||
const char* d_tag_value = extract_d_tag_value(tags);
|
||||
if (check_and_handle_addressable_event(kind, pubkey, d_tag_value, created_at) < 0) {
|
||||
snprintf(error_message, error_size, "duplicate: older addressable event ignored");
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
} else if (event_type == EVENT_TYPE_EPHEMERAL) {
|
||||
// Ephemeral events should not be stored
|
||||
snprintf(error_message, error_size, ""); // Success but no storage
|
||||
return 0; // Accept but don't store
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Store event in database
|
||||
if (store_event(event) == 0) {
|
||||
log_success("Event stored successfully");
|
||||
snprintf(error_message, error_size, ""); // Success
|
||||
log_success("Event validated and stored successfully");
|
||||
return 0;
|
||||
}
|
||||
|
||||
snprintf(error_message, error_size, "error: failed to store event in database");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -1366,7 +1903,8 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Handle EVENT message
|
||||
cJSON* event = cJSON_GetArrayItem(json, 1);
|
||||
if (event && cJSON_IsObject(event)) {
|
||||
int result = handle_event_message(event);
|
||||
char error_message[512] = {0};
|
||||
int result = handle_event_message(event, error_message, sizeof(error_message));
|
||||
|
||||
// Broadcast event to matching persistent subscriptions
|
||||
if (result == 0) {
|
||||
@@ -1380,7 +1918,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
cJSON_AddItemToArray(response, cJSON_CreateString("OK"));
|
||||
cJSON_AddItemToArray(response, cJSON_CreateString(cJSON_GetStringValue(event_id)));
|
||||
cJSON_AddItemToArray(response, cJSON_CreateBool(result == 0));
|
||||
cJSON_AddItemToArray(response, cJSON_CreateString(result == 0 ? "" : "error: failed to store event"));
|
||||
cJSON_AddItemToArray(response, cJSON_CreateString(strlen(error_message) > 0 ? error_message : ""));
|
||||
|
||||
char *response_str = cJSON_Print(response);
|
||||
if (response_str) {
|
||||
|
||||
Reference in New Issue
Block a user