Files
c-relay/IMPLEMENT_API.md
2025-09-25 16:35:16 -04:00

20 KiB

Implementation Plan: Enhanced Admin Event API Structure

Current Issue

The current admin event routing at main.c:3248-3268 has a security vulnerability:

if (event_kind == 23455 || event_kind == 23456) {
    // Admin event processing
    int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
} else {
    // Regular event storage and broadcasting
}

Problem: Any event with these kinds gets routed to admin processing, regardless of authorization. This allows unauthorized users to send admin events that could be processed as legitimate admin commands.

Note: Event kinds 33334 and 33335 are no longer used and have been removed from the admin event routing.

Required Security Enhancement

Admin events must be validated for proper authorization BEFORE routing to admin processing:

  1. Relay Public Key Check: Event must have a p tag equal to the relay's public key
  2. Admin Signature Check: Event must be signed by an authorized admin private key
  3. Fallback to Regular Processing: If authorization fails, treat as regular event (not admin event)

Implementation Plan

Phase 1: Add Admin Authorization Validation

1.1 Create Consolidated Admin Authorization Function

Location: src/main.c or src/config.c

/**
 * Consolidated admin event authorization validator
 * Implements defense-in-depth security for admin events
 *
 * @param event - The event to validate for admin authorization
 * @param error_message - Buffer for detailed error messages
 * @param error_size - Size of error message buffer
 * @return 0 if authorized, -1 if unauthorized, -2 if validation error
 */
int is_authorized_admin_event(cJSON* event, char* error_message, size_t error_size) {
    if (!event) {
        snprintf(error_message, error_size, "admin_auth: null event");
        return -2;
    }
    
    // Extract event components
    cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
    cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
    cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
    
    if (!kind_obj || !pubkey_obj || !tags_obj) {
        snprintf(error_message, error_size, "admin_auth: missing required fields");
        return -2;
    }
    
    // Validation Layer 1: Kind Check
    int event_kind = (int)cJSON_GetNumberValue(kind_obj);
    if (event_kind != 23455 && event_kind != 23456) {
        snprintf(error_message, error_size, "admin_auth: not an admin event kind");
        return -1;
    }
    
    // Validation Layer 2: Relay Targeting Check
    const char* relay_pubkey = get_config_value("relay_pubkey");
    if (!relay_pubkey) {
        snprintf(error_message, error_size, "admin_auth: relay pubkey not configured");
        return -2;
    }
    
    // Check for 'p' tag targeting this relay
    int has_relay_target = 0;
    if (cJSON_IsArray(tags_obj)) {
        cJSON* tag = NULL;
        cJSON_ArrayForEach(tag, tags_obj) {
            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);
                    const char* value = cJSON_GetStringValue(tag_value);
                    
                    if (strcmp(name, "p") == 0 && strcmp(value, relay_pubkey) == 0) {
                        has_relay_target = 1;
                        break;
                    }
                }
            }
        }
    }
    
    if (!has_relay_target) {
        // Admin event for different relay - not unauthorized, just not for us
        snprintf(error_message, error_size, "admin_auth: admin event for different relay");
        return -1;
    }
    
    // Validation Layer 3: Admin Signature Check (only if targeting this relay)
    const char* event_pubkey = cJSON_GetStringValue(pubkey_obj);
    if (!event_pubkey) {
        snprintf(error_message, error_size, "admin_auth: invalid pubkey format");
        return -2;
    }
    
    const char* admin_pubkey = get_config_value("admin_pubkey");
    if (!admin_pubkey || strcmp(event_pubkey, admin_pubkey) != 0) {
        // This is the ONLY case where we log as "Unauthorized admin event attempt"
        // because it's targeting THIS relay but from wrong admin
        snprintf(error_message, error_size, "admin_auth: unauthorized admin for this relay");
        log_warning("SECURITY: Unauthorized admin event attempt for this relay");
        return -1;
    }
    
    // All validation layers passed
    log_info("ADMIN: Admin event authorized");
    return 0;
}

1.2 Update Event Routing Logic

Location: main.c:3248

// Current problematic code:
if (event_kind == 23455 || event_kind == 23456) {
    // Admin event processing
    int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
} else {
    // Regular event storage and broadcasting
}

// Enhanced secure code with consolidated authorization:
if (result == 0) {
    cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
    if (kind_obj && cJSON_IsNumber(kind_obj)) {
        int event_kind = (int)cJSON_GetNumberValue(kind_obj);
        
        // Check if this is an admin event
        if (event_kind == 23455 || event_kind == 23456) {
            // Use consolidated authorization check
            char auth_error[512] = {0};
            int auth_result = is_authorized_admin_event(event, auth_error, sizeof(auth_error));
            
            if (auth_result == 0) {
                // Authorized admin event - process through admin API
                char admin_error[512] = {0};
                int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
                
                if (admin_result != 0) {
                    result = -1;
                    strncpy(error_message, admin_error, sizeof(error_message) - 1);
                }
                // Admin events are NOT broadcast to subscriptions
            } else {
                // Unauthorized admin event - treat as regular event
                log_warning("Unauthorized admin event treated as regular event");
                if (store_event(event) != 0) {
                    result = -1;
                    strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
                } else {
                    broadcast_event_to_subscriptions(event);
                }
            }
        } else {
            // Regular event - normal processing
            if (store_event(event) != 0) {
                result = -1;
                strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
            } else {
                broadcast_event_to_subscriptions(event);
            }
        }
    }
}

Phase 2: Enhanced Admin Event Processing

2.1 Admin Event Validation in Config System

Location: src/config.c - process_admin_event_in_config()

Add additional validation within the admin processing function:

int process_admin_event_in_config(cJSON* event, char* error_buffer, size_t error_buffer_size, struct lws* wsi) {
    // Double-check authorization (defense in depth)
    if (!is_authorized_admin_event(event)) {
        snprintf(error_buffer, error_buffer_size, "unauthorized: not a valid admin event");
        return -1;
    }
    
    // Continue with existing admin event processing...
    // ... rest of function unchanged
}

2.2 Logging and Monitoring

Add comprehensive logging for admin event attempts:

// In the routing logic - enhanced logging
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
int event_kind = kind_obj ? cJSON_GetNumberValue(kind_obj) : -1;
const char* event_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : "unknown";

if (is_authorized_admin_event(event)) {
    char log_msg[256];
    snprintf(log_msg, sizeof(log_msg),
            "ADMIN EVENT: Authorized admin event (kind=%d) from pubkey=%.16s...",
            event_kind, event_pubkey);
    log_info(log_msg);
} else if (event_kind == 23455 || event_kind == 23456) {
    // This catches unauthorized admin event attempts
    char log_msg[256];
    snprintf(log_msg, sizeof(log_msg),
            "SECURITY: Unauthorized admin event attempt (kind=%d) from pubkey=%.16s...",
            event_kind, event_pubkey);
    log_warning(log_msg);
}

Phase 3: Unified Output Flow Architecture

3.1 Current Output Flow Analysis

After analyzing both main.c and config.c, the admin event responses already flow through the standard WebSocket output pipeline. This is the correct architecture and requires no changes.

Standard WebSocket Output Pipeline

Regular Events (main.c:2978-2996):

// Database query responses
unsigned char* buf = malloc(LWS_PRE + msg_len);
memcpy(buf + LWS_PRE, msg_str, msg_len);
lws_write(wsi, buf + LWS_PRE, msg_len, LWS_WRITE_TEXT);
free(buf);

OK Responses (main.c:3342-3375):

// Event processing results: ["OK", event_id, success_boolean, message]
unsigned char *buf = malloc(LWS_PRE + response_len);
memcpy(buf + LWS_PRE, response_str, response_len);
lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
free(buf);

Admin Event Output Pipeline (Already Unified)

Admin Responses (config.c:2363-2414):

// Admin query responses use IDENTICAL pattern
int send_websocket_response_data(struct lws* wsi, cJSON* response_data) {
    unsigned char* buf = malloc(LWS_PRE + response_len);
    memcpy(buf + LWS_PRE, response_str, response_len);
    
    // Same lws_write() call as regular events
    int result = lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
    
    free(buf);
    return result;
}

3.2 Unified Output Flow Confirmation

Admin responses already use the same WebSocket transmission mechanism as regular events

Both admin and regular events use identical buffer allocation patterns

Both admin and regular events use the same lws_write() function

Both admin and regular events follow the same cleanup patterns

3.3 Output Flow Integration Points

The admin event processing in config.c:2436 already integrates correctly with the unified output system:

  1. Admin Query Processing (config.c:2568-2583):

  2. Response Format Consistency:

    • Admin responses use standard JSON format
    • Regular events use standard Nostr event format
    • Both transmitted through same WebSocket pipeline
  3. Error Handling Consistency:

    • Admin errors returned via same WebSocket connection
    • Regular event errors returned via OK messages
    • Both use identical transmission mechanism

3.4 Key Architectural Benefits

No Changes Required: The output flow is already unified and correctly implemented.

Security Separation: Admin events are processed separately but responses flow through the same secure WebSocket channel.

Performance Consistency: Both admin and regular responses use the same optimized transmission path.

Maintenance Simplicity: Single WebSocket output pipeline reduces complexity and potential bugs.

3.5 Admin Event Flow Summary

Admin Event Input → Authorization Check → Admin Processing → Unified WebSocket Output
Regular Event Input → Validation → Storage + Broadcast → Unified WebSocket Output

Both flows converge at the Unified WebSocket Output stage, which is already correctly implemented.

Phase 4: Integration Points for Secure Admin Event Routing

4.1 Configuration System Integration

Required Configuration Values:

  • admin_pubkey - Public key of authorized administrator
  • relay_pubkey - Public key of this relay instance

Integration Points:

  1. get_config_value() - Used by authorization function
  2. get_relay_pubkey_cached() - Used for relay targeting validation
  3. Configuration loading during startup - Must ensure admin/relay pubkeys are available

4.3 Forward Declarations Required

Location: src/main.c - Add near other forward declarations (around line 230)

// Forward declarations for enhanced admin event authorization
int is_authorized_admin_event(cJSON* event, char* error_message, size_t error_size);

4.4 Error Handling Integration

Enhanced Error Response System:

// In main.c event processing - enhanced error handling for admin events
if (auth_result != 0) {
    // Admin authorization failed - send detailed OK response
    cJSON* event_id = cJSON_GetObjectItem(event, "id");
    if (event_id && cJSON_IsString(event_id)) {
        cJSON* response = cJSON_CreateArray();
        cJSON_AddItemToArray(response, cJSON_CreateString("OK"));
        cJSON_AddItemToArray(response, cJSON_CreateString(cJSON_GetStringValue(event_id)));
        cJSON_AddItemToArray(response, cJSON_CreateBool(0)); // Failed
        cJSON_AddItemToArray(response, cJSON_CreateString(auth_error));
        
        // Send via standard WebSocket output pipeline
        char *response_str = cJSON_Print(response);
        if (response_str) {
            size_t response_len = strlen(response_str);
            unsigned char *buf = malloc(LWS_PRE + response_len);
            if (buf) {
                memcpy(buf + LWS_PRE, response_str, response_len);
                lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
                free(buf);
            }
            free(response_str);
        }
        cJSON_Delete(response);
    }
}

4.5 Logging Integration Points

Console Logging: Uses existing log_warning(), log_info() functions

Security Event Categories:

  • Admin authorization success logged via log_info()
  • Admin authorization failures logged via log_warning()
  • Admin event processing logged via existing admin logging

Phase 5: Detailed Function Specifications

5.1 Core Authorization Function

Function: is_authorized_admin_event() Location: src/main.c or src/config.c Dependencies:

  • get_config_value() for admin/relay pubkeys
  • log_warning() and log_info() for logging
  • cJSON library for event parsing

Return Values:

  • 0 - Event is authorized for admin processing
  • -1 - Event is unauthorized (treat as regular event)
  • -2 - Validation error (malformed event)

Error Handling: Detailed error messages in provided buffer for client feedback

5.2 Enhanced Event Routing

Location: main.c:3248-3340 Integration: Replaces existing admin event routing logic Dependencies:

  • is_authorized_admin_event() for authorization
  • process_admin_event_in_config() for admin processing
  • store_event() and broadcast_event_to_subscriptions() for regular events

Security Features:

  • Graceful degradation for unauthorized admin events
  • Comprehensive logging of authorization attempts
  • No broadcast of admin events to subscriptions
  • Detailed error responses for failed authorization

5.4 Defense-in-Depth Validation

Primary Validation: In main event routing logic Secondary Validation: In process_admin_event_in_config() function Tertiary Validation: In individual admin command handlers

Validation Layers:

  1. Kind Check - Must be admin event kind (23455/23456)
  2. Relay Targeting Check - Must have 'p' tag with this relay's pubkey
  3. Admin Signature Check - Must be signed by authorized admin (only if targeting this relay)
  4. Processing Check - Additional validation in admin handlers

Security Logic:

  • If no 'p' tag for this relay → Admin event for different relay (not unauthorized)
  • If 'p' tag for this relay + wrong admin signature → "Unauthorized admin event attempt"

Phase 6: Event Flow Documentation

6.1 Complete Event Processing Flow

┌─────────────────┐
│ WebSocket Input │
└─────────┬───────┘
          │
          ▼
┌─────────────────┐
│ Unified         │
│ Validation      │ ← nostr_validate_unified_request()
└─────────┬───────┘
          │
          ▼
┌─────────────────┐
│ Kind-Based      │
│ Routing Check   │ ← Check if kind 23455/23456
└─────────┬───────┘
          │
     ┌────▼────┐
     │ Admin?  │
     └────┬────┘
          │
    ┌─────▼─────┐         ┌─────────────┐
    │ YES       │         │ NO          │
    │           │         │             │
    ▼           │         ▼             │
┌─────────────┐ │    ┌─────────────┐    │
│ Admin       │ │    │ Regular     │    │
│ Authorization│ │    │ Event       │    │
│ Check       │ │    │ Processing  │    │
└─────┬───────┘ │    └─────┬───────┘    │
      │         │          │            │
 ┌────▼────┐    │          ▼            │
 │Authorized?│   │    ┌─────────────┐    │
 └────┬────┘    │    │ store_event()│    │
      │         │    │ +           │    │
┌─────▼─────┐   │    │ broadcast() │    │
│ YES   NO  │   │    └─────┬───────┘    │
│  │     │  │   │          │            │
│  ▼     ▼  │   │          ▼            │
│┌─────┐┌───┴┐  │    ┌─────────────┐    │
││Admin││Treat│  │    │ WebSocket   │    │
││API  ││as   │  │    │ OK Response │    │
││     ││Reg  │  │    └─────────────┘    │
│└──┬──┘└───┬┘  │                       │
│   │      │   │                       │
│   ▼      │   │                       │
│┌─────────┐│   │                       │
││WebSocket││   │                       │
││Response ││   │                       │
│└─────────┘│   │                       │
└───────────┴───┘                       │
            │                           │
            └───────────────────────────┘
                        │
                        ▼
                ┌─────────────┐
                │ Unified     │
                │ WebSocket   │
                │ Output      │
                └─────────────┘

6.2 Security Decision Points

  1. Event Kind Check - Identifies potential admin events
  2. Authorization Validation - Three-layer security check
  3. Routing Decision - Admin API vs Regular processing
  4. Response Generation - Unified output pipeline
  5. Audit Logging - Security event tracking

6.3 Error Handling Paths

Validation Errors: Return detailed error messages via OK response Authorization Failures: Log security event + treat as regular event Processing Errors: Return admin-specific error responses System Errors: Fallback to standard error handling

This completes the comprehensive implementation plan for the enhanced admin event API structure with unified output flow architecture.