Working on API
This commit is contained in:
513
IMPLEMENT_API.md
Normal file
513
IMPLEMENT_API.md
Normal file
@@ -0,0 +1,513 @@
|
|||||||
|
# Implementation Plan: Enhanced Admin Event API Structure
|
||||||
|
|
||||||
|
## Current Issue
|
||||||
|
|
||||||
|
The current admin event routing at [`main.c:3248-3268`](src/main.c:3248) has a security vulnerability:
|
||||||
|
|
||||||
|
```c
|
||||||
|
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`](src/main.c) or [`src/config.c`](src/config.c)
|
||||||
|
|
||||||
|
```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`](src/main.c:3248)
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 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`](src/config.c) - [`process_admin_event_in_config()`](src/config.c:2065)
|
||||||
|
|
||||||
|
Add additional validation within the admin processing function:
|
||||||
|
|
||||||
|
```c
|
||||||
|
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:
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 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`](src/main.c) and [`config.c`](src/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`](src/main.c:2978)):
|
||||||
|
```c
|
||||||
|
// 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`](src/main.c:3342)):
|
||||||
|
```c
|
||||||
|
// 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`](src/config.c:2363)):
|
||||||
|
```c
|
||||||
|
// 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()`](src/config.c:2393) 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`](src/config.c:2436) already integrates correctly with the unified output system:
|
||||||
|
|
||||||
|
1. **Admin Query Processing** ([`config.c:2568-2583`](src/config.c:2568)):
|
||||||
|
- Auth queries return structured JSON via [`send_websocket_response_data()`](src/config.c:2571)
|
||||||
|
- System commands return status data via [`send_websocket_response_data()`](src/config.c:2631)
|
||||||
|
|
||||||
|
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()`](src/config.c) - Used by authorization function
|
||||||
|
2. [`get_relay_pubkey_cached()`](src/config.c) - 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`](src/main.c) - Add near other forward declarations (around line 230)
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 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**:
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 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()`](src/main.c:993), [`log_info()`](src/main.c:972) 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`](src/main.c) or [`src/config.c`](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`](src/main.c:3248)
|
||||||
|
**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.
|
||||||
371
README.md
371
README.md
@@ -24,55 +24,66 @@ Do NOT modify the formatting, add emojis, or change the text. Keep the simple fo
|
|||||||
|
|
||||||
## 🔧 Administrator API
|
## 🔧 Administrator API
|
||||||
|
|
||||||
C-Relay uses an innovative **event-based administration system** where all configuration and management commands are sent as signed Nostr events using the admin private key generated during first startup. Admin commands use ephemeral kinds 23455 and 23456 with **optional NIP-44 encryption** for enhanced security.
|
C-Relay uses an innovative **event-based administration system** where all configuration and management commands are sent as signed Nostr events using the admin private key generated during first startup. All admin commands use **tag-based parameters** for simplicity and compatibility.
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
All admin commands require signing with the admin private key displayed during first-time startup. **Save this key securely** - it cannot be recovered and is needed for all administrative operations.
|
All admin commands require signing with the admin private key displayed during first-time startup. **Save this key securely** - it cannot be recovered and is needed for all administrative operations.
|
||||||
|
|
||||||
### Security Options
|
### Event Structure
|
||||||
|
|
||||||
**Standard Mode (Plaintext):** Commands sent in tags as normal
|
All admin commands use the same unified event structure with tag-based parameters:
|
||||||
**Encrypted Mode (NIP-44):** Commands encrypted in content field, no tags used
|
|
||||||
|
|
||||||
### Kind 23455: Configuration Management (Ephemeral)
|
**Admin Command Event:**
|
||||||
Update relay configuration parameters or query available settings.
|
|
||||||
|
|
||||||
**Configuration Update:**
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"kind": 23455,
|
"id": "event_id",
|
||||||
"content": "<optional nip-44 encrypted tags>",
|
"pubkey": "admin_public_key",
|
||||||
|
"created_at": 1234567890,
|
||||||
|
"kind": 23456,
|
||||||
|
"content": "<nip44 encrypted command>",
|
||||||
"tags": [
|
"tags": [
|
||||||
["config_key1", "config_value1"],
|
["p", "relay_public_key"],
|
||||||
["config_key2", "config_value2"]
|
],
|
||||||
]
|
"sig": "event_signature"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Query Available Config Keys:**
|
**Admin Response Event:**
|
||||||
```json
|
```json
|
||||||
{
|
["EVENT", "temp_sub_id", {
|
||||||
"kind": 23455,
|
"id": "response_event_id",
|
||||||
"content": "<optional nip-44 encrypted tags>",
|
"pubkey": "relay_public_key",
|
||||||
|
"created_at": 1234567890,
|
||||||
|
"kind": 23457,
|
||||||
|
"content": "<nip44 encrypted response>",
|
||||||
"tags": [
|
"tags": [
|
||||||
["config_query", "list_all_keys"]
|
["p", "admin_public_key"]
|
||||||
]
|
],
|
||||||
}
|
"sig": "response_event_signature"
|
||||||
|
}]
|
||||||
```
|
```
|
||||||
|
|
||||||
**Get Current Configuration:**
|
### Admin Commands
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 23455,
|
|
||||||
"content": "<optional nip-44 encrypted tags>",
|
|
||||||
"tags": [
|
|
||||||
["config_query", "get_current_config"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Available Configuration Keys:**
|
All commands are sent as nip44 encrypted content. The following table lists all available commands:
|
||||||
|
|
||||||
|
| Command Type | Tag Format | Description |
|
||||||
|
|--------------|------------|-------------|
|
||||||
|
| **Configuration Management** |
|
||||||
|
| `config_update` | `["relay_description", "My Relay"]` | Update relay configuration parameters |
|
||||||
|
| `config_query` | `["config_query", "list_all_keys"]` | List all available configuration keys |
|
||||||
|
| **Auth Rules Management** |
|
||||||
|
| `auth_add_blacklist` | `["blacklist", "pubkey", "abc123..."]` | Add pubkey to blacklist |
|
||||||
|
| `auth_add_whitelist` | `["whitelist", "pubkey", "def456..."]` | Add pubkey to whitelist |
|
||||||
|
| `auth_query_all` | `["auth_query", "all"]` | Query all auth rules |
|
||||||
|
| `auth_query_type` | `["auth_query", "whitelist"]` | Query specific rule type |
|
||||||
|
| `auth_query_pattern` | `["auth_query", "pattern", "abc123..."]` | Query specific pattern |
|
||||||
|
| **System Commands** |
|
||||||
|
| `system_clear_auth` | `["system_command", "clear_all_auth_rules"]` | Clear all auth rules |
|
||||||
|
| `system_status` | `["system_command", "system_status"]` | Get system status |
|
||||||
|
|
||||||
|
### Available Configuration Keys
|
||||||
|
|
||||||
**Basic Relay Settings:**
|
**Basic Relay Settings:**
|
||||||
- `relay_description`: Relay description text
|
- `relay_description`: Relay description text
|
||||||
@@ -92,259 +103,69 @@ Update relay configuration parameters or query available settings.
|
|||||||
- `pow_min_difficulty`: Minimum proof-of-work difficulty
|
- `pow_min_difficulty`: Minimum proof-of-work difficulty
|
||||||
- `nip40_expiration_enabled`: Enable event expiration (`true`/`false`)
|
- `nip40_expiration_enabled`: Enable event expiration (`true`/`false`)
|
||||||
|
|
||||||
### Kind 23456: Auth Rules & System Management (Ephemeral)
|
|
||||||
Manage whitelist/blacklist rules and system administration commands.
|
|
||||||
|
|
||||||
**Add Blacklist Rule:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 23456,
|
|
||||||
"content": "<optional nip-44 encrypted tags>",
|
|
||||||
"tags": [
|
|
||||||
["blacklist", "pubkey", "deadbeef1234abcd..."]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Add Whitelist Rule:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 23456,
|
|
||||||
"content": "<optional nip-44 encrypted tags>",
|
|
||||||
"tags": [
|
|
||||||
["whitelist", "pubkey", "cafebabe5678efgh..."]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Remove Rule:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 23456,
|
|
||||||
"content": "<optional nip-44 encrypted tags>",
|
|
||||||
"tags": [
|
|
||||||
["blacklist", "pubkey", "deadbeef1234abcd..."]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Query Auth Rules:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 23456,
|
|
||||||
"content": "<optional nip-44 encrypted tags>",
|
|
||||||
"tags": [
|
|
||||||
["auth_query", "all"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Query Specific Rule Type:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 23456,
|
|
||||||
"content": "<optional nip-44 encrypted tags>",
|
|
||||||
"tags": [
|
|
||||||
["auth_query", "whitelist"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Query Specific Pattern:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 23456,
|
|
||||||
"content": "<optional nip-44 encrypted tags>",
|
|
||||||
"tags": [
|
|
||||||
["auth_query", "pattern", "deadbeef1234abcd..."]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Clear All Auth Rules:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 23456,
|
|
||||||
"content": "<optional nip-44 encrypted tags>",
|
|
||||||
"tags": [
|
|
||||||
["system_command", "clear_all_auth_rules"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Response Format
|
### Response Format
|
||||||
All admin commands return JSON responses via WebSocket:
|
|
||||||
|
All admin commands return **signed EVENT responses** via WebSocket following standard Nostr protocol. Responses use JSON content with structured data.
|
||||||
|
|
||||||
|
#### Response Examples
|
||||||
|
|
||||||
**Success Response:**
|
**Success Response:**
|
||||||
```json
|
```json
|
||||||
["OK", "event_id", true, "Operation completed successfully"]
|
["EVENT", "temp_sub_id", {
|
||||||
|
"id": "response_event_id",
|
||||||
|
"pubkey": "relay_public_key",
|
||||||
|
"created_at": 1234567890,
|
||||||
|
"kind": 23457,
|
||||||
|
"content": "nip44 encrypted:{\"status\": \"success\", \"message\": \"Operation completed successfully\"}",
|
||||||
|
"tags": [
|
||||||
|
["p", "admin_public_key"]
|
||||||
|
],
|
||||||
|
"sig": "response_event_signature"
|
||||||
|
}]
|
||||||
```
|
```
|
||||||
|
|
||||||
**Error Response:**
|
**Error Response:**
|
||||||
```json
|
```json
|
||||||
["OK", "event_id", false, "Error: invalid configuration value"]
|
["EVENT", "temp_sub_id", {
|
||||||
```
|
"id": "response_event_id",
|
||||||
|
"pubkey": "relay_public_key",
|
||||||
### Command Examples
|
"created_at": 1234567890,
|
||||||
|
"kind": 23457,
|
||||||
**Using `nak` CLI tool:**
|
"content": "nip44 encrypted:{\"status\": \"error\", \"message\": \"Error: invalid configuration value\"}",
|
||||||
|
"tags": [
|
||||||
```bash
|
["p", "admin_public_key"]
|
||||||
# Set environment variables
|
],
|
||||||
ADMIN_PRIVKEY="your_admin_private_key_here"
|
"sig": "response_event_signature"
|
||||||
RELAY_PUBKEY="your_relay_public_key_here"
|
|
||||||
RELAY_URL="ws://localhost:8888"
|
|
||||||
|
|
||||||
# List all available configuration keys
|
|
||||||
nak event -k 23455 --content "Discovery query" \
|
|
||||||
-t "config_query=list_all_keys" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
|
||||||
|
|
||||||
# Enable whitelist/blacklist auth rules
|
|
||||||
nak event -k 23455 --content "Enable auth rules" \
|
|
||||||
-t "auth_enabled=true" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
|
||||||
|
|
||||||
# Add user to blacklist
|
|
||||||
nak event -k 23456 --content "Block spam user" \
|
|
||||||
-t "blacklist=pubkey;$SPAM_USER_PUBKEY" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
|
||||||
|
|
||||||
# Add user to whitelist
|
|
||||||
nak event -k 23456 --content "Allow trusted user" \
|
|
||||||
-t "whitelist=pubkey;$TRUSTED_USER_PUBKEY" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
|
||||||
|
|
||||||
# Query all current auth rules
|
|
||||||
nak event -k 23456 --content "Get all auth rules" \
|
|
||||||
-t "auth_query=all" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
|
||||||
|
|
||||||
# Query only whitelist rules
|
|
||||||
nak event -k 23456 --content "Get whitelist rules" \
|
|
||||||
-t "auth_query=whitelist" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
|
||||||
|
|
||||||
# Query only blacklist rules
|
|
||||||
nak event -k 23456 --content "Get blacklist rules" \
|
|
||||||
-t "auth_query=blacklist" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
|
||||||
|
|
||||||
# Check if specific pattern exists
|
|
||||||
nak event -k 23456 --content "Check user status" \
|
|
||||||
-t "auth_query=pattern;$CHECK_USER_PUBKEY" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
|
||||||
|
|
||||||
# Clear all auth rules (for testing)
|
|
||||||
nak event -k 23456 --content "Clear all rules" \
|
|
||||||
-t "system_command=clear_all_auth_rules" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
|
||||||
|
|
||||||
# Update relay description
|
|
||||||
nak event -k 23455 --content "Update description" \
|
|
||||||
-t "relay_description=My awesome relay" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
|
||||||
|
|
||||||
# Set connection limits
|
|
||||||
nak event -k 23455 --content "Update limits" \
|
|
||||||
-t "max_connections=500" \
|
|
||||||
-t "max_subscriptions_per_client=10" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
|
||||||
```
|
|
||||||
|
|
||||||
**For encrypted commands (NIP-44), use empty tags and encrypted content:**
|
|
||||||
```bash
|
|
||||||
# Enable auth rules (encrypted)
|
|
||||||
ENCRYPTED_TAGS=$(echo '[["auth_enabled","true"]]' | nip44_encrypt_with_relay_pubkey)
|
|
||||||
nak event -k 23455 --content "{\"encrypted_tags\":\"$ENCRYPTED_TAGS\"}" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
|
||||||
|
|
||||||
# Add user to blacklist (encrypted)
|
|
||||||
ENCRYPTED_TAGS=$(echo '[["blacklist","pubkey","'$SPAM_USER_PUBKEY'"]]' | nip44_encrypt_with_relay_pubkey)
|
|
||||||
nak event -k 23456 --content "{\"encrypted_tags\":\"$ENCRYPTED_TAGS\"}" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
|
||||||
```
|
|
||||||
|
|
||||||
### Authentication Systems
|
|
||||||
|
|
||||||
C-Relay supports **two independent authentication systems**:
|
|
||||||
|
|
||||||
#### 1. Auth Rules (Whitelist/Blacklist)
|
|
||||||
- **Config Key**: `auth_enabled=true`
|
|
||||||
- **Purpose**: Block or allow specific pubkeys from publishing events
|
|
||||||
- **Method**: Database-driven rules enforcement
|
|
||||||
- **Use Case**: Spam prevention, content moderation
|
|
||||||
|
|
||||||
#### 2. NIP-42 Cryptographic Authentication
|
|
||||||
- **Config Key**: `nip42_auth_required=true`
|
|
||||||
- **Purpose**: Require cryptographic proof of pubkey ownership
|
|
||||||
- **Method**: Challenge-response authentication protocol
|
|
||||||
- **Use Case**: Verified identity, premium features
|
|
||||||
|
|
||||||
**Important**: These systems can be used independently or together:
|
|
||||||
- `auth_enabled=true` + `nip42_auth_required=false`: Only whitelist/blacklist rules
|
|
||||||
- `auth_enabled=false` + `nip42_auth_required=true`: Only NIP-42 auth challenges
|
|
||||||
- Both `true`: Users must pass NIP-42 auth AND not be blacklisted
|
|
||||||
|
|
||||||
### Security Considerations
|
|
||||||
|
|
||||||
- **Private Key Protection**: Admin private key grants full relay control
|
|
||||||
- **Network Access**: Admin commands should only be sent over secure connections
|
|
||||||
- **Backup**: Keep secure backups of admin private key
|
|
||||||
- **Rotation**: Consider implementing admin key rotation for production deployments
|
|
||||||
|
|
||||||
### Troubleshooting
|
|
||||||
|
|
||||||
**Common Issues:**
|
|
||||||
- `auth-required: not authorized admin`: Verify you're using the correct admin private key
|
|
||||||
- `invalid: missing or invalid kind`: Ensure event kind is 23455 or 23456
|
|
||||||
- `error: failed to process configuration event`: Check configuration key/value validity
|
|
||||||
- `no valid auth rules found`: Verify tag format uses semicolon syntax for multi-element tags
|
|
||||||
|
|
||||||
**Debug Commands:**
|
|
||||||
```bash
|
|
||||||
# Check if relay is accepting connections
|
|
||||||
echo '["REQ","test",{}]' | websocat ws://localhost:8888
|
|
||||||
|
|
||||||
# Test admin authentication
|
|
||||||
nak event -k 23455 --content "Test auth" \
|
|
||||||
-t "test_config=true" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
|
||||||
|
|
||||||
# Discover available configuration keys
|
|
||||||
nak event -k 23455 --content "Discovery query" \
|
|
||||||
-t "config_query=list_all_keys" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
|
||||||
|
|
||||||
# Query current auth rules
|
|
||||||
nak event -k 23456 --content "Get auth rules" \
|
|
||||||
-t "auth_query=all" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuration Discovery
|
|
||||||
|
|
||||||
Before making changes, admins can query the relay to discover all available configuration options:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Get list of all editable configuration keys with descriptions
|
|
||||||
nak event -k 23455 --content '{"query":"list_config_keys","description":"Discovery"}' \
|
|
||||||
-t "config_query=list_all_keys" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
|
||||||
|
|
||||||
# Get current values of all configuration parameters
|
|
||||||
nak event -k 23455 --content '{"query":"get_config","description":"Current state"}' \
|
|
||||||
-t "config_query=get_current_config" \
|
|
||||||
--sec $ADMIN_PRIVKEY | nak event $RELAY_URL
|
|
||||||
```
|
|
||||||
|
|
||||||
**Expected Response Format:**
|
|
||||||
```json
|
|
||||||
["EVENT", "subscription_id", {
|
|
||||||
"kind": 23455,
|
|
||||||
"content": "{\"config_keys\": [\"auth_enabled\", \"max_connections\", ...], \"descriptions\": {...}}",
|
|
||||||
"tags": [["response_type", "config_keys_list"]]
|
|
||||||
}]
|
}]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Auth Rules Query Response:**
|
||||||
|
```json
|
||||||
|
["EVENT", "temp_sub_id", {
|
||||||
|
"id": "response_event_id",
|
||||||
|
"pubkey": "relay_public_key",
|
||||||
|
"created_at": 1234567890,
|
||||||
|
"kind": 23457,
|
||||||
|
"content": "nip44 encrypted:{\"query_type\": \"auth_rules\", \"total_results\": 2, \"data\": [{\"rule_type\": \"blacklist\", \"pattern_type\": \"pubkey\", \"pattern_value\": \"abc123...\", \"action\": \"deny\"}]}",
|
||||||
|
"tags": [
|
||||||
|
["p", "admin_public_key"]
|
||||||
|
],
|
||||||
|
"sig": "response_event_signature"
|
||||||
|
}]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Configuration Query Response:**
|
||||||
|
```json
|
||||||
|
["EVENT", "temp_sub_id", {
|
||||||
|
"id": "response_event_id",
|
||||||
|
"pubkey": "relay_public_key",
|
||||||
|
"created_at": 1234567890,
|
||||||
|
"kind": 23457,
|
||||||
|
"content": "nip44 encrypted:{\"query_type\": \"config_keys\", \"config_keys\": [\"auth_enabled\", \"max_connections\"], \"descriptions\": {\"auth_enabled\": \"Enable whitelist/blacklist rules\"}}",
|
||||||
|
"tags": [
|
||||||
|
["p", "admin_public_key"]
|
||||||
|
],
|
||||||
|
"sig": "response_event_signature"
|
||||||
|
}]
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -198,25 +198,54 @@ fi
|
|||||||
|
|
||||||
echo "Build successful. Proceeding with relay restart..."
|
echo "Build successful. Proceeding with relay restart..."
|
||||||
|
|
||||||
# Kill existing relay if running
|
# Kill existing relay if running - start aggressive immediately
|
||||||
echo "Stopping any existing relay servers..."
|
echo "Stopping any existing relay servers..."
|
||||||
pkill -f "c_relay_" 2>/dev/null
|
|
||||||
sleep 2 # Give time for shutdown
|
|
||||||
|
|
||||||
# Check if port is still bound
|
# Get all relay processes and kill them immediately with -9
|
||||||
if lsof -i :8888 >/dev/null 2>&1; then
|
RELAY_PIDS=$(pgrep -f "c_relay_" || echo "")
|
||||||
echo "Port 8888 still in use, force killing..."
|
if [ -n "$RELAY_PIDS" ]; then
|
||||||
fuser -k 8888/tcp 2>/dev/null || echo "No process on port 8888"
|
echo "Force killing relay processes immediately: $RELAY_PIDS"
|
||||||
|
kill -9 $RELAY_PIDS 2>/dev/null
|
||||||
|
else
|
||||||
|
echo "No existing relay processes found"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get any remaining processes
|
# Ensure port 8888 is completely free with retry loop
|
||||||
REMAINING_PIDS=$(pgrep -f "c_relay_" || echo "")
|
echo "Ensuring port 8888 is available..."
|
||||||
if [ -n "$REMAINING_PIDS" ]; then
|
for attempt in {1..15}; do
|
||||||
echo "Force killing remaining processes: $REMAINING_PIDS"
|
if ! lsof -i :8888 >/dev/null 2>&1; then
|
||||||
kill -9 $REMAINING_PIDS 2>/dev/null
|
echo "Port 8888 is now free"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Attempt $attempt: Port 8888 still in use, force killing..."
|
||||||
|
# Kill anything using port 8888
|
||||||
|
fuser -k 8888/tcp 2>/dev/null || true
|
||||||
|
|
||||||
|
# Double-check for any remaining relay processes
|
||||||
|
REMAINING_PIDS=$(pgrep -f "c_relay_" || echo "")
|
||||||
|
if [ -n "$REMAINING_PIDS" ]; then
|
||||||
|
echo "Killing remaining relay processes: $REMAINING_PIDS"
|
||||||
|
kill -9 $REMAINING_PIDS 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
if [ $attempt -eq 15 ]; then
|
||||||
|
echo "ERROR: Could not free port 8888 after 15 attempts"
|
||||||
|
echo "Current processes using port:"
|
||||||
|
lsof -i :8888 2>/dev/null || echo "No process details available"
|
||||||
|
echo "You may need to manually kill processes or reboot"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Final safety check - ensure no relay processes remain
|
||||||
|
FINAL_PIDS=$(pgrep -f "c_relay_" || echo "")
|
||||||
|
if [ -n "$FINAL_PIDS" ]; then
|
||||||
|
echo "Final cleanup: killing processes $FINAL_PIDS"
|
||||||
|
kill -9 $FINAL_PIDS 2>/dev/null || true
|
||||||
sleep 1
|
sleep 1
|
||||||
else
|
|
||||||
echo "No existing relay found"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Clean up PID file
|
# Clean up PID file
|
||||||
|
|||||||
975
src/config.c
975
src/config.c
File diff suppressed because it is too large
Load Diff
19
src/config.h
19
src/config.h
@@ -6,6 +6,9 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
|
// Forward declaration for WebSocket support
|
||||||
|
struct lws;
|
||||||
|
|
||||||
// Configuration constants
|
// Configuration constants
|
||||||
#define CONFIG_VALUE_MAX_LENGTH 1024
|
#define CONFIG_VALUE_MAX_LENGTH 1024
|
||||||
#define RELAY_NAME_MAX_LENGTH 256
|
#define RELAY_NAME_MAX_LENGTH 256
|
||||||
@@ -160,10 +163,20 @@ int update_config_in_table(const char* key, const char* value);
|
|||||||
int populate_default_config_values(void);
|
int populate_default_config_values(void);
|
||||||
int add_pubkeys_to_config_table(void);
|
int add_pubkeys_to_config_table(void);
|
||||||
|
|
||||||
// Admin event processing functions
|
// Admin event processing functions (updated with WebSocket support)
|
||||||
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size);
|
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||||
int process_admin_config_event(cJSON* event, char* error_message, size_t error_size);
|
int process_admin_config_event(cJSON* event, char* error_message, size_t error_size);
|
||||||
int process_admin_auth_event(cJSON* event, char* error_message, size_t error_size);
|
int process_admin_auth_event(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||||
|
|
||||||
|
// Unified Kind 23456 handler functions
|
||||||
|
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);
|
||||||
|
|
||||||
|
// WebSocket response functions
|
||||||
|
int send_websocket_response_data(cJSON* event, cJSON* response_data, struct lws* wsi);
|
||||||
|
cJSON* build_query_response(const char* query_type, cJSON* results_array, int total_count);
|
||||||
|
|
||||||
// Auth rules management functions
|
// Auth rules management functions
|
||||||
int add_auth_rule_from_config(const char* rule_type, const char* pattern_type,
|
int add_auth_rule_from_config(const char* rule_type, const char* pattern_type,
|
||||||
|
|||||||
197
src/main.c
197
src/main.c
@@ -228,7 +228,10 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length);
|
|||||||
int handle_configuration_event(cJSON* event, char* error_message, size_t error_size);
|
int handle_configuration_event(cJSON* event, char* error_message, size_t error_size);
|
||||||
|
|
||||||
// Forward declaration for admin event processing (kinds 33334 and 33335)
|
// Forward declaration for admin event processing (kinds 33334 and 33335)
|
||||||
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size);
|
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||||
|
|
||||||
|
// Forward declaration for enhanced admin event authorization
|
||||||
|
int is_authorized_admin_event(cJSON* event, char* error_message, size_t error_size);
|
||||||
|
|
||||||
// Forward declaration for NOTICE message support
|
// Forward declaration for NOTICE message support
|
||||||
void send_notice_message(struct lws* wsi, const char* message);
|
void send_notice_message(struct lws* wsi, const char* message);
|
||||||
@@ -3009,6 +3012,109 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
|||||||
|
|
||||||
return events_sent;
|
return events_sent;
|
||||||
}
|
}
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// ADMIN EVENT AUTHORIZATION
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Enhanced admin event authorization function
|
||||||
|
int is_authorized_admin_event(cJSON* event, char* error_buffer, size_t error_buffer_size) {
|
||||||
|
if (!event || !error_buffer) {
|
||||||
|
if (error_buffer && error_buffer_size > 0) {
|
||||||
|
snprintf(error_buffer, error_buffer_size, "Invalid parameters for admin authorization");
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Verify event kind is admin type
|
||||||
|
cJSON *kind_json = cJSON_GetObjectItem(event, "kind");
|
||||||
|
if (!kind_json || !cJSON_IsNumber(kind_json)) {
|
||||||
|
snprintf(error_buffer, error_buffer_size, "Missing or invalid event kind");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int event_kind = kind_json->valueint;
|
||||||
|
if (event_kind != 33334 && event_kind != 33335 && event_kind != 23455 && event_kind != 23456) {
|
||||||
|
snprintf(error_buffer, error_buffer_size, "Event kind %d is not an admin event type", event_kind);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Check if event targets this relay (look for 'p' tag with our relay pubkey)
|
||||||
|
cJSON *tags = cJSON_GetObjectItem(event, "tags");
|
||||||
|
if (!tags || !cJSON_IsArray(tags)) {
|
||||||
|
// No tags array - treat as regular event for different relay
|
||||||
|
log_info("Admin event has no tags array - treating as event for different relay");
|
||||||
|
snprintf(error_buffer, error_buffer_size, "Admin event not targeting this relay (no tags)");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int targets_this_relay = 0;
|
||||||
|
cJSON *tag;
|
||||||
|
cJSON_ArrayForEach(tag, tags) {
|
||||||
|
if (cJSON_IsArray(tag)) {
|
||||||
|
cJSON *tag_name = cJSON_GetArrayItem(tag, 0);
|
||||||
|
cJSON *tag_value = cJSON_GetArrayItem(tag, 1);
|
||||||
|
|
||||||
|
if (tag_name && cJSON_IsString(tag_name) &&
|
||||||
|
tag_value && cJSON_IsString(tag_value) &&
|
||||||
|
strcmp(tag_name->valuestring, "p") == 0) {
|
||||||
|
|
||||||
|
// Compare with our relay pubkey
|
||||||
|
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||||
|
if (relay_pubkey && strcmp(tag_value->valuestring, relay_pubkey) == 0) {
|
||||||
|
targets_this_relay = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targets_this_relay) {
|
||||||
|
// Admin event for different relay - not an error, just not for us
|
||||||
|
log_info("Admin event targets different relay - treating as regular event");
|
||||||
|
snprintf(error_buffer, error_buffer_size, "Admin event not targeting this relay");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Verify admin signature authorization
|
||||||
|
cJSON *pubkey_json = cJSON_GetObjectItem(event, "pubkey");
|
||||||
|
if (!pubkey_json || !cJSON_IsString(pubkey_json)) {
|
||||||
|
log_warning("Unauthorized admin event attempt: missing or invalid pubkey");
|
||||||
|
snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: missing pubkey");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get admin pubkey from configuration
|
||||||
|
const char* admin_pubkey = get_config_value("admin_pubkey");
|
||||||
|
if (!admin_pubkey || strlen(admin_pubkey) == 0) {
|
||||||
|
log_warning("Unauthorized admin event attempt: no admin pubkey configured");
|
||||||
|
snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: no admin configured");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare pubkeys
|
||||||
|
if (strcmp(pubkey_json->valuestring, admin_pubkey) != 0) {
|
||||||
|
log_warning("Unauthorized admin event attempt: pubkey mismatch");
|
||||||
|
char warning_msg[256];
|
||||||
|
snprintf(warning_msg, sizeof(warning_msg),
|
||||||
|
"Unauthorized admin event attempt from pubkey: %.32s...", pubkey_json->valuestring);
|
||||||
|
log_warning(warning_msg);
|
||||||
|
snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: invalid admin pubkey");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Verify event signature
|
||||||
|
if (nostr_verify_event_signature(event) != 0) {
|
||||||
|
log_warning("Unauthorized admin event attempt: invalid signature");
|
||||||
|
snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: signature verification failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All checks passed - authorized admin event
|
||||||
|
log_info("Admin event authorization successful");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -3266,47 +3372,68 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event_kind == 33334 || event_kind == 33335 || event_kind == 23455 || event_kind == 23456) {
|
if (event_kind == 33334 || event_kind == 33335 || event_kind == 23455 || event_kind == 23456) {
|
||||||
// This is an admin event - process it through the admin API instead of normal storage
|
// Enhanced admin event security - check authorization first
|
||||||
log_info("DEBUG ADMIN: Admin event detected, processing through admin API");
|
log_info("DEBUG ADMIN: Admin event detected, checking authorization");
|
||||||
|
|
||||||
char admin_error[512] = {0};
|
char auth_error[512] = {0};
|
||||||
int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error));
|
int auth_result = is_authorized_admin_event(event, auth_error, sizeof(auth_error));
|
||||||
|
|
||||||
char debug_admin_msg[256];
|
if (auth_result != 0) {
|
||||||
snprintf(debug_admin_msg, sizeof(debug_admin_msg),
|
// Authorization failed - log and reject
|
||||||
"DEBUG ADMIN: process_admin_event_in_config returned %d", admin_result);
|
log_warning("DEBUG ADMIN: Admin event authorization failed");
|
||||||
log_info(debug_admin_msg);
|
|
||||||
|
|
||||||
// Log results for Kind 23455 and 23456 events
|
|
||||||
if (event_kind == 23455 || event_kind == 23456) {
|
|
||||||
if (admin_result == 0) {
|
|
||||||
char success_result_msg[256];
|
|
||||||
snprintf(success_result_msg, sizeof(success_result_msg),
|
|
||||||
"SUCCESS: Kind %d event processed successfully", event_kind);
|
|
||||||
log_success(success_result_msg);
|
|
||||||
} else {
|
|
||||||
char error_result_msg[512];
|
|
||||||
snprintf(error_result_msg, sizeof(error_result_msg),
|
|
||||||
"ERROR: Kind %d event processing failed: %s", event_kind, admin_error);
|
|
||||||
log_error(error_result_msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (admin_result != 0) {
|
|
||||||
log_error("DEBUG ADMIN: Failed to process admin event through admin API");
|
|
||||||
result = -1;
|
result = -1;
|
||||||
size_t error_len = strlen(admin_error);
|
size_t error_len = strlen(auth_error);
|
||||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||||
memcpy(error_message, admin_error, copy_len);
|
memcpy(error_message, auth_error, copy_len);
|
||||||
error_message[copy_len] = '\0';
|
error_message[copy_len] = '\0';
|
||||||
|
|
||||||
char debug_admin_error_msg[600];
|
char debug_auth_error_msg[600];
|
||||||
snprintf(debug_admin_error_msg, sizeof(debug_admin_error_msg),
|
snprintf(debug_auth_error_msg, sizeof(debug_auth_error_msg),
|
||||||
"DEBUG ADMIN ERROR: %.400s", admin_error);
|
"DEBUG ADMIN AUTH ERROR: %.400s", auth_error);
|
||||||
log_error(debug_admin_error_msg);
|
log_warning(debug_auth_error_msg);
|
||||||
} else {
|
} else {
|
||||||
log_success("DEBUG ADMIN: Admin event processed successfully through admin API");
|
// Authorization successful - process through admin API
|
||||||
// Admin events are processed by the admin API, not broadcast to subscriptions
|
log_info("DEBUG ADMIN: Admin event authorized, processing through admin API");
|
||||||
|
|
||||||
|
char admin_error[512] = {0};
|
||||||
|
int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
|
||||||
|
|
||||||
|
char debug_admin_msg[256];
|
||||||
|
snprintf(debug_admin_msg, sizeof(debug_admin_msg),
|
||||||
|
"DEBUG ADMIN: process_admin_event_in_config returned %d", admin_result);
|
||||||
|
log_info(debug_admin_msg);
|
||||||
|
|
||||||
|
// Log results for Kind 23455 and 23456 events
|
||||||
|
if (event_kind == 23455 || event_kind == 23456) {
|
||||||
|
if (admin_result == 0) {
|
||||||
|
char success_result_msg[256];
|
||||||
|
snprintf(success_result_msg, sizeof(success_result_msg),
|
||||||
|
"SUCCESS: Kind %d event processed successfully", event_kind);
|
||||||
|
log_success(success_result_msg);
|
||||||
|
} else {
|
||||||
|
char error_result_msg[512];
|
||||||
|
snprintf(error_result_msg, sizeof(error_result_msg),
|
||||||
|
"ERROR: Kind %d event processing failed: %s", event_kind, admin_error);
|
||||||
|
log_error(error_result_msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (admin_result != 0) {
|
||||||
|
log_error("DEBUG ADMIN: Failed to process admin event through admin API");
|
||||||
|
result = -1;
|
||||||
|
size_t error_len = strlen(admin_error);
|
||||||
|
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||||
|
memcpy(error_message, admin_error, copy_len);
|
||||||
|
error_message[copy_len] = '\0';
|
||||||
|
|
||||||
|
char debug_admin_error_msg[600];
|
||||||
|
snprintf(debug_admin_error_msg, sizeof(debug_admin_error_msg),
|
||||||
|
"DEBUG ADMIN ERROR: %.400s", admin_error);
|
||||||
|
log_error(debug_admin_error_msg);
|
||||||
|
} else {
|
||||||
|
log_success("DEBUG ADMIN: Admin event processed successfully through admin API");
|
||||||
|
// Admin events are processed by the admin API, not broadcast to subscriptions
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Regular event - store in database and broadcast
|
// Regular event - store in database and broadcast
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ RELAY_URL="ws://${RELAY_HOST}:${RELAY_PORT}"
|
|||||||
TIMEOUT=5
|
TIMEOUT=5
|
||||||
TEMP_DIR="/tmp/c_relay_test_$$"
|
TEMP_DIR="/tmp/c_relay_test_$$"
|
||||||
|
|
||||||
|
# WebSocket connection state
|
||||||
|
WS_PID=""
|
||||||
|
WS_INPUT_FIFO=""
|
||||||
|
WS_OUTPUT_FIFO=""
|
||||||
|
WS_CONNECTED=0
|
||||||
|
WS_RESPONSE_LOG=""
|
||||||
|
|
||||||
# Color codes for output
|
# Color codes for output
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
@@ -147,6 +154,145 @@ send_websocket_message() {
|
|||||||
echo "$response"
|
echo "$response"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# =======================================================================
|
||||||
|
# PERSISTENT WEBSOCKET CONNECTION MANAGEMENT
|
||||||
|
# =======================================================================
|
||||||
|
|
||||||
|
# Open persistent WebSocket connection
|
||||||
|
open_websocket_connection() {
|
||||||
|
log_info "Opening persistent WebSocket connection to $RELAY_URL..."
|
||||||
|
|
||||||
|
# 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_$$"
|
||||||
|
|
||||||
|
# Create named pipes
|
||||||
|
mkfifo "$WS_INPUT_FIFO" "$WS_OUTPUT_FIFO"
|
||||||
|
|
||||||
|
# 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=$!
|
||||||
|
|
||||||
|
# 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}>&-
|
||||||
|
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"
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wait for query response data from relay
|
||||||
|
wait_for_query_response() {
|
||||||
|
local timeout_seconds="${1:-10}"
|
||||||
|
local start_time=$(date +%s)
|
||||||
|
|
||||||
|
log_info "Waiting for query response data..."
|
||||||
|
|
||||||
|
# Clear any OK responses and wait for JSON data
|
||||||
|
sleep 0.5 # Brief delay to ensure OK response is processed first
|
||||||
|
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
log_error "Timeout waiting for query response data"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# Create and send auth rule event
|
# Create and send auth rule event
|
||||||
send_auth_rule_event() {
|
send_auth_rule_event() {
|
||||||
local action="$1" # "add" or "remove"
|
local action="$1" # "add" or "remove"
|
||||||
@@ -157,12 +303,13 @@ send_auth_rule_event() {
|
|||||||
|
|
||||||
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:0:16}..."
|
||||||
|
|
||||||
# Create the auth rule event using nak with correct tag format
|
# Create the auth rule event using nak with correct tag format for the actual implementation
|
||||||
# Server expects proper key=value tags for auth rules
|
# Server expects tags like ["whitelist", "pubkey", "abc123..."] or ["blacklist", "pubkey", "def456..."]
|
||||||
# Using Kind 23456 (ephemeral auth rules management) - no d tag needed
|
# Using Kind 23456 (ephemeral auth rules management) with proper relay targeting
|
||||||
local event_json
|
local event_json
|
||||||
event_json=$(nak event -k 23456 --content "{\"action\":\"$action\",\"description\":\"$description\"}" \
|
event_json=$(nak event -k 23456 --content "" \
|
||||||
-t "$rule_type=$pattern_type" -t "pattern_value=$pattern_value" \
|
-t "p=$RELAY_PUBKEY" \
|
||||||
|
-t "$rule_type=$pattern_type=$pattern_value" \
|
||||||
--sec "$ADMIN_PRIVKEY" 2>/dev/null)
|
--sec "$ADMIN_PRIVKEY" 2>/dev/null)
|
||||||
|
|
||||||
if [ $? -ne 0 ] || [ -z "$event_json" ]; then
|
if [ $? -ne 0 ] || [ -z "$event_json" ]; then
|
||||||
@@ -170,21 +317,38 @@ send_auth_rule_event() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Send the event using nak directly to relay (more reliable than websocat)
|
# Send the event through persistent WebSocket connection
|
||||||
log_info "Publishing auth rule event to relay..."
|
log_info "Publishing auth rule event to relay..."
|
||||||
local result
|
local result
|
||||||
result=$(echo "$event_json" | timeout 10s nak event "$RELAY_URL" 2>&1)
|
if [ "$WS_CONNECTED" = "1" ]; then
|
||||||
local exit_code=$?
|
result=$(send_websocket_event "$event_json")
|
||||||
|
local exit_code=$?
|
||||||
log_info "Auth rule event result: $result"
|
|
||||||
|
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
|
# Check if response indicates success
|
||||||
log_success "Auth rule $action successful"
|
if [ $exit_code -eq 0 ] && echo "$result" | grep -q -i '"OK".*true'; then
|
||||||
return 0
|
log_success "Auth rule $action successful"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_error "Auth rule $action failed: $result (exit code: $exit_code)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
log_error "Auth rule $action failed: $result (exit code: $exit_code)"
|
# Fallback to one-shot connection if persistent connection not available
|
||||||
return 1
|
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
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,9 +357,10 @@ clear_all_auth_rules() {
|
|||||||
log_info "Clearing all existing auth rules..."
|
log_info "Clearing all existing auth rules..."
|
||||||
|
|
||||||
# Create system command event to clear all auth rules
|
# Create system command event to clear all auth rules
|
||||||
# Using Kind 23456 (ephemeral auth rules management)
|
# Using Kind 23456 (ephemeral auth rules management) with proper relay targeting
|
||||||
local event_json
|
local event_json
|
||||||
event_json=$(nak event -k 23456 --content "{\"action\":\"clear_all\"}" \
|
event_json=$(nak event -k 23456 --content "" \
|
||||||
|
-t "p=$RELAY_PUBKEY" \
|
||||||
-t "system_command=clear_all_auth_rules" \
|
-t "system_command=clear_all_auth_rules" \
|
||||||
--sec "$ADMIN_PRIVKEY" 2>/dev/null)
|
--sec "$ADMIN_PRIVKEY" 2>/dev/null)
|
||||||
|
|
||||||
@@ -204,21 +369,38 @@ clear_all_auth_rules() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Send the event using nak directly to relay
|
# Send the event through persistent WebSocket connection
|
||||||
log_info "Sending clear all auth rules command..."
|
log_info "Sending clear all auth rules command..."
|
||||||
local result
|
local result
|
||||||
result=$(echo "$event_json" | timeout 10s nak event "$RELAY_URL" 2>&1)
|
if [ "$WS_CONNECTED" = "1" ]; then
|
||||||
local exit_code=$?
|
result=$(send_websocket_event "$event_json")
|
||||||
|
local exit_code=$?
|
||||||
log_info "Clear auth rules result: $result"
|
|
||||||
|
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
|
# Check if response indicates success
|
||||||
log_success "All auth rules cleared successfully"
|
if [ $exit_code -eq 0 ] && echo "$result" | grep -q -i '"OK".*true'; then
|
||||||
return 0
|
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
|
||||||
else
|
else
|
||||||
log_error "Failed to clear auth rules: $result (exit code: $exit_code)"
|
# Fallback to one-shot connection if persistent connection not available
|
||||||
return 1
|
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
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,11 +506,11 @@ test_admin_authentication() {
|
|||||||
log "Test $TESTS_RUN: Admin Authentication"
|
log "Test $TESTS_RUN: Admin Authentication"
|
||||||
|
|
||||||
# Create a simple configuration event to test admin authentication
|
# Create a simple configuration event to test admin authentication
|
||||||
# Using Kind 23455 (ephemeral configuration management) - no d tag needed
|
# Using Kind 23456 (admin commands) with proper relay targeting
|
||||||
local content="{\"action\":\"set\",\"description\":\"Testing admin authentication\"}"
|
|
||||||
local config_event
|
local config_event
|
||||||
config_event=$(nak event -k 23455 --content "$content" \
|
config_event=$(nak event -k 23456 --content "" \
|
||||||
-t "test_auth=true" \
|
-t "p=$RELAY_PUBKEY" \
|
||||||
|
-t "system_command=system_status" \
|
||||||
--sec "$ADMIN_PRIVKEY" 2>/dev/null)
|
--sec "$ADMIN_PRIVKEY" 2>/dev/null)
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
@@ -367,8 +549,9 @@ test_auth_rules_storage_query() {
|
|||||||
# Query all auth rules using admin query
|
# Query all auth rules using admin query
|
||||||
log_info "Querying all auth rules..."
|
log_info "Querying all auth rules..."
|
||||||
local query_event
|
local query_event
|
||||||
query_event=$(nak event -k 23456 --content "{\"action\":\"list_all\"}" \
|
query_event=$(nak event -k 23456 --content "" \
|
||||||
-t "auth_query=list_all" \
|
-t "p=$RELAY_PUBKEY" \
|
||||||
|
-t "auth_query=all" \
|
||||||
--sec "$ADMIN_PRIVKEY" 2>/dev/null)
|
--sec "$ADMIN_PRIVKEY" 2>/dev/null)
|
||||||
|
|
||||||
if [ $? -ne 0 ] || [ -z "$query_event" ]; then
|
if [ $? -ne 0 ] || [ -z "$query_event" ]; then
|
||||||
@@ -697,11 +880,11 @@ run_all_tests() {
|
|||||||
# Setup
|
# Setup
|
||||||
setup_test_environment
|
setup_test_environment
|
||||||
|
|
||||||
# Clear all auth rules before starting tests
|
|
||||||
clear_all_auth_rules
|
clear_all_auth_rules
|
||||||
|
|
||||||
# test_admin_authentication
|
test_admin_authentication
|
||||||
# test_auth_rules_storage_query
|
test_auth_rules_storage_query
|
||||||
# test_basic_whitelist
|
# test_basic_whitelist
|
||||||
# test_basic_blacklist
|
# test_basic_blacklist
|
||||||
# test_rule_removal
|
# test_rule_removal
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
=== C-Relay Whitelist/Blacklist Test Started at Thu Sep 25 07:28:40 AM EDT 2025 ===
|
|
||||||
[0;34m[INFO][0m Checking dependencies...
|
|
||||||
[0;32m[SUCCESS][0m Dependencies check complete
|
|
||||||
[0;34m[INFO][0m Generated keypair for TEST1: pubkey=36e6521000b2ddda...
|
|
||||||
[0;34m[INFO][0m Generated keypair for TEST2: pubkey=9cdd32f27fffeea8...
|
|
||||||
[0;34m[INFO][0m Generated keypair for TEST3: pubkey=e05928b64d3ad54a...
|
|
||||||
[0;32m[SUCCESS][0m Test environment setup complete
|
|
||||||
[0;34m[07:28:42][0m Test 1: Admin Authentication
|
|
||||||
[0;34m[INFO][0m === DEBUG: Full admin event being sent ===
|
|
||||||
[0;34m[INFO][0m === END DEBUG EVENT ===
|
|
||||||
[0;34m[INFO][0m === DEBUG: Full WebSocket message ===
|
|
||||||
[0;34m[INFO][0m === END DEBUG MESSAGE ===
|
|
||||||
[0;34m[INFO][0m Sending WebSocket message (full):
|
|
||||||
[0;34m[INFO][0m ["EVENT",{"kind":33334,"id":"ba07a9d01ef3bf8c424eb5ecd8a162980e5d596f3c8520ea59c4cf80961a347a","pubkey":"6a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3","created_at":1758799722,"tags":[["d","4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa"],["test_auth","true"]],"content":"Testing admin authentication","sig":"b8f40558060f402da232f3cc2ff72ea98257a3e3d74bc5c4bebd6fbd24d8d258138f879795b6089c01c4afa4c64088306dc917fdcad7b054d3c12513581b9228"}]
|
|
||||||
[0;34m[INFO][0m WebSocket response (full):
|
|
||||||
[0;34m[INFO][0m
|
|
||||||
[0;34m[INFO][0m === DEBUG: Full server response ===
|
|
||||||
[0;34m[INFO][0m === END DEBUG RESPONSE ===
|
|
||||||
[0;31m[ERROR][0m Test 1: FAILED - Admin authentication failed: [0;34m[INFO][0m Sending WebSocket message (full):
|
|
||||||
[0;34m[INFO][0m ["EVENT",{"kind":33334,"id":"ba07a9d01ef3bf8c424eb5ecd8a162980e5d596f3c8520ea59c4cf80961a347a","pubkey":"6a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3","created_at":1758799722,"tags":[["d","4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa"],["test_auth","true"]],"content":"Testing admin authentication","sig":"b8f40558060f402da232f3cc2ff72ea98257a3e3d74bc5c4bebd6fbd24d8d258138f879795b6089c01c4afa4c64088306dc917fdcad7b054d3c12513581b9228"}]
|
|
||||||
[0;34m[INFO][0m WebSocket response (full):
|
|
||||||
[0;34m[INFO][0m
|
|
||||||
[0;31m[ERROR][0m 1 out of 1 tests failed.
|
|
||||||
[0;31m[ERROR][0m Some tests failed. Check the log for details.
|
|
||||||
[0;34m[07:28:42][0m Cleaning up test environment...
|
|
||||||
[0;34m[INFO][0m Temporary directory removed: /tmp/c_relay_test_184904
|
|
||||||
[0;34m[07:28:42][0m Test cleanup completed.
|
|
||||||
Reference in New Issue
Block a user