Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c23d81b740 | ||
|
|
6dac231040 | ||
|
|
6fd3e531c3 | ||
|
|
c1c05991cf | ||
|
|
ab378e14d1 | ||
|
|
c0f9bf9ef5 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,3 +9,4 @@ dev-config/
|
|||||||
db/
|
db/
|
||||||
copy_executable_local.sh
|
copy_executable_local.sh
|
||||||
nostr_login_lite/
|
nostr_login_lite/
|
||||||
|
style_guide/
|
||||||
28
AGENTS.md
28
AGENTS.md
@@ -27,7 +27,7 @@
|
|||||||
## Critical Integration Issues
|
## Critical Integration Issues
|
||||||
|
|
||||||
### Event-Based Configuration System
|
### Event-Based Configuration System
|
||||||
- **No traditional config files** - all configuration stored as kind 33334 Nostr events
|
- **No traditional config files** - all configuration stored in config table
|
||||||
- Admin private key shown **only once** on first startup
|
- Admin private key shown **only once** on first startup
|
||||||
- Configuration changes require cryptographically signed events
|
- Configuration changes require cryptographically signed events
|
||||||
- Database path determined by generated relay pubkey
|
- Database path determined by generated relay pubkey
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
### First-Time Startup Sequence
|
### First-Time Startup Sequence
|
||||||
1. Relay generates admin keypair and relay keypair
|
1. Relay generates admin keypair and relay keypair
|
||||||
2. Creates database file with relay pubkey as filename
|
2. Creates database file with relay pubkey as filename
|
||||||
3. Stores default configuration as kind 33334 event
|
3. Stores default configuration in config table
|
||||||
4. **CRITICAL**: Admin private key displayed once and never stored on disk
|
4. **CRITICAL**: Admin private key displayed once and never stored on disk
|
||||||
|
|
||||||
### Port Management
|
### Port Management
|
||||||
@@ -48,20 +48,30 @@
|
|||||||
- Schema version 4 with JSON tag storage
|
- Schema version 4 with JSON tag storage
|
||||||
- **Critical**: Event expiration filtering done at application level, not SQL level
|
- **Critical**: Event expiration filtering done at application level, not SQL level
|
||||||
|
|
||||||
### Configuration Event Structure
|
### Admin API Event Structure
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"kind": 33334,
|
"kind": 23456,
|
||||||
"content": "C Nostr Relay Configuration",
|
"content": "base64_nip44_encrypted_command_array",
|
||||||
"tags": [
|
"tags": [
|
||||||
["d", "<relay_pubkey>"],
|
["p", "<relay_pubkey>"]
|
||||||
["relay_description", "value"],
|
|
||||||
["max_subscriptions_per_client", "25"],
|
|
||||||
["pow_min_difficulty", "16"]
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Configuration Commands** (encrypted in content):
|
||||||
|
- `["relay_description", "My Relay"]`
|
||||||
|
- `["max_subscriptions_per_client", "25"]`
|
||||||
|
- `["pow_min_difficulty", "16"]`
|
||||||
|
|
||||||
|
**Auth Rule Commands** (encrypted in content):
|
||||||
|
- `["blacklist", "pubkey", "hex_pubkey_value"]`
|
||||||
|
- `["whitelist", "pubkey", "hex_pubkey_value"]`
|
||||||
|
|
||||||
|
**Query Commands** (encrypted in content):
|
||||||
|
- `["auth_query", "all"]`
|
||||||
|
- `["system_command", "system_status"]`
|
||||||
|
|
||||||
### Process Management
|
### Process Management
|
||||||
```bash
|
```bash
|
||||||
# Kill existing relay processes
|
# Kill existing relay processes
|
||||||
|
|||||||
513
IMPLEMENT_API.md
513
IMPLEMENT_API.md
@@ -1,513 +0,0 @@
|
|||||||
# 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.
|
|
||||||
62
README.md
62
README.md
@@ -24,7 +24,7 @@ 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. All admin commands use **tag-based parameters** for simplicity and compatibility.
|
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 **NIP-44 encrypted command arrays** for security and compatibility.
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ All admin commands require signing with the admin private key displayed during f
|
|||||||
|
|
||||||
### Event Structure
|
### Event Structure
|
||||||
|
|
||||||
All admin commands use the same unified event structure with tag-based parameters:
|
All admin commands use the same unified event structure with NIP-44 encrypted content:
|
||||||
|
|
||||||
**Admin Command Event:**
|
**Admin Command Event:**
|
||||||
```json
|
```json
|
||||||
@@ -41,14 +41,16 @@ All admin commands use the same unified event structure with tag-based parameter
|
|||||||
"pubkey": "admin_public_key",
|
"pubkey": "admin_public_key",
|
||||||
"created_at": 1234567890,
|
"created_at": 1234567890,
|
||||||
"kind": 23456,
|
"kind": 23456,
|
||||||
"content": "<nip44 encrypted command>",
|
"content": "AqHBUgcM7dXFYLQuDVzGwMST1G8jtWYyVvYxXhVGEu4nAb4LVw...",
|
||||||
"tags": [
|
"tags": [
|
||||||
["p", "relay_public_key"],
|
["p", "relay_public_key"]
|
||||||
],
|
],
|
||||||
"sig": "event_signature"
|
"sig": "event_signature"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `content` field contains a NIP-44 encrypted JSON array representing the command.
|
||||||
|
|
||||||
**Admin Response Event:**
|
**Admin Response Event:**
|
||||||
```json
|
```json
|
||||||
["EVENT", "temp_sub_id", {
|
["EVENT", "temp_sub_id", {
|
||||||
@@ -56,7 +58,7 @@ All admin commands use the same unified event structure with tag-based parameter
|
|||||||
"pubkey": "relay_public_key",
|
"pubkey": "relay_public_key",
|
||||||
"created_at": 1234567890,
|
"created_at": 1234567890,
|
||||||
"kind": 23457,
|
"kind": 23457,
|
||||||
"content": "<nip44 encrypted response>",
|
"content": "BpKCVhfN8eYtRmPqSvWxZnMkL2gHjUiOp3rTyEwQaS5dFg...",
|
||||||
"tags": [
|
"tags": [
|
||||||
["p", "admin_public_key"]
|
["p", "admin_public_key"]
|
||||||
],
|
],
|
||||||
@@ -64,18 +66,21 @@ All admin commands use the same unified event structure with tag-based parameter
|
|||||||
}]
|
}]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `content` field contains a NIP-44 encrypted JSON response object.
|
||||||
|
|
||||||
### Admin Commands
|
### Admin Commands
|
||||||
|
|
||||||
All commands are sent as nip44 encrypted content. The following table lists all available commands:
|
All commands are sent as NIP-44 encrypted JSON arrays in the event content. The following table lists all available commands:
|
||||||
|
|
||||||
| Command Type | Tag Format | Description |
|
| Command Type | Command Format | Description |
|
||||||
|--------------|------------|-------------|
|
|--------------|----------------|-------------|
|
||||||
| **Configuration Management** |
|
| **Configuration Management** |
|
||||||
| `config_update` | `["relay_description", "My Relay"]` | Update relay configuration parameters |
|
| `config_update` | `["config_update", [{"key": "auth_enabled", "value": "true", "data_type": "boolean", "category": "auth"}, {"key": "relay_description", "value": "My Relay", "data_type": "string", "category": "relay"}, ...]]` | Update relay configuration parameters (supports multiple updates) |
|
||||||
| `config_query` | `["config_query", "list_all_keys"]` | List all available configuration keys |
|
| `config_query` | `["config_query", "all"]` | Query all configuration parameters |
|
||||||
| **Auth Rules Management** |
|
| **Auth Rules Management** |
|
||||||
| `auth_add_blacklist` | `["blacklist", "pubkey", "abc123..."]` | Add pubkey to blacklist |
|
| `auth_add_blacklist` | `["blacklist", "pubkey", "abc123..."]` | Add pubkey to blacklist |
|
||||||
| `auth_add_whitelist` | `["whitelist", "pubkey", "def456..."]` | Add pubkey to whitelist |
|
| `auth_add_whitelist` | `["whitelist", "pubkey", "def456..."]` | Add pubkey to whitelist |
|
||||||
|
| `auth_delete_rule` | `["delete_auth_rule", "blacklist", "pubkey", "abc123..."]` | Delete specific auth rule |
|
||||||
| `auth_query_all` | `["auth_query", "all"]` | Query all auth rules |
|
| `auth_query_all` | `["auth_query", "all"]` | Query all auth rules |
|
||||||
| `auth_query_type` | `["auth_query", "whitelist"]` | Query specific rule type |
|
| `auth_query_type` | `["auth_query", "whitelist"]` | Query specific rule type |
|
||||||
| `auth_query_pattern` | `["auth_query", "pattern", "abc123..."]` | Query specific pattern |
|
| `auth_query_pattern` | `["auth_query", "pattern", "abc123..."]` | Query specific pattern |
|
||||||
@@ -116,7 +121,7 @@ All admin commands return **signed EVENT responses** via WebSocket following sta
|
|||||||
"pubkey": "relay_public_key",
|
"pubkey": "relay_public_key",
|
||||||
"created_at": 1234567890,
|
"created_at": 1234567890,
|
||||||
"kind": 23457,
|
"kind": 23457,
|
||||||
"content": "nip44 encrypted:{\"status\": \"success\", \"message\": \"Operation completed successfully\"}",
|
"content": "nip44 encrypted:{\"query_type\": \"config_update\", \"status\": \"success\", \"message\": \"Operation completed successfully\", \"timestamp\": 1234567890}",
|
||||||
"tags": [
|
"tags": [
|
||||||
["p", "admin_public_key"]
|
["p", "admin_public_key"]
|
||||||
],
|
],
|
||||||
@@ -131,7 +136,7 @@ All admin commands return **signed EVENT responses** via WebSocket following sta
|
|||||||
"pubkey": "relay_public_key",
|
"pubkey": "relay_public_key",
|
||||||
"created_at": 1234567890,
|
"created_at": 1234567890,
|
||||||
"kind": 23457,
|
"kind": 23457,
|
||||||
"content": "nip44 encrypted:{\"status\": \"error\", \"message\": \"Error: invalid configuration value\"}",
|
"content": "nip44 encrypted:{\"query_type\": \"config_update\", \"status\": \"error\", \"error\": \"invalid configuration value\", \"timestamp\": 1234567890}",
|
||||||
"tags": [
|
"tags": [
|
||||||
["p", "admin_public_key"]
|
["p", "admin_public_key"]
|
||||||
],
|
],
|
||||||
@@ -146,7 +151,7 @@ All admin commands return **signed EVENT responses** via WebSocket following sta
|
|||||||
"pubkey": "relay_public_key",
|
"pubkey": "relay_public_key",
|
||||||
"created_at": 1234567890,
|
"created_at": 1234567890,
|
||||||
"kind": 23457,
|
"kind": 23457,
|
||||||
"content": "nip44 encrypted:{\"query_type\": \"auth_rules\", \"total_results\": 2, \"data\": [{\"rule_type\": \"blacklist\", \"pattern_type\": \"pubkey\", \"pattern_value\": \"abc123...\", \"action\": \"deny\"}]}",
|
"content": "nip44 encrypted:{\"query_type\": \"auth_rules_all\", \"total_results\": 2, \"timestamp\": 1234567890, \"data\": [{\"rule_type\": \"blacklist\", \"pattern_type\": \"pubkey\", \"pattern_value\": \"abc123...\", \"action\": \"allow\"}]}",
|
||||||
"tags": [
|
"tags": [
|
||||||
["p", "admin_public_key"]
|
["p", "admin_public_key"]
|
||||||
],
|
],
|
||||||
@@ -161,7 +166,7 @@ All admin commands return **signed EVENT responses** via WebSocket following sta
|
|||||||
"pubkey": "relay_public_key",
|
"pubkey": "relay_public_key",
|
||||||
"created_at": 1234567890,
|
"created_at": 1234567890,
|
||||||
"kind": 23457,
|
"kind": 23457,
|
||||||
"content": "nip44 encrypted:{\"query_type\": \"config_keys\", \"config_keys\": [\"auth_enabled\", \"max_connections\"], \"descriptions\": {\"auth_enabled\": \"Enable whitelist/blacklist rules\"}}",
|
"content": "nip44 encrypted:{\"query_type\": \"config_all\", \"total_results\": 27, \"timestamp\": 1234567890, \"data\": [{\"key\": \"auth_enabled\", \"value\": \"false\", \"data_type\": \"boolean\", \"category\": \"auth\", \"description\": \"Enable NIP-42 authentication\"}, {\"key\": \"relay_description\", \"value\": \"My Relay\", \"data_type\": \"string\", \"category\": \"relay\", \"description\": \"Relay description text\"}]}",
|
||||||
"tags": [
|
"tags": [
|
||||||
["p", "admin_public_key"]
|
["p", "admin_public_key"]
|
||||||
],
|
],
|
||||||
@@ -169,3 +174,32 @@ All admin commands return **signed EVENT responses** via WebSocket following sta
|
|||||||
}]
|
}]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Configuration Update Success 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_update\", \"total_results\": 2, \"timestamp\": 1234567890, \"status\": \"success\", \"data\": [{\"key\": \"auth_enabled\", \"value\": \"true\", \"status\": \"updated\"}, {\"key\": \"relay_description\", \"value\": \"My Updated Relay\", \"status\": \"updated\"}]}",
|
||||||
|
"tags": [
|
||||||
|
["p", "admin_public_key"]
|
||||||
|
],
|
||||||
|
"sig": "response_event_signature"
|
||||||
|
}]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Configuration Update Error 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_update\", \"status\": \"error\", \"error\": \"field validation failed: invalid port number '99999' (must be 1-65535)\", \"timestamp\": 1234567890}",
|
||||||
|
"tags": [
|
||||||
|
["p", "admin_public_key"]
|
||||||
|
],
|
||||||
|
"sig": "response_event_signature"
|
||||||
|
}]
|
||||||
|
```
|
||||||
|
|||||||
134
api/button.html
Normal file
134
api/button.html
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Embedded NOSTR_LOGIN_LITE</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 40px;
|
||||||
|
background: white;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 90vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 400px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login-button {
|
||||||
|
background: #0066cc;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login-button:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div id="login-button">Login</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../lite/nostr.bundle.js"></script>
|
||||||
|
<script src="../lite/nostr-lite.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let isAuthenticated = false;
|
||||||
|
let currentUser = null;
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
await window.NOSTR_LOGIN_LITE.init({
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
extension: true,
|
||||||
|
local: true,
|
||||||
|
readonly: true,
|
||||||
|
connect: true,
|
||||||
|
remote: true,
|
||||||
|
otp: true
|
||||||
|
},
|
||||||
|
floatingTab: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for authentication events
|
||||||
|
window.addEventListener('nlMethodSelected', handleAuthEvent);
|
||||||
|
window.addEventListener('nlLogout', handleLogoutEvent);
|
||||||
|
|
||||||
|
// Check for existing authentication state
|
||||||
|
checkAuthState();
|
||||||
|
|
||||||
|
// Initialize button
|
||||||
|
updateButtonState();
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleAuthEvent(event) {
|
||||||
|
const { pubkey, method } = event.detail;
|
||||||
|
console.log(`Authenticated with ${method}, pubkey: ${pubkey}`);
|
||||||
|
|
||||||
|
isAuthenticated = true;
|
||||||
|
currentUser = event.detail;
|
||||||
|
updateButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLogoutEvent() {
|
||||||
|
console.log('Logout event received');
|
||||||
|
|
||||||
|
isAuthenticated = false;
|
||||||
|
currentUser = null;
|
||||||
|
updateButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAuthState() {
|
||||||
|
// Check if user is already authenticated (from persistent storage)
|
||||||
|
try {
|
||||||
|
// Try to get public key - this will work if already authenticated
|
||||||
|
window.nostr.getPublicKey().then(pubkey => {
|
||||||
|
console.log('Found existing authentication, pubkey:', pubkey);
|
||||||
|
isAuthenticated = true;
|
||||||
|
currentUser = { pubkey, method: 'persistent' };
|
||||||
|
updateButtonState();
|
||||||
|
}).catch(error => {
|
||||||
|
console.log('No existing authentication found:', error.message);
|
||||||
|
// User is not authenticated, button stays in login state
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log('No existing authentication found');
|
||||||
|
// User is not authenticated, button stays in login state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateButtonState() {
|
||||||
|
const button = document.getElementById('login-button');
|
||||||
|
|
||||||
|
if (isAuthenticated) {
|
||||||
|
button.textContent = 'Logout';
|
||||||
|
button.onclick = () => window.NOSTR_LOGIN_LITE.logout();
|
||||||
|
button.style.background = '#dc3545'; // Red for logout
|
||||||
|
} else {
|
||||||
|
button.textContent = 'Login';
|
||||||
|
button.onclick = () => window.NOSTR_LOGIN_LITE.launch('login');
|
||||||
|
button.style.background = '#0066cc'; // Blue for login
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
3015
api/index.html
3015
api/index.html
File diff suppressed because it is too large
Load Diff
@@ -36,122 +36,70 @@ CREATE TABLE auth_rules (
|
|||||||
|
|
||||||
#### Admin API Commands (via WebSocket with admin private key)
|
#### Admin API Commands (via WebSocket with admin private key)
|
||||||
|
|
||||||
**Kind 23455: Configuration Management (Ephemeral)**
|
**Kind 23456: Unified Admin API (Ephemeral)**
|
||||||
- Update relay settings, limits, authentication policies
|
- Configuration management: Update relay settings, limits, authentication policies
|
||||||
- **Standard Mode**: Commands in tags `["config_key", "config_value"]`
|
|
||||||
- **Encrypted Mode**: Commands NIP-44 encrypted in content `{"encrypted_tags": "..."}`
|
|
||||||
- Content: Descriptive text or encrypted payload
|
|
||||||
- Security: Optional NIP-44 encryption for sensitive operations
|
|
||||||
|
|
||||||
**Kind 23456: Auth Rules & System Management (Ephemeral)**
|
|
||||||
- Auth rules: Add/remove/query whitelist/blacklist rules
|
- Auth rules: Add/remove/query whitelist/blacklist rules
|
||||||
- System commands: clear rules, status, cache management
|
- System commands: clear rules, status, cache management
|
||||||
- **Standard Mode**: Commands in tags
|
- **Unified Format**: All commands use NIP-44 encrypted content with `["p", "relay_pubkey"]` tags
|
||||||
- Rule format: `["rule_type", "pattern_type", "pattern_value"]`
|
- **Command Types**:
|
||||||
- Query format: `["auth_query", "filter"]`
|
- Configuration: `["config_key", "config_value"]`
|
||||||
- System format: `["system_command", "command_name"]`
|
- Auth rules: `["rule_type", "pattern_type", "pattern_value"]`
|
||||||
- **Encrypted Mode**: Commands NIP-44 encrypted in content `{"encrypted_tags": "..."}`
|
- Queries: `["auth_query", "filter"]` or `["system_command", "command_name"]`
|
||||||
- Content: Action description + optional encrypted payload
|
- **Security**: All admin commands use NIP-44 encryption for privacy and security
|
||||||
- Security: Optional NIP-44 encryption for sensitive operations
|
|
||||||
|
|
||||||
#### Configuration Query Commands (using Kind 23455)
|
#### Configuration Commands (using Kind 23456)
|
||||||
|
|
||||||
1. **List All Configuration Keys (Standard)**:
|
1. **Update Configuration**:
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 23455,
|
|
||||||
"content": "Discovery query",
|
|
||||||
"tags": [["config_query", "list_all_keys"]]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **List All Configuration Keys (Encrypted)**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 23455,
|
|
||||||
"content": "{\"query\":\"list_config_keys\",\"encrypted_tags\":\"nip44_encrypted_payload\"}",
|
|
||||||
"tags": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
*Encrypted payload contains:* `[["config_query", "list_all_keys"]]`
|
|
||||||
|
|
||||||
3. **Get Current Configuration (Standard)**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 23455,
|
|
||||||
"content": "Config query",
|
|
||||||
"tags": [["config_query", "get_current_config"]]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Get Current Configuration (Encrypted)**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 23455,
|
|
||||||
"content": "{\"query\":\"get_config\",\"encrypted_tags\":\"nip44_encrypted_payload\"}",
|
|
||||||
"tags": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
*Encrypted payload contains:* `[["config_query", "get_current_config"]]`
|
|
||||||
|
|
||||||
#### System Management Commands (using Kind 23456)
|
|
||||||
|
|
||||||
1. **Clear All Auth Rules (Standard)**:
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"kind": 23456,
|
"kind": 23456,
|
||||||
"content": "{\"action\":\"clear_all\"}",
|
"content": "base64_nip44_encrypted_command_array",
|
||||||
"tags": [["system_command", "clear_all_auth_rules"]]
|
"tags": [["p", "relay_pubkey"]]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
*Encrypted content contains:* `["relay_description", "My Relay"]`
|
||||||
|
|
||||||
2. **Clear All Auth Rules (Encrypted)**:
|
2. **Query System Status**:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"kind": 23456,
|
"kind": 23456,
|
||||||
"content": "{\"action\":\"clear_all\",\"encrypted_tags\":\"nip44_encrypted_payload\"}",
|
"content": "base64_nip44_encrypted_command_array",
|
||||||
"tags": []
|
"tags": [["p", "relay_pubkey"]]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
*Encrypted payload contains:* `[["system_command", "clear_all_auth_rules"]]`
|
*Encrypted content contains:* `["system_command", "system_status"]`
|
||||||
|
|
||||||
3. **Query All Auth Rules (Standard)**:
|
#### Auth Rules and System Commands (using Kind 23456)
|
||||||
|
|
||||||
|
1. **Clear All Auth Rules**:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"kind": 23456,
|
"kind": 23456,
|
||||||
"content": "{\"query\":\"list_auth_rules\"}",
|
"content": "base64_nip44_encrypted_command_array",
|
||||||
"tags": [["auth_query", "all"]]
|
"tags": [["p", "relay_pubkey"]]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
*Encrypted content contains:* `["system_command", "clear_all_auth_rules"]`
|
||||||
|
|
||||||
4. **Query All Auth Rules (Encrypted)**:
|
2. **Query All Auth Rules**:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"kind": 23456,
|
"kind": 23456,
|
||||||
"content": "{\"query\":\"list_auth_rules\",\"encrypted_tags\":\"nip44_encrypted_payload\"}",
|
"content": "base64_nip44_encrypted_command_array",
|
||||||
"tags": []
|
"tags": [["p", "relay_pubkey"]]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
*Encrypted payload contains:* `[["auth_query", "all"]]`
|
*Encrypted content contains:* `["auth_query", "all"]`
|
||||||
|
|
||||||
5. **Add Blacklist Rule (Standard)**:
|
3. **Add Blacklist Rule**:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"kind": 23456,
|
"kind": 23456,
|
||||||
"content": "{\"action\":\"add\"}",
|
"content": "base64_nip44_encrypted_command_array",
|
||||||
"tags": [["blacklist", "pubkey", "deadbeef1234abcd..."]]
|
"tags": [["p", "relay_pubkey"]]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
*Encrypted content contains:* `["blacklist", "pubkey", "deadbeef1234abcd..."]`
|
||||||
6. **Add Blacklist Rule (Encrypted)**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 23456,
|
|
||||||
"content": "{\"action\":\"add\",\"encrypted_tags\":\"nip44_encrypted_payload\"}",
|
|
||||||
"tags": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
*Encrypted payload contains:* `[["blacklist", "pubkey", "deadbeef1234abcd..."]]`
|
|
||||||
|
|
||||||
### Phase 2: Auth Rules Schema Alignment
|
### Phase 2: Auth Rules Schema Alignment
|
||||||
|
|
||||||
@@ -181,12 +129,12 @@ Would require changing schema, migration scripts, and storage logic.
|
|||||||
#### High Priority (Critical for blacklist functionality):
|
#### High Priority (Critical for blacklist functionality):
|
||||||
1. Fix request_validator.c schema mismatch
|
1. Fix request_validator.c schema mismatch
|
||||||
2. Ensure auth_required configuration is enabled
|
2. Ensure auth_required configuration is enabled
|
||||||
3. Update tests to use ephemeral event kinds (23455/23456)
|
3. Update tests to use unified ephemeral event kind (23456)
|
||||||
4. Test blacklist enforcement
|
4. Test blacklist enforcement
|
||||||
|
|
||||||
#### Medium Priority (Enhanced Admin Features):
|
#### Medium Priority (Enhanced Admin Features):
|
||||||
1. **Implement NIP-44 Encryption Support**:
|
1. **Implement NIP-44 Encryption Support**:
|
||||||
- Detect empty tags array for Kind 23455/23456 events
|
- Detect NIP-44 encrypted content for Kind 23456 events
|
||||||
- Parse `encrypted_tags` field from content JSON
|
- Parse `encrypted_tags` field from content JSON
|
||||||
- Decrypt using admin privkey and relay pubkey
|
- Decrypt using admin privkey and relay pubkey
|
||||||
- Process decrypted tags as normal commands
|
- Process decrypted tags as normal commands
|
||||||
@@ -218,45 +166,20 @@ Would require changing schema, migration scripts, and storage logic.
|
|||||||
## Authentication
|
## Authentication
|
||||||
All admin commands require signing with the admin private key generated during first startup.
|
All admin commands require signing with the admin private key generated during first startup.
|
||||||
|
|
||||||
## Configuration Management (Kind 23455 - Ephemeral)
|
## Unified Admin API (Kind 23456 - Ephemeral)
|
||||||
Update relay configuration parameters or query available settings.
|
Update relay configuration parameters or query available settings.
|
||||||
|
|
||||||
**Configuration Update Event:**
|
**Configuration Update Event:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"kind": 23455,
|
"kind": 23456,
|
||||||
"content": "Configuration update",
|
"content": "base64_nip44_encrypted_command_array",
|
||||||
"tags": [
|
"tags": [["p", "relay_pubkey"]]
|
||||||
["config_key1", "config_value1"],
|
|
||||||
["config_key2", "config_value2"]
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
*Encrypted content contains:* `["relay_description", "My Relay Description"]`
|
||||||
|
|
||||||
**List Available Config Keys:**
|
**Auth Rules Management:**
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 23455,
|
|
||||||
"content": "{\"query\":\"list_config_keys\",\"description\":\"Get editable config keys\"}",
|
|
||||||
"tags": [
|
|
||||||
["config_query", "list_all_keys"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Get Current Configuration:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 23455,
|
|
||||||
"content": "{\"query\":\"get_config\",\"description\":\"Get current config values\"}",
|
|
||||||
"tags": [
|
|
||||||
["config_query", "get_current_config"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Auth Rules Management (Kind 23456 - Ephemeral)
|
|
||||||
Manage whitelist and blacklist rules.
|
|
||||||
|
|
||||||
**Add Rule Event:**
|
**Add Rule Event:**
|
||||||
```json
|
```json
|
||||||
@@ -364,7 +287,7 @@ All admin commands return JSON responses via WebSocket:
|
|||||||
### Enable Authentication & Add Blacklist
|
### Enable Authentication & Add Blacklist
|
||||||
```bash
|
```bash
|
||||||
# 1. Enable auth system
|
# 1. Enable auth system
|
||||||
nak event -k 23455 --content "Enable authentication" \
|
nak event -k 23456 --content "base64_nip44_encrypted_command" \
|
||||||
-t "auth_enabled=true" \
|
-t "auth_enabled=true" \
|
||||||
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
||||||
|
|
||||||
@@ -389,18 +312,18 @@ nak event -k 23456 --content '{"action":"clear_all","description":"Clear all rul
|
|||||||
### Configuration Query Response
|
### Configuration Query Response
|
||||||
```json
|
```json
|
||||||
["EVENT", "subscription_id", {
|
["EVENT", "subscription_id", {
|
||||||
"kind": 23455,
|
"kind": 23457,
|
||||||
"content": "{\"config_keys\": [\"auth_enabled\", \"max_connections\"], \"descriptions\": {\"auth_enabled\": \"Enable whitelist/blacklist rules\"}}",
|
"content": "base64_nip44_encrypted_response",
|
||||||
"tags": [["response_type", "config_keys_list"]]
|
"tags": [["p", "admin_pubkey"]]
|
||||||
}]
|
}]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Current Config Response
|
### Current Config Response
|
||||||
```json
|
```json
|
||||||
["EVENT", "subscription_id", {
|
["EVENT", "subscription_id", {
|
||||||
"kind": 23455,
|
"kind": 23457,
|
||||||
"content": "{\"current_config\": {\"auth_enabled\": \"true\", \"max_connections\": \"1000\"}}",
|
"content": "base64_nip44_encrypted_response",
|
||||||
"tags": [["response_type", "current_config"]]
|
"tags": [["p", "admin_pubkey"]]
|
||||||
}]
|
}]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -427,7 +350,7 @@ nak event -k 23456 --content '{"action":"clear_all","description":"Clear all rul
|
|||||||
1. **Document API** (this file) ✅
|
1. **Document API** (this file) ✅
|
||||||
2. **Update to ephemeral event kinds** ✅
|
2. **Update to ephemeral event kinds** ✅
|
||||||
3. **Fix request_validator.c** schema mismatch
|
3. **Fix request_validator.c** schema mismatch
|
||||||
4. **Update tests** to use Kind 23455/23456
|
4. **Update tests** to use unified Kind 23456
|
||||||
5. **Add auth rule query functionality**
|
5. **Add auth rule query functionality**
|
||||||
6. **Add configuration discovery feature**
|
6. **Add configuration discovery feature**
|
||||||
7. **Test blacklist functionality**
|
7. **Test blacklist functionality**
|
||||||
@@ -449,8 +372,8 @@ This plan addresses the immediate blacklist issue while establishing a comprehen
|
|||||||
```c
|
```c
|
||||||
// In admin event processing function
|
// In admin event processing function
|
||||||
bool is_encrypted_command(struct nostr_event *event) {
|
bool is_encrypted_command(struct nostr_event *event) {
|
||||||
// Check if Kind 23455 or 23456 with empty tags
|
// Check if Kind 23456 with NIP-44 encrypted content
|
||||||
if ((event->kind == 23455 || event->kind == 23456) &&
|
if (event->kind == 23456 &&
|
||||||
event->tags_count == 0) {
|
event->tags_count == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -483,7 +406,7 @@ cJSON *decrypt_admin_tags(struct nostr_event *event) {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Admin Event Processing Flow
|
### Admin Event Processing Flow
|
||||||
1. **Receive Event**: Kind 23455/23456 with admin signature
|
1. **Receive Event**: Kind 23456 with admin signature
|
||||||
2. **Check Mode**: Empty tags = encrypted, populated tags = standard
|
2. **Check Mode**: Empty tags = encrypted, populated tags = standard
|
||||||
3. **Decrypt if Needed**: Extract and decrypt `encrypted_tags` from content
|
3. **Decrypt if Needed**: Extract and decrypt `encrypted_tags` from content
|
||||||
4. **Process Commands**: Use decrypted/standard tags for command processing
|
4. **Process Commands**: Use decrypted/standard tags for command processing
|
||||||
@@ -510,7 +433,7 @@ char* nip44_decrypt(const char* ciphertext, const char* recipient_privkey, const
|
|||||||
|
|
||||||
#### Phase 1: Core Infrastructure (Complete)
|
#### Phase 1: Core Infrastructure (Complete)
|
||||||
- [x] Event-based admin authentication system
|
- [x] Event-based admin authentication system
|
||||||
- [x] Kind 23455/23456 (Configuration/Auth Rules) processing
|
- [x] Kind 23456 (Unified Admin API) processing
|
||||||
- [x] Basic configuration parameter updates
|
- [x] Basic configuration parameter updates
|
||||||
- [x] Auth rule add/remove/clear functionality
|
- [x] Auth rule add/remove/clear functionality
|
||||||
- [x] Updated to ephemeral event kinds
|
- [x] Updated to ephemeral event kinds
|
||||||
|
|||||||
@@ -282,14 +282,14 @@ cd build
|
|||||||
# Start relay in background and capture its PID
|
# Start relay in background and capture its PID
|
||||||
if [ "$USE_TEST_KEYS" = true ]; then
|
if [ "$USE_TEST_KEYS" = true ]; then
|
||||||
echo "Using deterministic test keys for development..."
|
echo "Using deterministic test keys for development..."
|
||||||
./$(basename $BINARY_PATH) -a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -r 1111111111111111111111111111111111111111111111111111111111111111 > ../relay.log 2>&1 &
|
./$(basename $BINARY_PATH) -a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -r 1111111111111111111111111111111111111111111111111111111111111111 --strict-port > ../relay.log 2>&1 &
|
||||||
elif [ -n "$RELAY_ARGS" ]; then
|
elif [ -n "$RELAY_ARGS" ]; then
|
||||||
echo "Starting relay with custom configuration..."
|
echo "Starting relay with custom configuration..."
|
||||||
./$(basename $BINARY_PATH) $RELAY_ARGS > ../relay.log 2>&1 &
|
./$(basename $BINARY_PATH) $RELAY_ARGS --strict-port > ../relay.log 2>&1 &
|
||||||
else
|
else
|
||||||
# No command line arguments needed for random key generation
|
# No command line arguments needed for random key generation
|
||||||
echo "Starting relay with random key generation..."
|
echo "Starting relay with random key generation..."
|
||||||
./$(basename $BINARY_PATH) > ../relay.log 2>&1 &
|
./$(basename $BINARY_PATH) --strict-port > ../relay.log 2>&1 &
|
||||||
fi
|
fi
|
||||||
RELAY_PID=$!
|
RELAY_PID=$!
|
||||||
# Change back to original directory
|
# Change back to original directory
|
||||||
|
|||||||
455
nip11_relay_connection_implementation_plan.md
Normal file
455
nip11_relay_connection_implementation_plan.md
Normal file
@@ -0,0 +1,455 @@
|
|||||||
|
# NIP-11 Relay Connection Implementation Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Implement NIP-11 relay information fetching in the web admin interface to replace hardcoded relay pubkey and provide proper relay connection flow.
|
||||||
|
|
||||||
|
## Current Issues
|
||||||
|
1. **Hardcoded Relay Pubkey**: `getRelayPubkey()` returns hardcoded value `'4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa'`
|
||||||
|
2. **Relay URL in Debug Section**: Currently in "DEBUG - TEST FETCH WITHOUT LOGIN" section (lines 336-385)
|
||||||
|
3. **No Relay Verification**: Users can attempt admin operations without verifying relay identity
|
||||||
|
4. **Missing NIP-11 Support**: No fetching of relay information document
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
### 1. New Relay Connection Section (HTML Structure)
|
||||||
|
|
||||||
|
Add after User Info section (around line 332):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Relay Connection Section -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>RELAY CONNECTION</h2>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="relay-url-input">Relay URL:</label>
|
||||||
|
<input type="text" id="relay-url-input" value="ws://localhost:8888" placeholder="ws://localhost:8888 or wss://relay.example.com">
|
||||||
|
</div>
|
||||||
|
<div class="inline-buttons">
|
||||||
|
<button type="button" id="connect-relay-btn">CONNECT TO RELAY</button>
|
||||||
|
<button type="button" id="disconnect-relay-btn" style="display: none;">DISCONNECT</button>
|
||||||
|
</div>
|
||||||
|
<div class="status disconnected" id="relay-connection-status">NOT CONNECTED</div>
|
||||||
|
|
||||||
|
<!-- Relay Information Display -->
|
||||||
|
<div id="relay-info-display" class="hidden">
|
||||||
|
<h3>Relay Information</h3>
|
||||||
|
<div class="user-info">
|
||||||
|
<div><strong>Name:</strong> <span id="relay-name">-</span></div>
|
||||||
|
<div><strong>Description:</strong> <span id="relay-description">-</span></div>
|
||||||
|
<div><strong>Public Key:</strong>
|
||||||
|
<div class="user-pubkey" id="relay-pubkey-display">-</div>
|
||||||
|
</div>
|
||||||
|
<div><strong>Software:</strong> <span id="relay-software">-</span></div>
|
||||||
|
<div><strong>Version:</strong> <span id="relay-version">-</span></div>
|
||||||
|
<div><strong>Contact:</strong> <span id="relay-contact">-</span></div>
|
||||||
|
<div><strong>Supported NIPs:</strong> <span id="relay-nips">-</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. JavaScript Implementation
|
||||||
|
|
||||||
|
#### Global State Variables
|
||||||
|
Add to global state section (around line 535):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Relay connection state
|
||||||
|
let relayInfo = null;
|
||||||
|
let isRelayConnected = false;
|
||||||
|
let relayWebSocket = null;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### NIP-11 Fetching Function
|
||||||
|
Add new function:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Fetch relay information using NIP-11
|
||||||
|
async function fetchRelayInfo(relayUrl) {
|
||||||
|
try {
|
||||||
|
console.log('=== FETCHING RELAY INFO VIA NIP-11 ===');
|
||||||
|
console.log('Relay URL:', relayUrl);
|
||||||
|
|
||||||
|
// Convert WebSocket URL to HTTP URL for NIP-11
|
||||||
|
let httpUrl = relayUrl;
|
||||||
|
if (relayUrl.startsWith('ws://')) {
|
||||||
|
httpUrl = relayUrl.replace('ws://', 'http://');
|
||||||
|
} else if (relayUrl.startsWith('wss://')) {
|
||||||
|
httpUrl = relayUrl.replace('wss://', 'https://');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('HTTP URL for NIP-11:', httpUrl);
|
||||||
|
|
||||||
|
// Fetch relay information document
|
||||||
|
const response = await fetch(httpUrl, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/nostr+json'
|
||||||
|
},
|
||||||
|
// Add timeout
|
||||||
|
signal: AbortSignal.timeout(10000) // 10 second timeout
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
if (!contentType || !contentType.includes('application/json')) {
|
||||||
|
throw new Error(`Invalid content type: ${contentType}. Expected application/json or application/nostr+json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const relayInfoData = await response.json();
|
||||||
|
console.log('Fetched relay info:', relayInfoData);
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (!relayInfoData.pubkey) {
|
||||||
|
throw new Error('Relay information missing required pubkey field');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate pubkey format (64 hex characters)
|
||||||
|
if (!/^[0-9a-fA-F]{64}$/.test(relayInfoData.pubkey)) {
|
||||||
|
throw new Error(`Invalid relay pubkey format: ${relayInfoData.pubkey}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return relayInfoData;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch relay info:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Relay Connection Function
|
||||||
|
Add new function:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Connect to relay and fetch information
|
||||||
|
async function connectToRelay() {
|
||||||
|
try {
|
||||||
|
const relayUrlInput = document.getElementById('relay-url-input');
|
||||||
|
const connectBtn = document.getElementById('connect-relay-btn');
|
||||||
|
const disconnectBtn = document.getElementById('disconnect-relay-btn');
|
||||||
|
const statusDiv = document.getElementById('relay-connection-status');
|
||||||
|
const infoDisplay = document.getElementById('relay-info-display');
|
||||||
|
|
||||||
|
const url = relayUrlInput.value.trim();
|
||||||
|
if (!url) {
|
||||||
|
throw new Error('Please enter a relay URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update UI to show connecting state
|
||||||
|
connectBtn.disabled = true;
|
||||||
|
statusDiv.textContent = 'CONNECTING...';
|
||||||
|
statusDiv.className = 'status connected';
|
||||||
|
|
||||||
|
console.log('Connecting to relay:', url);
|
||||||
|
|
||||||
|
// Fetch relay information via NIP-11
|
||||||
|
console.log('Fetching relay information...');
|
||||||
|
const fetchedRelayInfo = await fetchRelayInfo(url);
|
||||||
|
|
||||||
|
// Test WebSocket connection
|
||||||
|
console.log('Testing WebSocket connection...');
|
||||||
|
await testWebSocketConnection(url);
|
||||||
|
|
||||||
|
// Store relay information
|
||||||
|
relayInfo = fetchedRelayInfo;
|
||||||
|
isRelayConnected = true;
|
||||||
|
|
||||||
|
// Update UI with relay information
|
||||||
|
displayRelayInfo(relayInfo);
|
||||||
|
|
||||||
|
// Update connection status
|
||||||
|
statusDiv.textContent = 'CONNECTED';
|
||||||
|
statusDiv.className = 'status connected';
|
||||||
|
|
||||||
|
// Update button states
|
||||||
|
connectBtn.style.display = 'none';
|
||||||
|
disconnectBtn.style.display = 'inline-block';
|
||||||
|
relayUrlInput.disabled = true;
|
||||||
|
|
||||||
|
// Show relay info
|
||||||
|
infoDisplay.classList.remove('hidden');
|
||||||
|
|
||||||
|
console.log('Successfully connected to relay:', relayInfo.name || url);
|
||||||
|
log(`Connected to relay: ${relayInfo.name || url}`, 'INFO');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to connect to relay:', error);
|
||||||
|
|
||||||
|
// Reset UI state
|
||||||
|
const connectBtn = document.getElementById('connect-relay-btn');
|
||||||
|
const statusDiv = document.getElementById('relay-connection-status');
|
||||||
|
|
||||||
|
connectBtn.disabled = false;
|
||||||
|
statusDiv.textContent = `CONNECTION FAILED: ${error.message}`;
|
||||||
|
statusDiv.className = 'status error';
|
||||||
|
|
||||||
|
// Clear any partial state
|
||||||
|
relayInfo = null;
|
||||||
|
isRelayConnected = false;
|
||||||
|
|
||||||
|
log(`Failed to connect to relay: ${error.message}`, 'ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### WebSocket Connection Test
|
||||||
|
Add new function:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Test WebSocket connection to relay
|
||||||
|
async function testWebSocketConnection(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
ws.close();
|
||||||
|
reject(new Error('WebSocket connection timeout'));
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
const ws = new WebSocket(url);
|
||||||
|
|
||||||
|
ws.onopen = () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
console.log('WebSocket connection successful');
|
||||||
|
ws.close();
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = (error) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
console.error('WebSocket connection failed:', error);
|
||||||
|
reject(new Error('WebSocket connection failed'));
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = (event) => {
|
||||||
|
if (event.code !== 1000) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
reject(new Error(`WebSocket closed with code ${event.code}: ${event.reason}`));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Display Relay Information
|
||||||
|
Add new function:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Display relay information in the UI
|
||||||
|
function displayRelayInfo(info) {
|
||||||
|
document.getElementById('relay-name').textContent = info.name || 'Unknown';
|
||||||
|
document.getElementById('relay-description').textContent = info.description || 'No description';
|
||||||
|
document.getElementById('relay-pubkey-display').textContent = info.pubkey || 'Unknown';
|
||||||
|
document.getElementById('relay-software').textContent = info.software || 'Unknown';
|
||||||
|
document.getElementById('relay-version').textContent = info.version || 'Unknown';
|
||||||
|
document.getElementById('relay-contact').textContent = info.contact || 'No contact info';
|
||||||
|
|
||||||
|
// Format supported NIPs
|
||||||
|
let nipsText = 'None specified';
|
||||||
|
if (info.supported_nips && Array.isArray(info.supported_nips) && info.supported_nips.length > 0) {
|
||||||
|
nipsText = info.supported_nips.map(nip => `NIP-${nip.toString().padStart(2, '0')}`).join(', ');
|
||||||
|
}
|
||||||
|
document.getElementById('relay-nips').textContent = nipsText;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Disconnect Function
|
||||||
|
Add new function:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Disconnect from relay
|
||||||
|
function disconnectFromRelay() {
|
||||||
|
console.log('Disconnecting from relay...');
|
||||||
|
|
||||||
|
// Clear relay state
|
||||||
|
relayInfo = null;
|
||||||
|
isRelayConnected = false;
|
||||||
|
|
||||||
|
// Close any existing connections
|
||||||
|
if (relayPool) {
|
||||||
|
const url = document.getElementById('relay-url-input').value.trim();
|
||||||
|
if (url) {
|
||||||
|
relayPool.close([url]);
|
||||||
|
}
|
||||||
|
relayPool = null;
|
||||||
|
subscriptionId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset UI
|
||||||
|
const connectBtn = document.getElementById('connect-relay-btn');
|
||||||
|
const disconnectBtn = document.getElementById('disconnect-relay-btn');
|
||||||
|
const statusDiv = document.getElementById('relay-connection-status');
|
||||||
|
const infoDisplay = document.getElementById('relay-info-display');
|
||||||
|
const relayUrlInput = document.getElementById('relay-url-input');
|
||||||
|
|
||||||
|
connectBtn.style.display = 'inline-block';
|
||||||
|
disconnectBtn.style.display = 'none';
|
||||||
|
connectBtn.disabled = false;
|
||||||
|
relayUrlInput.disabled = false;
|
||||||
|
|
||||||
|
statusDiv.textContent = 'NOT CONNECTED';
|
||||||
|
statusDiv.className = 'status disconnected';
|
||||||
|
|
||||||
|
infoDisplay.classList.add('hidden');
|
||||||
|
|
||||||
|
// Reset configuration status
|
||||||
|
updateConfigStatus(false);
|
||||||
|
|
||||||
|
log('Disconnected from relay', 'INFO');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Update getRelayPubkey Function
|
||||||
|
Replace existing function (around line 3142):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Helper function to get relay pubkey from connected relay info
|
||||||
|
function getRelayPubkey() {
|
||||||
|
if (relayInfo && relayInfo.pubkey) {
|
||||||
|
return relayInfo.pubkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to hardcoded value if no relay connected (for testing)
|
||||||
|
console.warn('No relay connected, using fallback pubkey');
|
||||||
|
return '4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Event Handlers
|
||||||
|
|
||||||
|
Add event handlers in the DOMContentLoaded section:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Relay connection event handlers
|
||||||
|
const connectRelayBtn = document.getElementById('connect-relay-btn');
|
||||||
|
const disconnectRelayBtn = document.getElementById('disconnect-relay-btn');
|
||||||
|
|
||||||
|
if (connectRelayBtn) {
|
||||||
|
connectRelayBtn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
connectToRelay().catch(error => {
|
||||||
|
console.error('Connect to relay failed:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disconnectRelayBtn) {
|
||||||
|
disconnectRelayBtn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
disconnectFromRelay();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Update Existing Functions
|
||||||
|
|
||||||
|
#### Update fetchConfiguration Function
|
||||||
|
Add relay connection check at the beginning:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function fetchConfiguration() {
|
||||||
|
try {
|
||||||
|
console.log('=== FETCHING CONFIGURATION VIA ADMIN API ===');
|
||||||
|
|
||||||
|
// Check if relay is connected
|
||||||
|
if (!isRelayConnected || !relayInfo) {
|
||||||
|
throw new Error('Must be connected to relay first. Please connect to relay in the Relay Connection section.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... rest of existing function
|
||||||
|
} catch (error) {
|
||||||
|
// ... existing error handling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Update subscribeToConfiguration Function
|
||||||
|
Add relay connection check:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function subscribeToConfiguration() {
|
||||||
|
try {
|
||||||
|
console.log('=== STARTING SIMPLEPOOL CONFIGURATION SUBSCRIPTION ===');
|
||||||
|
|
||||||
|
if (!isRelayConnected || !relayInfo) {
|
||||||
|
console.error('Must be connected to relay first');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the relay URL from the connection section instead of the debug section
|
||||||
|
const url = document.getElementById('relay-url-input').value.trim();
|
||||||
|
|
||||||
|
// ... rest of existing function
|
||||||
|
} catch (error) {
|
||||||
|
// ... existing error handling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Update UI Flow
|
||||||
|
|
||||||
|
#### Modify showMainInterface Function
|
||||||
|
Update to show relay connection requirement:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function showMainInterface() {
|
||||||
|
loginSection.classList.add('hidden');
|
||||||
|
mainInterface.classList.remove('hidden');
|
||||||
|
userPubkeyDisplay.textContent = userPubkey;
|
||||||
|
|
||||||
|
// Show message about relay connection requirement
|
||||||
|
if (!isRelayConnected) {
|
||||||
|
log('Please connect to a relay to access admin functions', 'INFO');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Remove/Update Debug Section
|
||||||
|
|
||||||
|
#### Option 1: Remove Debug Section Entirely
|
||||||
|
Remove the "DEBUG - TEST FETCH WITHOUT LOGIN" section (lines 335-385) since relay URL is now in the proper connection section.
|
||||||
|
|
||||||
|
#### Option 2: Keep Debug Section for Testing
|
||||||
|
Update the debug section to use the connected relay URL and add a note that it's for testing purposes.
|
||||||
|
|
||||||
|
### 7. Error Handling
|
||||||
|
|
||||||
|
Add comprehensive error handling for:
|
||||||
|
- Network timeouts
|
||||||
|
- Invalid relay URLs
|
||||||
|
- Missing NIP-11 support
|
||||||
|
- Invalid relay pubkey format
|
||||||
|
- WebSocket connection failures
|
||||||
|
- CORS issues
|
||||||
|
|
||||||
|
### 8. Security Considerations
|
||||||
|
|
||||||
|
- Validate relay pubkey format (64 hex characters)
|
||||||
|
- Verify relay identity before admin operations
|
||||||
|
- Handle CORS properly for NIP-11 requests
|
||||||
|
- Sanitize relay information display
|
||||||
|
- Warn users about connecting to untrusted relays
|
||||||
|
|
||||||
|
## Testing Plan
|
||||||
|
|
||||||
|
1. **NIP-11 Fetching**: Test with various relay URLs (localhost, remote relays)
|
||||||
|
2. **Error Handling**: Test with invalid URLs, non-Nostr servers, network failures
|
||||||
|
3. **WebSocket Connection**: Verify WebSocket connectivity after NIP-11 fetch
|
||||||
|
4. **Admin API Integration**: Ensure admin commands use correct relay pubkey
|
||||||
|
5. **UI Flow**: Test complete user journey from login → relay connection → admin operations
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
1. **Proper Relay Identification**: Uses actual relay pubkey instead of hardcoded value
|
||||||
|
2. **Better UX**: Clear connection flow and relay information display
|
||||||
|
3. **Protocol Compliance**: Implements NIP-11 standard for relay discovery
|
||||||
|
4. **Security**: Verifies relay identity before admin operations
|
||||||
|
5. **Flexibility**: Works with any NIP-11 compliant relay
|
||||||
|
|
||||||
|
## Migration Notes
|
||||||
|
|
||||||
|
- Existing users will need to connect to relay after this update
|
||||||
|
- Debug section can be kept for development/testing purposes
|
||||||
|
- All admin functions will require relay connection
|
||||||
|
- Relay pubkey will be dynamically fetched instead of hardcoded
|
||||||
1423
src/config.c
1423
src/config.c
File diff suppressed because it is too large
Load Diff
@@ -98,6 +98,7 @@ typedef struct {
|
|||||||
int port_override; // -1 = not set, >0 = port value
|
int port_override; // -1 = not set, >0 = port value
|
||||||
char admin_privkey_override[65]; // Empty string = not set, 64-char hex = override
|
char admin_privkey_override[65]; // Empty string = not set, 64-char hex = override
|
||||||
char relay_privkey_override[65]; // Empty string = not set, 64-char hex = override
|
char relay_privkey_override[65]; // Empty string = not set, 64-char hex = override
|
||||||
|
int strict_port; // 0 = allow port increment, 1 = fail if exact port unavailable
|
||||||
} cli_options_t;
|
} cli_options_t;
|
||||||
|
|
||||||
// Global unified configuration cache
|
// Global unified configuration cache
|
||||||
@@ -172,10 +173,10 @@ int process_admin_auth_event(cJSON* event, char* error_message, size_t error_siz
|
|||||||
int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
int handle_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_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_system_command_unified(cJSON* event, const char* command, char* error_message, size_t error_size, struct lws* wsi);
|
||||||
int handle_auth_rule_modification_unified(cJSON* event, char* error_message, size_t error_size);
|
int handle_auth_rule_modification_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||||
|
|
||||||
// WebSocket response functions
|
// Admin response functions
|
||||||
int send_websocket_response_data(cJSON* event, cJSON* response_data, struct lws* wsi);
|
int send_admin_response_event(const cJSON* response_data, const char* recipient_pubkey, struct lws* wsi);
|
||||||
cJSON* build_query_response(const char* query_type, cJSON* results_array, int total_count);
|
cJSON* build_query_response(const char* query_type, cJSON* results_array, int total_count);
|
||||||
|
|
||||||
// Auth rules management functions
|
// Auth rules management functions
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
* Default Configuration Event Template
|
* Default Configuration Event Template
|
||||||
*
|
*
|
||||||
* This header contains the default configuration values for the C Nostr Relay.
|
* This header contains the default configuration values for the C Nostr Relay.
|
||||||
* These values are used to create the initial kind 33334 configuration event
|
* These values are used to populate the config table during first-time startup.
|
||||||
* during first-time startup.
|
|
||||||
*
|
*
|
||||||
* IMPORTANT: These values should never be accessed directly by other parts
|
* IMPORTANT: These values should never be accessed directly by other parts
|
||||||
* of the program. They are only used during initial configuration event creation.
|
* of the program. They are only used during initial configuration event creation.
|
||||||
|
|||||||
80
src/main.c
80
src/main.c
@@ -224,10 +224,7 @@ int handle_event_message(cJSON* event, char* error_message, size_t error_size);
|
|||||||
// Forward declaration for unified validation
|
// Forward declaration for unified validation
|
||||||
int nostr_validate_unified_request(const char* json_string, size_t json_length);
|
int nostr_validate_unified_request(const char* json_string, size_t json_length);
|
||||||
|
|
||||||
// Forward declaration for configuration event handling (kind 33334)
|
// Forward declaration for admin event processing (kind 23456)
|
||||||
int handle_configuration_event(cJSON* event, char* error_message, size_t error_size);
|
|
||||||
|
|
||||||
// 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, struct lws* wsi);
|
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
|
// Forward declaration for enhanced admin event authorization
|
||||||
@@ -3035,7 +3032,7 @@ int is_authorized_admin_event(cJSON* event, char* error_buffer, size_t error_buf
|
|||||||
}
|
}
|
||||||
|
|
||||||
int event_kind = kind_json->valueint;
|
int event_kind = kind_json->valueint;
|
||||||
if (event_kind != 33334 && event_kind != 33335 && event_kind != 23455 && event_kind != 23456) {
|
if (event_kind != 23456) {
|
||||||
snprintf(error_buffer, error_buffer_size, "Event kind %d is not an admin event type", event_kind);
|
snprintf(error_buffer, error_buffer_size, "Event kind %d is not an admin event type", event_kind);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -3203,11 +3200,18 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
memcpy(message, in, len);
|
memcpy(message, in, len);
|
||||||
message[len] = '\0';
|
message[len] = '\0';
|
||||||
|
|
||||||
log_info("Received WebSocket message");
|
// Parse JSON message (this is the normal program flow)
|
||||||
|
|
||||||
// Parse JSON message
|
|
||||||
cJSON* json = cJSON_Parse(message);
|
cJSON* json = cJSON_Parse(message);
|
||||||
if (json && cJSON_IsArray(json)) {
|
if (json && cJSON_IsArray(json)) {
|
||||||
|
// Log the complete parsed JSON message once
|
||||||
|
char* complete_message = cJSON_Print(json);
|
||||||
|
if (complete_message) {
|
||||||
|
char debug_msg[2048];
|
||||||
|
snprintf(debug_msg, sizeof(debug_msg),
|
||||||
|
"Received complete WebSocket message: %s", complete_message);
|
||||||
|
log_info(debug_msg);
|
||||||
|
free(complete_message);
|
||||||
|
}
|
||||||
// Get message type
|
// Get message type
|
||||||
cJSON* type = cJSON_GetArrayItem(json, 0);
|
cJSON* type = cJSON_GetArrayItem(json, 0);
|
||||||
if (type && cJSON_IsString(type)) {
|
if (type && cJSON_IsString(type)) {
|
||||||
@@ -3349,7 +3353,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
// Cleanup event JSON string
|
// Cleanup event JSON string
|
||||||
free(event_json_str);
|
free(event_json_str);
|
||||||
|
|
||||||
// Check for admin events (kinds 33334, 33335, 23455, and 23456) and intercept them
|
// Check for admin events (kind 23456) and intercept them
|
||||||
if (result == 0) {
|
if (result == 0) {
|
||||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||||
if (kind_obj && cJSON_IsNumber(kind_obj)) {
|
if (kind_obj && cJSON_IsNumber(kind_obj)) {
|
||||||
@@ -3357,8 +3361,8 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
|
|
||||||
log_info("DEBUG ADMIN: Checking if admin event processing is needed");
|
log_info("DEBUG ADMIN: Checking if admin event processing is needed");
|
||||||
|
|
||||||
// Log reception of Kind 23455 and 23456 events
|
// Log reception of Kind 23456 events
|
||||||
if (event_kind == 23455 || event_kind == 23456) {
|
if (event_kind == 23456) {
|
||||||
char* event_json_debug = cJSON_Print(event);
|
char* event_json_debug = cJSON_Print(event);
|
||||||
char debug_received_msg[1024];
|
char debug_received_msg[1024];
|
||||||
snprintf(debug_received_msg, sizeof(debug_received_msg),
|
snprintf(debug_received_msg, sizeof(debug_received_msg),
|
||||||
@@ -3371,7 +3375,7 @@ 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 == 23456) {
|
||||||
// Enhanced admin event security - check authorization first
|
// Enhanced admin event security - check authorization first
|
||||||
log_info("DEBUG ADMIN: Admin event detected, checking authorization");
|
log_info("DEBUG ADMIN: Admin event detected, checking authorization");
|
||||||
|
|
||||||
@@ -3403,8 +3407,8 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
"DEBUG ADMIN: process_admin_event_in_config returned %d", admin_result);
|
"DEBUG ADMIN: process_admin_event_in_config returned %d", admin_result);
|
||||||
log_info(debug_admin_msg);
|
log_info(debug_admin_msg);
|
||||||
|
|
||||||
// Log results for Kind 23455 and 23456 events
|
// Log results for Kind 23456 events
|
||||||
if (event_kind == 23455 || event_kind == 23456) {
|
if (event_kind == 23456) {
|
||||||
if (admin_result == 0) {
|
if (admin_result == 0) {
|
||||||
char success_result_msg[256];
|
char success_result_msg[256];
|
||||||
snprintf(success_result_msg, sizeof(success_result_msg),
|
snprintf(success_result_msg, sizeof(success_result_msg),
|
||||||
@@ -3666,6 +3670,7 @@ int check_port_available(int port) {
|
|||||||
int sockfd;
|
int sockfd;
|
||||||
struct sockaddr_in addr;
|
struct sockaddr_in addr;
|
||||||
int result;
|
int result;
|
||||||
|
int reuse = 1;
|
||||||
|
|
||||||
// Create a socket
|
// Create a socket
|
||||||
sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
@@ -3673,6 +3678,13 @@ int check_port_available(int port) {
|
|||||||
return 0; // Cannot create socket, assume port unavailable
|
return 0; // Cannot create socket, assume port unavailable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set SO_REUSEADDR to allow binding to ports in TIME_WAIT state
|
||||||
|
// This matches libwebsockets behavior and prevents false unavailability
|
||||||
|
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
|
||||||
|
close(sockfd);
|
||||||
|
return 0; // Failed to set socket option
|
||||||
|
}
|
||||||
|
|
||||||
// Set up the address structure
|
// Set up the address structure
|
||||||
memset(&addr, 0, sizeof(addr));
|
memset(&addr, 0, sizeof(addr));
|
||||||
addr.sin_family = AF_INET;
|
addr.sin_family = AF_INET;
|
||||||
@@ -3690,7 +3702,7 @@ int check_port_available(int port) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start libwebsockets-based WebSocket Nostr relay server
|
// Start libwebsockets-based WebSocket Nostr relay server
|
||||||
int start_websocket_relay(int port_override) {
|
int start_websocket_relay(int port_override, int strict_port) {
|
||||||
struct lws_context_creation_info info;
|
struct lws_context_creation_info info;
|
||||||
|
|
||||||
log_info("Starting libwebsockets-based Nostr relay server...");
|
log_info("Starting libwebsockets-based Nostr relay server...");
|
||||||
@@ -3700,7 +3712,7 @@ int start_websocket_relay(int port_override) {
|
|||||||
int configured_port = (port_override > 0) ? port_override : get_config_int("relay_port", DEFAULT_PORT);
|
int configured_port = (port_override > 0) ? port_override : get_config_int("relay_port", DEFAULT_PORT);
|
||||||
int actual_port = configured_port;
|
int actual_port = configured_port;
|
||||||
int port_attempts = 0;
|
int port_attempts = 0;
|
||||||
const int max_port_attempts = 5;
|
const int max_port_attempts = 10; // Increased from 5 to 10
|
||||||
|
|
||||||
// Minimal libwebsockets configuration
|
// Minimal libwebsockets configuration
|
||||||
info.protocols = protocols;
|
info.protocols = protocols;
|
||||||
@@ -3719,8 +3731,8 @@ int start_websocket_relay(int port_override) {
|
|||||||
// Max payload size for Nostr events
|
// Max payload size for Nostr events
|
||||||
info.max_http_header_data = 4096;
|
info.max_http_header_data = 4096;
|
||||||
|
|
||||||
// Find an available port with pre-checking
|
// Find an available port with pre-checking (or fail immediately in strict mode)
|
||||||
while (port_attempts < max_port_attempts) {
|
while (port_attempts < (strict_port ? 1 : max_port_attempts)) {
|
||||||
char attempt_msg[256];
|
char attempt_msg[256];
|
||||||
snprintf(attempt_msg, sizeof(attempt_msg), "Checking port availability: %d", actual_port);
|
snprintf(attempt_msg, sizeof(attempt_msg), "Checking port availability: %d", actual_port);
|
||||||
log_info(attempt_msg);
|
log_info(attempt_msg);
|
||||||
@@ -3728,7 +3740,13 @@ int start_websocket_relay(int port_override) {
|
|||||||
// Pre-check if port is available
|
// Pre-check if port is available
|
||||||
if (!check_port_available(actual_port)) {
|
if (!check_port_available(actual_port)) {
|
||||||
port_attempts++;
|
port_attempts++;
|
||||||
if (port_attempts < max_port_attempts) {
|
if (strict_port) {
|
||||||
|
char error_msg[256];
|
||||||
|
snprintf(error_msg, sizeof(error_msg),
|
||||||
|
"Strict port mode: port %d is not available", actual_port);
|
||||||
|
log_error(error_msg);
|
||||||
|
return -1;
|
||||||
|
} else if (port_attempts < max_port_attempts) {
|
||||||
char retry_msg[256];
|
char retry_msg[256];
|
||||||
snprintf(retry_msg, sizeof(retry_msg), "Port %d is in use, trying port %d (attempt %d/%d)",
|
snprintf(retry_msg, sizeof(retry_msg), "Port %d is in use, trying port %d (attempt %d/%d)",
|
||||||
actual_port, actual_port + 1, port_attempts + 1, max_port_attempts);
|
actual_port, actual_port + 1, port_attempts + 1, max_port_attempts);
|
||||||
@@ -3767,7 +3785,13 @@ int start_websocket_relay(int port_override) {
|
|||||||
log_warning(lws_error_msg);
|
log_warning(lws_error_msg);
|
||||||
|
|
||||||
port_attempts++;
|
port_attempts++;
|
||||||
if (port_attempts < max_port_attempts) {
|
if (strict_port) {
|
||||||
|
char error_msg[256];
|
||||||
|
snprintf(error_msg, sizeof(error_msg),
|
||||||
|
"Strict port mode: failed to bind to port %d", actual_port);
|
||||||
|
log_error(error_msg);
|
||||||
|
break;
|
||||||
|
} else if (port_attempts < max_port_attempts) {
|
||||||
actual_port++;
|
actual_port++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -3833,6 +3857,7 @@ void print_usage(const char* program_name) {
|
|||||||
printf(" -p, --port PORT Override relay port (first-time startup only)\n");
|
printf(" -p, --port PORT Override relay port (first-time startup only)\n");
|
||||||
printf(" -a, --admin-privkey HEX Override admin private key (64-char hex)\n");
|
printf(" -a, --admin-privkey HEX Override admin private key (64-char hex)\n");
|
||||||
printf(" -r, --relay-privkey HEX Override relay private key (64-char hex)\n");
|
printf(" -r, --relay-privkey HEX Override relay private key (64-char hex)\n");
|
||||||
|
printf(" --strict-port Fail if exact port is unavailable (no port increment)\n");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("Configuration:\n");
|
printf("Configuration:\n");
|
||||||
printf(" This relay uses event-based configuration stored in the database.\n");
|
printf(" This relay uses event-based configuration stored in the database.\n");
|
||||||
@@ -3841,10 +3866,16 @@ void print_usage(const char* program_name) {
|
|||||||
printf(" After initial setup, all configuration is managed via database events.\n");
|
printf(" After initial setup, all configuration is managed via database events.\n");
|
||||||
printf(" Database file: <relay_pubkey>.db (created automatically)\n");
|
printf(" Database file: <relay_pubkey>.db (created automatically)\n");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
printf("Port Binding:\n");
|
||||||
|
printf(" Default: Try up to 10 consecutive ports if requested port is busy\n");
|
||||||
|
printf(" --strict-port: Fail immediately if exact requested port is unavailable\n");
|
||||||
|
printf("\n");
|
||||||
printf("Examples:\n");
|
printf("Examples:\n");
|
||||||
printf(" %s # Start relay (auto-configure on first run)\n", program_name);
|
printf(" %s # Start relay (auto-configure on first run)\n", program_name);
|
||||||
printf(" %s -p 8080 # First-time setup with port 8080\n", program_name);
|
printf(" %s -p 8080 # First-time setup with port 8080\n", program_name);
|
||||||
printf(" %s --port 9000 # First-time setup with port 9000\n", program_name);
|
printf(" %s --port 9000 # First-time setup with port 9000\n", program_name);
|
||||||
|
printf(" %s --strict-port # Fail if default port 8888 is unavailable\n", program_name);
|
||||||
|
printf(" %s -p 8080 --strict-port # Fail if port 8080 is unavailable\n", program_name);
|
||||||
printf(" %s --help # Show this help\n", program_name);
|
printf(" %s --help # Show this help\n", program_name);
|
||||||
printf(" %s --version # Show version info\n", program_name);
|
printf(" %s --version # Show version info\n", program_name);
|
||||||
printf("\n");
|
printf("\n");
|
||||||
@@ -3863,7 +3894,8 @@ int main(int argc, char* argv[]) {
|
|||||||
cli_options_t cli_options = {
|
cli_options_t cli_options = {
|
||||||
.port_override = -1, // -1 = not set
|
.port_override = -1, // -1 = not set
|
||||||
.admin_privkey_override = {0}, // Empty string = not set
|
.admin_privkey_override = {0}, // Empty string = not set
|
||||||
.relay_privkey_override = {0} // Empty string = not set
|
.relay_privkey_override = {0}, // Empty string = not set
|
||||||
|
.strict_port = 0 // 0 = allow port increment (default)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse command line arguments
|
// Parse command line arguments
|
||||||
@@ -3958,6 +3990,10 @@ int main(int argc, char* argv[]) {
|
|||||||
i++; // Skip the key argument
|
i++; // Skip the key argument
|
||||||
|
|
||||||
log_info("Relay private key override specified");
|
log_info("Relay private key override specified");
|
||||||
|
} else if (strcmp(argv[i], "--strict-port") == 0) {
|
||||||
|
// Strict port mode option
|
||||||
|
cli_options.strict_port = 1;
|
||||||
|
log_info("Strict port mode enabled - will fail if exact port is unavailable");
|
||||||
} else {
|
} else {
|
||||||
log_error("Unknown argument. Use --help for usage information.");
|
log_error("Unknown argument. Use --help for usage information.");
|
||||||
print_usage(argv[0]);
|
print_usage(argv[0]);
|
||||||
@@ -4179,7 +4215,7 @@ int main(int argc, char* argv[]) {
|
|||||||
log_info("Starting relay server...");
|
log_info("Starting relay server...");
|
||||||
|
|
||||||
// Start WebSocket Nostr relay server (port from configuration)
|
// Start WebSocket Nostr relay server (port from configuration)
|
||||||
int result = start_websocket_relay(-1); // Let config system determine port
|
int result = start_websocket_relay(-1, cli_options.strict_port); // Let config system determine port, pass strict_port flag
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
cleanup_relay_info();
|
cleanup_relay_info();
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
static const char* const EMBEDDED_SCHEMA_SQL =
|
static const char* const EMBEDDED_SCHEMA_SQL =
|
||||||
"-- C Nostr Relay Database Schema\n\
|
"-- C Nostr Relay Database Schema\n\
|
||||||
-- SQLite schema for storing Nostr events with JSON tags support\n\
|
-- SQLite schema for storing Nostr events with JSON tags support\n\
|
||||||
-- Event-based configuration system using kind 33334 Nostr events\n\
|
-- Configuration system using config table\n\
|
||||||
\n\
|
\n\
|
||||||
-- Schema version tracking\n\
|
-- Schema version tracking\n\
|
||||||
PRAGMA user_version = 7;\n\
|
PRAGMA user_version = 7;\n\
|
||||||
|
|||||||
@@ -34,12 +34,9 @@ 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
|
# WebSocket connection state (simplified - no persistent connections)
|
||||||
WS_PID=""
|
# These variables are kept for compatibility but not used
|
||||||
WS_INPUT_FIFO=""
|
|
||||||
WS_OUTPUT_FIFO=""
|
|
||||||
WS_CONNECTED=0
|
WS_CONNECTED=0
|
||||||
WS_RESPONSE_LOG=""
|
|
||||||
|
|
||||||
# Color codes for output
|
# Color codes for output
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
@@ -120,24 +117,99 @@ generate_test_keypair() {
|
|||||||
|
|
||||||
echo "$pubkey" > "$pubkey_file"
|
echo "$pubkey" > "$pubkey_file"
|
||||||
|
|
||||||
log_info "Generated keypair for $name: pubkey=${pubkey:0:16}..."
|
log_info "Generated keypair for $name: pubkey=$pubkey"
|
||||||
|
|
||||||
# Export for use in calling functions
|
# Export for use in calling functions
|
||||||
eval "${name}_PRIVKEY=\"$privkey\""
|
eval "${name}_PRIVKEY=\"$privkey\""
|
||||||
eval "${name}_PUBKEY=\"$pubkey\""
|
eval "${name}_PUBKEY=\"$pubkey\""
|
||||||
}
|
}
|
||||||
|
|
||||||
# Send WebSocket message and capture response
|
# NIP-44 encryption helper function using nak
|
||||||
|
encrypt_nip44_content() {
|
||||||
|
local content="$1"
|
||||||
|
local sender_privkey="$2"
|
||||||
|
local receiver_pubkey="$3"
|
||||||
|
|
||||||
|
if [ -z "$content" ] || [ -z "$sender_privkey" ] || [ -z "$receiver_pubkey" ]; then
|
||||||
|
log_error "encrypt_nip44_content: missing required parameters"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "DEBUG: About to encrypt content: '$content'" >&2
|
||||||
|
log_info "DEBUG: Sender privkey: $sender_privkey" >&2
|
||||||
|
log_info "DEBUG: Receiver pubkey: $receiver_pubkey" >&2
|
||||||
|
|
||||||
|
# Use nak to perform NIP-44 encryption with correct syntax:
|
||||||
|
# nak encrypt --recipient-pubkey <pubkey> --sec <private_key> [plaintext]
|
||||||
|
local encrypted_content
|
||||||
|
encrypted_content=$(nak encrypt --recipient-pubkey "$receiver_pubkey" --sec "$sender_privkey" "$content" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ $? -ne 0 ] || [ -z "$encrypted_content" ]; then
|
||||||
|
log_error "Failed to encrypt content with NIP-44"
|
||||||
|
log_error "Content: $content"
|
||||||
|
log_error "Sender privkey: $sender_privkey"
|
||||||
|
log_error "Receiver pubkey: $receiver_pubkey"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate that encrypted content is valid base64 and doesn't contain problematic characters
|
||||||
|
if ! echo "$encrypted_content" | grep -q '^[A-Za-z0-9+/]*=*$'; then
|
||||||
|
log_error "Encrypted content contains invalid characters for JSON: $encrypted_content"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if encrypted content is valid UTF-8/base64
|
||||||
|
if ! echo "$encrypted_content" | base64 -d >/dev/null 2>&1; then
|
||||||
|
log_warning "Encrypted content may not be valid base64: $encrypted_content"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "DEBUG: Encrypted content: $encrypted_content" >&2
|
||||||
|
log_info "Successfully encrypted content with NIP-44" >&2
|
||||||
|
echo "$encrypted_content"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# NIP-44 decryption helper function using nak
|
||||||
|
decrypt_nip44_content() {
|
||||||
|
local encrypted_content="$1"
|
||||||
|
local receiver_privkey="$2"
|
||||||
|
local sender_pubkey="$3"
|
||||||
|
|
||||||
|
if [ -z "$encrypted_content" ] || [ -z "$receiver_privkey" ] || [ -z "$sender_pubkey" ]; then
|
||||||
|
log_error "decrypt_nip44_content: missing required parameters"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "DEBUG: Decrypting content: $encrypted_content"
|
||||||
|
|
||||||
|
# Use nak to perform NIP-44 decryption with correct syntax:
|
||||||
|
# nak decrypt --sender-pubkey <pubkey> --sec <private_key> [encrypted_content]
|
||||||
|
local decrypted_content
|
||||||
|
decrypted_content=$(nak decrypt --sender-pubkey "$sender_pubkey" --sec "$receiver_privkey" "$encrypted_content" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ $? -ne 0 ] || [ -z "$decrypted_content" ]; then
|
||||||
|
log_error "Failed to decrypt content with NIP-44"
|
||||||
|
log_error "Encrypted content: $encrypted_content"
|
||||||
|
log_error "Receiver privkey: $receiver_privkey"
|
||||||
|
log_error "Sender pubkey: $sender_pubkey"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "DEBUG: Decrypted content: $decrypted_content"
|
||||||
|
log_info "Successfully decrypted content with NIP-44"
|
||||||
|
echo "$decrypted_content"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send WebSocket message and capture response (simplified pattern from 1_nip_test.sh)
|
||||||
send_websocket_message() {
|
send_websocket_message() {
|
||||||
local message="$1"
|
local message="$1"
|
||||||
local expected_response="$2"
|
local timeout="${2:-$TIMEOUT}"
|
||||||
local timeout="${3:-$TIMEOUT}"
|
|
||||||
|
|
||||||
# Use websocat to send message and capture response (following pattern from tests/1_nip_test.sh)
|
# Use websocat to send message and capture response (following pattern from tests/1_nip_test.sh)
|
||||||
local response=""
|
local response=""
|
||||||
if command -v websocat &> /dev/null; then
|
if command -v websocat &> /dev/null; then
|
||||||
# Capture output from websocat (following working pattern from 1_nip_test.sh)
|
response=$(printf '%s\n' "$message" | timeout "$timeout" websocat "$RELAY_URL" 2>&1 || echo "Connection failed")
|
||||||
response=$(echo "$message" | timeout "$timeout" websocat "$RELAY_URL" 2>&1 || echo "Connection failed")
|
|
||||||
|
|
||||||
# Check if connection failed
|
# Check if connection failed
|
||||||
if [[ "$response" == *"Connection failed"* ]]; then
|
if [[ "$response" == *"Connection failed"* ]]; then
|
||||||
@@ -154,143 +226,137 @@ send_websocket_message() {
|
|||||||
echo "$response"
|
echo "$response"
|
||||||
}
|
}
|
||||||
|
|
||||||
# =======================================================================
|
# Send admin event and capture response (simplified pattern from 1_nip_test.sh)
|
||||||
# PERSISTENT WEBSOCKET CONNECTION MANAGEMENT
|
send_admin_event() {
|
||||||
# =======================================================================
|
|
||||||
|
|
||||||
# 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 event_json="$1"
|
||||||
local timeout_seconds="${2:-10}"
|
local description="$2"
|
||||||
|
local timeout_seconds="${3:-10}"
|
||||||
|
|
||||||
if [ "$WS_CONNECTED" != "1" ]; then
|
log_info "Sending admin event: $description"
|
||||||
log_error "WebSocket connection not established"
|
|
||||||
|
# Create EVENT message using jq to properly handle special characters
|
||||||
|
local event_message
|
||||||
|
event_message=$(jq -n --argjson event "$event_json" '["EVENT", $event]')
|
||||||
|
|
||||||
|
# Validate that the event message is valid UTF-8 (temporarily disabled for debugging)
|
||||||
|
# if ! echo "$event_message" | iconv -f utf-8 -t utf-8 >/dev/null 2>&1; then
|
||||||
|
# log_error "Event message contains invalid UTF-8 characters"
|
||||||
|
# return 1
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# Use websocat to send event and capture OK response
|
||||||
|
local response=""
|
||||||
|
if command -v websocat &> /dev/null; then
|
||||||
|
log_info "Sending event using websocat..."
|
||||||
|
|
||||||
|
# Debug: Show what we're sending
|
||||||
|
log_info "DEBUG: Event message being sent: $event_message"
|
||||||
|
|
||||||
|
# Write to temporary file to avoid shell interpretation issues
|
||||||
|
local temp_file="${TEMP_DIR}/event_message_$$"
|
||||||
|
printf '%s\n' "$event_message" > "$temp_file"
|
||||||
|
|
||||||
|
# Send via websocat using file input with delay to receive response
|
||||||
|
response=$(timeout "$timeout_seconds" sh -c "cat '$temp_file'; sleep 0.5" | websocat "$RELAY_URL" 2>&1)
|
||||||
|
local websocat_exit_code=$?
|
||||||
|
|
||||||
|
# Clean up temp file
|
||||||
|
rm -f "$temp_file"
|
||||||
|
|
||||||
|
log_info "DEBUG: Websocat exit code: $websocat_exit_code"
|
||||||
|
log_info "DEBUG: Websocat response: $response"
|
||||||
|
|
||||||
|
# Check for specific websocat errors
|
||||||
|
if [[ "$response" == *"UTF-8 failure"* ]]; then
|
||||||
|
log_error "UTF-8 encoding error in event data for $description"
|
||||||
|
log_error "Event message: $event_message"
|
||||||
|
return 1
|
||||||
|
elif [[ "$response" == *"Connection failed"* ]] || [[ "$response" == *"Connection refused"* ]] || [[ "$response" == *"timeout"* ]]; then
|
||||||
|
log_error "Failed to connect to relay for $description"
|
||||||
|
return 1
|
||||||
|
elif [[ "$response" == *"error running"* ]]; then
|
||||||
|
log_error "Websocat error for $description: $response"
|
||||||
|
return 1
|
||||||
|
elif [ $websocat_exit_code -eq 0 ]; then
|
||||||
|
log_info "Event sent successfully via websocat"
|
||||||
|
else
|
||||||
|
log_warning "Websocat returned exit code $websocat_exit_code"
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
log_error "websocat not found - required for WebSocket testing"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Clear previous responses
|
echo "$response"
|
||||||
> "$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
|
# Send admin query and wait for encrypted response
|
||||||
wait_for_query_response() {
|
send_admin_query() {
|
||||||
local timeout_seconds="${1:-10}"
|
local event_json="$1"
|
||||||
local start_time=$(date +%s)
|
local description="$2"
|
||||||
|
local timeout_seconds="${3:-15}"
|
||||||
|
|
||||||
log_info "Waiting for query response data..."
|
log_info "Sending admin query: $description"
|
||||||
|
|
||||||
# Clear any OK responses and wait for JSON data
|
# Create EVENT message using jq to properly handle special characters
|
||||||
sleep 0.5 # Brief delay to ensure OK response is processed first
|
local event_message
|
||||||
|
event_message=$(jq -n --argjson event "$event_json" '["EVENT", $event]')
|
||||||
|
|
||||||
while [ $(($(date +%s) - start_time)) -lt $timeout_seconds ]; do
|
# For queries, we need to also send a REQ to get the response
|
||||||
# Look for JSON response with query data (not just OK responses)
|
local sub_id="admin_query_$(date +%s)"
|
||||||
if grep -q '"query_type"' "$WS_RESPONSE_LOG" 2>/dev/null; then
|
local req_message="[\"REQ\",\"$sub_id\",{\"kinds\":[23456],\"authors\":[\"$RELAY_PUBKEY\"],\"#p\":[\"$ADMIN_PUBKEY\"]}]"
|
||||||
local response=$(grep '"query_type"' "$WS_RESPONSE_LOG" | tail -1)
|
local close_message="[\"CLOSE\",\"$sub_id\"]"
|
||||||
echo "$response"
|
|
||||||
return 0
|
# Send query event and subscription in sequence
|
||||||
|
local response=""
|
||||||
|
if command -v websocat &> /dev/null; then
|
||||||
|
response=$(printf '%s\n%s\n%s\n' "$event_message" "$req_message" "$close_message" | timeout "$timeout_seconds" websocat "$RELAY_URL" 2>&1 || echo "Connection failed")
|
||||||
|
|
||||||
|
# Check if connection failed
|
||||||
|
if [[ "$response" == *"Connection failed"* ]]; then
|
||||||
|
log_error "Failed to connect to relay for $description"
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
sleep 0.1
|
|
||||||
done
|
|
||||||
|
|
||||||
log_error "Timeout waiting for query response data"
|
# Look for EVENT responses that might contain encrypted query data
|
||||||
return 1
|
local event_response=$(echo "$response" | grep '"EVENT"' | tail -1)
|
||||||
|
if [ -n "$event_response" ]; then
|
||||||
|
# Extract the event JSON from the EVENT message
|
||||||
|
local event_json=$(echo "$event_response" | jq -r '.[2]' 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$event_json" ] && [ "$event_json" != "null" ]; then
|
||||||
|
# Check if this is a kind 23456 response event
|
||||||
|
local event_kind=$(echo "$event_json" | jq -r '.kind' 2>/dev/null)
|
||||||
|
|
||||||
|
if [ "$event_kind" = "23456" ]; then
|
||||||
|
# Extract encrypted content and decrypt it
|
||||||
|
local encrypted_content=$(echo "$event_json" | jq -r '.content' 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$encrypted_content" ] && [ "$encrypted_content" != "null" ]; then
|
||||||
|
# Decrypt the response using NIP-44
|
||||||
|
local decrypted_content
|
||||||
|
decrypted_content=$(decrypt_nip44_content "$encrypted_content" "$ADMIN_PRIVKEY" "$RELAY_PUBKEY")
|
||||||
|
|
||||||
|
if [ $? -eq 0 ] && [ -n "$decrypted_content" ]; then
|
||||||
|
log_info "Successfully decrypted query response"
|
||||||
|
echo "$decrypted_content"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_warning "Failed to decrypt query response content"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
log_error "websocat not found - required for WebSocket testing"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Return the raw response if no encrypted content found
|
||||||
|
echo "$response"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create and send auth rule event
|
# Create and send auth rule event
|
||||||
@@ -301,15 +367,26 @@ send_auth_rule_event() {
|
|||||||
local pattern_value="$4" # actual pubkey or hash value
|
local pattern_value="$4" # actual pubkey or hash value
|
||||||
local description="$5" # optional description
|
local description="$5" # optional description
|
||||||
|
|
||||||
log_info "Creating auth rule event: $action $rule_type $pattern_type ${pattern_value:0:16}..."
|
log_info "Creating auth rule event: $action $rule_type $pattern_type $pattern_value"
|
||||||
|
|
||||||
# Create the auth rule event using nak with correct tag format for the actual implementation
|
# Create command array according to README.md API specification
|
||||||
# Server expects tags like ["whitelist", "pubkey", "abc123..."] or ["blacklist", "pubkey", "def456..."]
|
# Format: ["blacklist", "pubkey", "abc123..."] or ["whitelist", "pubkey", "def456..."]
|
||||||
# Using Kind 23456 (ephemeral auth rules management) with proper relay targeting
|
local command_array="[\"$rule_type\", \"$pattern_type\", \"$pattern_value\"]"
|
||||||
|
|
||||||
|
# Encrypt the command content using NIP-44
|
||||||
|
local encrypted_content
|
||||||
|
encrypted_content=$(encrypt_nip44_content "$command_array" "$ADMIN_PRIVKEY" "$RELAY_PUBKEY")
|
||||||
|
|
||||||
|
if [ $? -ne 0 ] || [ -z "$encrypted_content" ]; then
|
||||||
|
log_error "Failed to encrypt auth rule command content"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create the auth rule event using nak with NIP-44 encrypted content
|
||||||
|
# Using Kind 23456 (admin commands) with proper relay targeting and encrypted content
|
||||||
local event_json
|
local event_json
|
||||||
event_json=$(nak event -k 23456 --content "" \
|
event_json=$(nak event -k 23456 --content "$encrypted_content" \
|
||||||
-t "p=$RELAY_PUBKEY" \
|
-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
|
||||||
@@ -317,38 +394,23 @@ send_auth_rule_event() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Send the event through persistent WebSocket connection
|
log_info "DEBUG: Created event JSON: $event_json"
|
||||||
|
|
||||||
|
# Send the event using simplified WebSocket pattern
|
||||||
log_info "Publishing auth rule event to relay..."
|
log_info "Publishing auth rule event to relay..."
|
||||||
local result
|
local result
|
||||||
if [ "$WS_CONNECTED" = "1" ]; then
|
result=$(send_admin_event "$event_json" "auth rule $action")
|
||||||
result=$(send_websocket_event "$event_json")
|
local exit_code=$?
|
||||||
local exit_code=$?
|
|
||||||
|
|
||||||
log_info "Auth rule event result: $result"
|
log_info "Auth rule event result: $result"
|
||||||
|
|
||||||
# Check if response indicates success
|
# Check if response indicates success
|
||||||
if [ $exit_code -eq 0 ] && echo "$result" | grep -q -i '"OK".*true'; then
|
if [ $exit_code -eq 0 ] && echo "$result" | grep -q -i '"OK".*true'; then
|
||||||
log_success "Auth rule $action successful"
|
log_success "Auth rule $action successful"
|
||||||
return 0
|
return 0
|
||||||
else
|
|
||||||
log_error "Auth rule $action failed: $result (exit code: $exit_code)"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
# Fallback to one-shot connection if persistent connection not available
|
log_error "Auth rule $action failed: $result (exit code: $exit_code)"
|
||||||
result=$(echo "$event_json" | timeout 10s nak event "$RELAY_URL" 2>&1)
|
return 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,12 +418,27 @@ send_auth_rule_event() {
|
|||||||
clear_all_auth_rules() {
|
clear_all_auth_rules() {
|
||||||
log_info "Clearing all existing auth rules..."
|
log_info "Clearing all existing auth rules..."
|
||||||
|
|
||||||
|
# Create command array according to README.md API specification
|
||||||
|
# Format: ["system_command", "clear_all_auth_rules"]
|
||||||
|
local command_array="[\"system_command\", \"clear_all_auth_rules\"]"
|
||||||
|
log_info "DEBUG: Command array: $command_array"
|
||||||
|
|
||||||
|
# Encrypt the command content using NIP-44
|
||||||
|
local encrypted_content
|
||||||
|
encrypted_content=$(encrypt_nip44_content "$command_array" "$ADMIN_PRIVKEY" "$RELAY_PUBKEY")
|
||||||
|
|
||||||
|
if [ $? -ne 0 ] || [ -z "$encrypted_content" ]; then
|
||||||
|
log_error "Failed to encrypt system command content"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "DEBUG: Encrypted content: $encrypted_content"
|
||||||
|
|
||||||
# Create system command event to clear all auth rules
|
# Create system command event to clear all auth rules
|
||||||
# Using Kind 23456 (ephemeral auth rules management) with proper relay targeting
|
# Using Kind 23456 (admin commands) with proper relay targeting and encrypted content
|
||||||
local event_json
|
local event_json
|
||||||
event_json=$(nak event -k 23456 --content "" \
|
event_json=$(nak event -k 23456 --content "$encrypted_content" \
|
||||||
-t "p=$RELAY_PUBKEY" \
|
-t "p=$RELAY_PUBKEY" \
|
||||||
-t "system_command=clear_all_auth_rules" \
|
|
||||||
--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
|
||||||
@@ -369,38 +446,23 @@ clear_all_auth_rules() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Send the event through persistent WebSocket connection
|
log_info "DEBUG: Created event JSON: $event_json"
|
||||||
|
|
||||||
|
# Send the event using simplified WebSocket pattern
|
||||||
log_info "Sending clear all auth rules command..."
|
log_info "Sending clear all auth rules command..."
|
||||||
local result
|
local result
|
||||||
if [ "$WS_CONNECTED" = "1" ]; then
|
result=$(send_admin_event "$event_json" "clear all auth rules")
|
||||||
result=$(send_websocket_event "$event_json")
|
local exit_code=$?
|
||||||
local exit_code=$?
|
|
||||||
|
|
||||||
log_info "Clear auth rules result: $result"
|
log_info "Clear auth rules result: $result"
|
||||||
|
|
||||||
# Check if response indicates success
|
# Check if response indicates success
|
||||||
if [ $exit_code -eq 0 ] && echo "$result" | grep -q -i '"OK".*true'; then
|
if [ $exit_code -eq 0 ] && echo "$result" | grep -q -i '"OK".*true'; then
|
||||||
log_success "All auth rules cleared successfully"
|
log_success "All auth rules cleared successfully"
|
||||||
return 0
|
return 0
|
||||||
else
|
|
||||||
log_error "Failed to clear auth rules: $result (exit code: $exit_code)"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
# Fallback to one-shot connection if persistent connection not available
|
log_error "Failed to clear auth rules: $result (exit code: $exit_code)"
|
||||||
result=$(echo "$event_json" | timeout 10s nak event "$RELAY_URL" 2>&1)
|
return 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,7 +476,7 @@ test_event_publishing() {
|
|||||||
log_info "Testing event publishing: $description"
|
log_info "Testing event publishing: $description"
|
||||||
|
|
||||||
# Create a simple test event (kind 1 - text note) using nak like NIP-42 test
|
# Create a simple test event (kind 1 - text note) using nak like NIP-42 test
|
||||||
local test_content="Test message from ${test_pubkey:0:16}... at $(date)"
|
local test_content="Test message from $test_pubkey at $(date)"
|
||||||
local test_event
|
local test_event
|
||||||
test_event=$(nak event -k 1 --content "$test_content" --sec "$test_privkey" 2>/dev/null)
|
test_event=$(nak event -k 1 --content "$test_content" --sec "$test_privkey" 2>/dev/null)
|
||||||
|
|
||||||
@@ -426,7 +488,7 @@ test_event_publishing() {
|
|||||||
# Send the event using nak directly (more reliable than websocat)
|
# Send the event using nak directly (more reliable than websocat)
|
||||||
log_info "Publishing test event to relay..."
|
log_info "Publishing test event to relay..."
|
||||||
local result
|
local result
|
||||||
result=$(echo "$test_event" | timeout 10s nak event "$RELAY_URL" 2>&1)
|
result=$(printf '%s\n' "$test_event" | timeout 10s nak event "$RELAY_URL" 2>&1)
|
||||||
local exit_code=$?
|
local exit_code=$?
|
||||||
|
|
||||||
log_info "Event publishing result: $result"
|
log_info "Event publishing result: $result"
|
||||||
@@ -506,11 +568,22 @@ 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 23456 (admin commands) with proper relay targeting
|
# Using Kind 23456 (admin commands) with NIP-44 encrypted content
|
||||||
|
# Format: ["system_command", "system_status"]
|
||||||
|
local command_array="[\"system_command\", \"system_status\"]"
|
||||||
|
|
||||||
|
# Encrypt the command content using NIP-44
|
||||||
|
local encrypted_content
|
||||||
|
encrypted_content=$(encrypt_nip44_content "$command_array" "$ADMIN_PRIVKEY" "$RELAY_PUBKEY")
|
||||||
|
|
||||||
|
if [ $? -ne 0 ] || [ -z "$encrypted_content" ]; then
|
||||||
|
fail_test "Failed to encrypt admin authentication test content"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
local config_event
|
local config_event
|
||||||
config_event=$(nak event -k 23456 --content "" \
|
config_event=$(nak event -k 23456 --content "$encrypted_content" \
|
||||||
-t "p=$RELAY_PUBKEY" \
|
-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
|
||||||
@@ -518,15 +591,17 @@ test_admin_authentication() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Send admin event
|
# Send admin event using the proper admin event function
|
||||||
local message="[\"EVENT\",$config_event]"
|
|
||||||
local response
|
local response
|
||||||
response=$(send_websocket_message "$message" "OK" 10)
|
response=$(send_admin_event "$config_event" "admin authentication test")
|
||||||
|
local exit_code=$?
|
||||||
|
|
||||||
if echo "$response" | grep -q '"OK".*true'; then
|
log_info "Admin authentication result: $response"
|
||||||
|
|
||||||
|
if [ $exit_code -eq 0 ] && echo "$response" | grep -q '"OK".*true'; then
|
||||||
pass_test "Admin authentication successful"
|
pass_test "Admin authentication successful"
|
||||||
else
|
else
|
||||||
fail_test "Admin authentication failed: $response"
|
fail_test "Admin authentication failed: $response (exit code: $exit_code)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,10 +623,22 @@ 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..."
|
||||||
|
# Create command array according to README.md API specification
|
||||||
|
# Format: ["auth_query", "all"]
|
||||||
|
local command_array="[\"auth_query\", \"all\"]"
|
||||||
|
|
||||||
|
# Encrypt the command content using NIP-44
|
||||||
|
local encrypted_content
|
||||||
|
encrypted_content=$(encrypt_nip44_content "$command_array" "$ADMIN_PRIVKEY" "$RELAY_PUBKEY")
|
||||||
|
|
||||||
|
if [ $? -ne 0 ] || [ -z "$encrypted_content" ]; then
|
||||||
|
fail_test "Failed to encrypt auth query content"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
local query_event
|
local query_event
|
||||||
query_event=$(nak event -k 23456 --content "" \
|
query_event=$(nak event -k 23456 --content "$encrypted_content" \
|
||||||
-t "p=$RELAY_PUBKEY" \
|
-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
|
||||||
@@ -559,23 +646,23 @@ test_auth_rules_storage_query() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Send the query event
|
# Send the query event using simplified WebSocket pattern
|
||||||
log_info "Sending auth query to relay..."
|
log_info "Sending auth query to relay..."
|
||||||
local query_result
|
local decrypted_response
|
||||||
query_result=$(echo "$query_event" | timeout 10s nak event "$RELAY_URL" 2>&1)
|
decrypted_response=$(send_admin_query "$query_event" "auth rules query")
|
||||||
local exit_code=$?
|
local exit_code=$?
|
||||||
|
|
||||||
log_info "Auth query result: $query_result"
|
if [ $exit_code -eq 0 ] && [ -n "$decrypted_response" ]; then
|
||||||
|
log_info "Decrypted query response: $decrypted_response"
|
||||||
|
|
||||||
# Check if we got a response and if it contains our test rule
|
# Check if the decrypted response contains our test rule
|
||||||
if [ $exit_code -eq 0 ]; then
|
if echo "$decrypted_response" | grep -q "$TEST1_PUBKEY"; then
|
||||||
if echo "$query_result" | grep -q "$TEST1_PUBKEY"; then
|
pass_test "Auth rule storage and query working - found test rule in decrypted query results"
|
||||||
pass_test "Auth rule storage and query working - found test rule in query results"
|
|
||||||
else
|
else
|
||||||
fail_test "Auth rule not found in query results - rule may not have been stored"
|
fail_test "Auth rule not found in decrypted query results - rule may not have been stored"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
fail_test "Auth query failed: $query_result"
|
fail_test "Failed to receive or decrypt auth query response"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
fail_test "Failed to add auth rule for storage test"
|
fail_test "Failed to add auth rule for storage test"
|
||||||
@@ -747,14 +834,14 @@ test_hash_blacklist() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "Testing hash blacklist with event ID: ${event_id:0:16}..."
|
log_info "Testing hash blacklist with event ID: $event_id"
|
||||||
|
|
||||||
# Add the event ID to hash blacklist
|
# Add the event ID to hash blacklist
|
||||||
if send_auth_rule_event "add" "blacklist" "hash" "$event_id" "Test hash blacklist"; then
|
if send_auth_rule_event "add" "blacklist" "hash" "$event_id" "Test hash blacklist"; then
|
||||||
# Try to publish the same event using nak - should be blocked
|
# Try to publish the same event using nak - should be blocked
|
||||||
log_info "Attempting to publish blacklisted event..."
|
log_info "Attempting to publish blacklisted event..."
|
||||||
local result
|
local result
|
||||||
result=$(echo "$test_event" | timeout 10s nak event "$RELAY_URL" 2>&1)
|
result=$(printf '%s\n' "$test_event" | timeout 10s nak event "$RELAY_URL" 2>&1)
|
||||||
local exit_code=$?
|
local exit_code=$?
|
||||||
|
|
||||||
if [ $exit_code -ne 0 ] || echo "$result" | grep -q -i "blocked\|denied\|rejected\|blacklist"; then
|
if [ $exit_code -ne 0 ] || echo "$result" | grep -q -i "blocked\|denied\|rejected\|blacklist"; then
|
||||||
@@ -786,7 +873,7 @@ test_websocket_behavior() {
|
|||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
local message="[\"EVENT\",$test_event]"
|
local message="[\"EVENT\",$test_event]"
|
||||||
local response
|
local response
|
||||||
response=$(send_websocket_message "$message" "OK" 5)
|
response=$(send_websocket_message "$message" 5)
|
||||||
|
|
||||||
if echo "$response" | grep -q '"OK"'; then
|
if echo "$response" | grep -q '"OK"'; then
|
||||||
rapid_success_count=$((rapid_success_count + 1))
|
rapid_success_count=$((rapid_success_count + 1))
|
||||||
@@ -884,7 +971,7 @@ run_all_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
|
||||||
@@ -941,7 +1028,7 @@ main() {
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Check if relay is running - using websocat like the working tests
|
# Check if relay is running - using websocat like the working tests
|
||||||
if ! echo '["REQ","connection_test",{}]' | timeout 5 websocat "$RELAY_URL" >/dev/null 2>&1; then
|
if ! printf '%s\n' '["REQ","connection_test",{}]' | timeout 5 websocat "$RELAY_URL" >/dev/null 2>&1; then
|
||||||
log_error "Cannot connect to relay at $RELAY_URL"
|
log_error "Cannot connect to relay at $RELAY_URL"
|
||||||
log_error "Please ensure the C-Relay server is running in test mode"
|
log_error "Please ensure the C-Relay server is running in test mode"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
Reference in New Issue
Block a user