Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6dac231040 | ||
|
|
6fd3e531c3 | ||
|
|
c1c05991cf | ||
|
|
ab378e14d1 | ||
|
|
c0f9bf9ef5 | ||
|
|
bc6a7b3f20 | ||
|
|
036b0823b9 | ||
|
|
be99595bde | ||
|
|
01836a4b4c |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,4 +8,5 @@ src/version.h
|
||||
dev-config/
|
||||
db/
|
||||
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
|
||||
|
||||
### 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
|
||||
- Configuration changes require cryptographically signed events
|
||||
- Database path determined by generated relay pubkey
|
||||
@@ -35,7 +35,7 @@
|
||||
### First-Time Startup Sequence
|
||||
1. Relay generates admin keypair and relay keypair
|
||||
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
|
||||
|
||||
### Port Management
|
||||
@@ -48,20 +48,30 @@
|
||||
- Schema version 4 with JSON tag storage
|
||||
- **Critical**: Event expiration filtering done at application level, not SQL level
|
||||
|
||||
### Configuration Event Structure
|
||||
### Admin API Event Structure
|
||||
```json
|
||||
{
|
||||
"kind": 33334,
|
||||
"content": "C Nostr Relay Configuration",
|
||||
"kind": 23456,
|
||||
"content": "base64_nip44_encrypted_command_array",
|
||||
"tags": [
|
||||
["d", "<relay_pubkey>"],
|
||||
["relay_description", "value"],
|
||||
["max_subscriptions_per_client", "25"],
|
||||
["pow_min_difficulty", "16"]
|
||||
["p", "<relay_pubkey>"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**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
|
||||
```bash
|
||||
# Kill existing relay processes
|
||||
|
||||
180
README.md
180
README.md
@@ -22,4 +22,184 @@ Do NOT modify the formatting, add emojis, or change the text. Keep the simple fo
|
||||
- [ ] NIP-50: Keywords filter
|
||||
- [ ] NIP-70: Protected Events
|
||||
|
||||
## 🔧 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 **NIP-44 encrypted command arrays** for security and compatibility.
|
||||
|
||||
### Authentication
|
||||
|
||||
All admin commands require signing with the admin private key displayed during first-time startup. **Save this key securely** - it cannot be recovered and is needed for all administrative operations.
|
||||
|
||||
### Event Structure
|
||||
|
||||
All admin commands use the same unified event structure with NIP-44 encrypted content:
|
||||
|
||||
**Admin Command Event:**
|
||||
```json
|
||||
{
|
||||
"id": "event_id",
|
||||
"pubkey": "admin_public_key",
|
||||
"created_at": 1234567890,
|
||||
"kind": 23456,
|
||||
"content": "AqHBUgcM7dXFYLQuDVzGwMST1G8jtWYyVvYxXhVGEu4nAb4LVw...",
|
||||
"tags": [
|
||||
["p", "relay_public_key"]
|
||||
],
|
||||
"sig": "event_signature"
|
||||
}
|
||||
```
|
||||
|
||||
The `content` field contains a NIP-44 encrypted JSON array representing the command.
|
||||
|
||||
**Admin Response Event:**
|
||||
```json
|
||||
["EVENT", "temp_sub_id", {
|
||||
"id": "response_event_id",
|
||||
"pubkey": "relay_public_key",
|
||||
"created_at": 1234567890,
|
||||
"kind": 23457,
|
||||
"content": "BpKCVhfN8eYtRmPqSvWxZnMkL2gHjUiOp3rTyEwQaS5dFg...",
|
||||
"tags": [
|
||||
["p", "admin_public_key"]
|
||||
],
|
||||
"sig": "response_event_signature"
|
||||
}]
|
||||
```
|
||||
|
||||
The `content` field contains a NIP-44 encrypted JSON response object.
|
||||
|
||||
### Admin Commands
|
||||
|
||||
All commands are sent as NIP-44 encrypted JSON arrays in the event content. The following table lists all available commands:
|
||||
|
||||
| Command Type | Command Format | Description |
|
||||
|--------------|----------------|-------------|
|
||||
| **Configuration Management** |
|
||||
| `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", "all"]` | Query all configuration parameters |
|
||||
| **Auth Rules Management** |
|
||||
| `auth_add_blacklist` | `["blacklist", "pubkey", "abc123..."]` | Add pubkey to blacklist |
|
||||
| `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_type` | `["auth_query", "whitelist"]` | Query specific rule type |
|
||||
| `auth_query_pattern` | `["auth_query", "pattern", "abc123..."]` | Query specific pattern |
|
||||
| **System Commands** |
|
||||
| `system_clear_auth` | `["system_command", "clear_all_auth_rules"]` | Clear all auth rules |
|
||||
| `system_status` | `["system_command", "system_status"]` | Get system status |
|
||||
|
||||
### Available Configuration Keys
|
||||
|
||||
**Basic Relay Settings:**
|
||||
- `relay_description`: Relay description text
|
||||
- `relay_contact`: Contact information
|
||||
- `max_connections`: Maximum concurrent connections
|
||||
- `max_subscriptions_per_client`: Max subscriptions per client
|
||||
- `max_event_tags`: Maximum tags per event
|
||||
- `max_content_length`: Maximum event content length
|
||||
|
||||
**Authentication & Access Control:**
|
||||
- `auth_enabled`: Enable whitelist/blacklist auth rules (`true`/`false`)
|
||||
- `nip42_auth_required`: Enable NIP-42 cryptographic authentication (`true`/`false`)
|
||||
- `nip42_auth_required_kinds`: Event kinds requiring NIP-42 auth (comma-separated)
|
||||
- `nip42_challenge_timeout`: NIP-42 challenge expiration seconds
|
||||
|
||||
**Proof of Work & Validation:**
|
||||
- `pow_min_difficulty`: Minimum proof-of-work difficulty
|
||||
- `nip40_expiration_enabled`: Enable event expiration (`true`/`false`)
|
||||
|
||||
### Response Format
|
||||
|
||||
All admin commands return **signed EVENT responses** via WebSocket following standard Nostr protocol. Responses use JSON content with structured data.
|
||||
|
||||
#### Response Examples
|
||||
|
||||
**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\", \"status\": \"success\", \"message\": \"Operation completed successfully\", \"timestamp\": 1234567890}",
|
||||
"tags": [
|
||||
["p", "admin_public_key"]
|
||||
],
|
||||
"sig": "response_event_signature"
|
||||
}]
|
||||
```
|
||||
|
||||
**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\": \"invalid configuration value\", \"timestamp\": 1234567890}",
|
||||
"tags": [
|
||||
["p", "admin_public_key"]
|
||||
],
|
||||
"sig": "response_event_signature"
|
||||
}]
|
||||
```
|
||||
|
||||
**Auth Rules Query Response:**
|
||||
```json
|
||||
["EVENT", "temp_sub_id", {
|
||||
"id": "response_event_id",
|
||||
"pubkey": "relay_public_key",
|
||||
"created_at": 1234567890,
|
||||
"kind": 23457,
|
||||
"content": "nip44 encrypted:{\"query_type\": \"auth_rules_all\", \"total_results\": 2, \"timestamp\": 1234567890, \"data\": [{\"rule_type\": \"blacklist\", \"pattern_type\": \"pubkey\", \"pattern_value\": \"abc123...\", \"action\": \"allow\"}]}",
|
||||
"tags": [
|
||||
["p", "admin_public_key"]
|
||||
],
|
||||
"sig": "response_event_signature"
|
||||
}]
|
||||
```
|
||||
|
||||
**Configuration Query Response:**
|
||||
```json
|
||||
["EVENT", "temp_sub_id", {
|
||||
"id": "response_event_id",
|
||||
"pubkey": "relay_public_key",
|
||||
"created_at": 1234567890,
|
||||
"kind": 23457,
|
||||
"content": "nip44 encrypted:{\"query_type\": \"config_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": [
|
||||
["p", "admin_public_key"]
|
||||
],
|
||||
"sig": "response_event_signature"
|
||||
}]
|
||||
```
|
||||
|
||||
**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"
|
||||
}]
|
||||
```
|
||||
|
||||
3377
api/index.html
3377
api/index.html
File diff suppressed because it is too large
Load Diff
460
docs/admin_api_plan.md
Normal file
460
docs/admin_api_plan.md
Normal file
@@ -0,0 +1,460 @@
|
||||
# C-Relay Administrator API Implementation Plan
|
||||
|
||||
## Problem Analysis
|
||||
|
||||
### Current Issues Identified:
|
||||
|
||||
1. **Schema Mismatch**: Storage system (config.c) vs Validation system (request_validator.c) use different column names and values
|
||||
2. **Missing API Endpoint**: No way to clear auth_rules table for testing
|
||||
3. **Configuration Gap**: Auth rules enforcement may not be properly enabled
|
||||
4. **Documentation Gap**: Admin API commands not documented
|
||||
|
||||
### Root Cause: Auth Rules Schema Inconsistency
|
||||
|
||||
**Current Schema (sql_schema.h lines 140-150):**
|
||||
```sql
|
||||
CREATE TABLE auth_rules (
|
||||
rule_type TEXT CHECK (rule_type IN ('whitelist', 'blacklist')),
|
||||
pattern_type TEXT CHECK (pattern_type IN ('pubkey', 'hash')),
|
||||
pattern_value TEXT,
|
||||
action TEXT CHECK (action IN ('allow', 'deny')),
|
||||
active INTEGER DEFAULT 1
|
||||
);
|
||||
```
|
||||
|
||||
**Storage Implementation (config.c):**
|
||||
- Stores: `rule_type='blacklist'`, `pattern_type='pubkey'`, `pattern_value='hex'`, `action='allow'`
|
||||
|
||||
**Validation Implementation (request_validator.c):**
|
||||
- Queries: `rule_type='pubkey_blacklist'`, `rule_target='hex'`, `operation='event'`, `enabled=1`
|
||||
|
||||
**MISMATCH**: Validator looks for non-existent columns and wrong rule_type values!
|
||||
|
||||
## Proposed Solution Architecture
|
||||
|
||||
### Phase 1: API Documentation & Standardization
|
||||
|
||||
#### Admin API Commands (via WebSocket with admin private key)
|
||||
|
||||
**Kind 23456: Unified Admin API (Ephemeral)**
|
||||
- Configuration management: Update relay settings, limits, authentication policies
|
||||
- Auth rules: Add/remove/query whitelist/blacklist rules
|
||||
- System commands: clear rules, status, cache management
|
||||
- **Unified Format**: All commands use NIP-44 encrypted content with `["p", "relay_pubkey"]` tags
|
||||
- **Command Types**:
|
||||
- Configuration: `["config_key", "config_value"]`
|
||||
- Auth rules: `["rule_type", "pattern_type", "pattern_value"]`
|
||||
- Queries: `["auth_query", "filter"]` or `["system_command", "command_name"]`
|
||||
- **Security**: All admin commands use NIP-44 encryption for privacy and security
|
||||
|
||||
#### Configuration Commands (using Kind 23456)
|
||||
|
||||
1. **Update Configuration**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "base64_nip44_encrypted_command_array",
|
||||
"tags": [["p", "relay_pubkey"]]
|
||||
}
|
||||
```
|
||||
*Encrypted content contains:* `["relay_description", "My Relay"]`
|
||||
|
||||
2. **Query System Status**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "base64_nip44_encrypted_command_array",
|
||||
"tags": [["p", "relay_pubkey"]]
|
||||
}
|
||||
```
|
||||
*Encrypted content contains:* `["system_command", "system_status"]`
|
||||
|
||||
#### Auth Rules and System Commands (using Kind 23456)
|
||||
|
||||
1. **Clear All Auth Rules**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "base64_nip44_encrypted_command_array",
|
||||
"tags": [["p", "relay_pubkey"]]
|
||||
}
|
||||
```
|
||||
*Encrypted content contains:* `["system_command", "clear_all_auth_rules"]`
|
||||
|
||||
2. **Query All Auth Rules**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "base64_nip44_encrypted_command_array",
|
||||
"tags": [["p", "relay_pubkey"]]
|
||||
}
|
||||
```
|
||||
*Encrypted content contains:* `["auth_query", "all"]`
|
||||
|
||||
3. **Add Blacklist Rule**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "base64_nip44_encrypted_command_array",
|
||||
"tags": [["p", "relay_pubkey"]]
|
||||
}
|
||||
```
|
||||
*Encrypted content contains:* `["blacklist", "pubkey", "deadbeef1234abcd..."]`
|
||||
|
||||
### Phase 2: Auth Rules Schema Alignment
|
||||
|
||||
#### Option A: Fix Validator to Match Schema (RECOMMENDED)
|
||||
|
||||
**Update request_validator.c:**
|
||||
```sql
|
||||
-- OLD (broken):
|
||||
WHERE rule_type = 'pubkey_blacklist' AND rule_target = ? AND operation = ? AND enabled = 1
|
||||
|
||||
-- NEW (correct):
|
||||
WHERE rule_type = 'blacklist' AND pattern_type = 'pubkey' AND pattern_value = ? AND active = 1
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Matches actual database schema
|
||||
- Simpler rule_type values ('blacklist' vs 'pubkey_blacklist')
|
||||
- Uses existing columns (pattern_value vs rule_target)
|
||||
- Consistent with storage implementation
|
||||
|
||||
#### Option B: Update Schema to Match Validator (NOT RECOMMENDED)
|
||||
|
||||
Would require changing schema, migration scripts, and storage logic.
|
||||
|
||||
### Phase 3: Implementation Priority
|
||||
|
||||
#### High Priority (Critical for blacklist functionality):
|
||||
1. Fix request_validator.c schema mismatch
|
||||
2. Ensure auth_required configuration is enabled
|
||||
3. Update tests to use unified ephemeral event kind (23456)
|
||||
4. Test blacklist enforcement
|
||||
|
||||
#### Medium Priority (Enhanced Admin Features):
|
||||
1. **Implement NIP-44 Encryption Support**:
|
||||
- Detect NIP-44 encrypted content for Kind 23456 events
|
||||
- Parse `encrypted_tags` field from content JSON
|
||||
- Decrypt using admin privkey and relay pubkey
|
||||
- Process decrypted tags as normal commands
|
||||
2. Add clear_all_auth_rules system command
|
||||
3. Add auth rule query functionality (both standard and encrypted modes)
|
||||
4. Add configuration discovery (list available config keys)
|
||||
5. Enhanced error reporting in admin API
|
||||
6. Conflict resolution (same pubkey in whitelist + blacklist)
|
||||
|
||||
#### Security Priority (NIP-44 Implementation):
|
||||
1. **Encryption Detection Logic**: Check for empty tags + encrypted_tags field
|
||||
2. **Key Pair Management**: Use admin private key + relay public key for NIP-44
|
||||
3. **Backward Compatibility**: Support both standard and encrypted modes
|
||||
4. **Error Handling**: Graceful fallback if decryption fails
|
||||
5. **Performance**: Cache decrypted results to avoid repeated decryption
|
||||
|
||||
#### Low Priority (Documentation & Polish):
|
||||
1. Complete README.md API documentation
|
||||
2. Example usage scripts
|
||||
3. Admin client tools
|
||||
|
||||
### Phase 4: Expected API Structure
|
||||
|
||||
#### README.md Documentation Format:
|
||||
|
||||
```markdown
|
||||
# C-Relay Administrator API
|
||||
|
||||
## Authentication
|
||||
All admin commands require signing with the admin private key generated during first startup.
|
||||
|
||||
## Unified Admin API (Kind 23456 - Ephemeral)
|
||||
Update relay configuration parameters or query available settings.
|
||||
|
||||
**Configuration Update Event:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "base64_nip44_encrypted_command_array",
|
||||
"tags": [["p", "relay_pubkey"]]
|
||||
}
|
||||
```
|
||||
*Encrypted content contains:* `["relay_description", "My Relay Description"]`
|
||||
|
||||
**Auth Rules Management:**
|
||||
|
||||
**Add Rule Event:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"action\":\"add\",\"description\":\"Block malicious user\"}",
|
||||
"tags": [
|
||||
["blacklist", "pubkey", "deadbeef1234..."]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Remove Rule Event:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"action\":\"remove\",\"description\":\"Unblock user\"}",
|
||||
"tags": [
|
||||
["blacklist", "pubkey", "deadbeef1234..."]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Query All Auth Rules:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"query\":\"list_auth_rules\",\"description\":\"Get all rules\"}",
|
||||
"tags": [
|
||||
["auth_query", "all"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Query Whitelist Rules Only:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"query\":\"list_auth_rules\",\"description\":\"Get whitelist\"}",
|
||||
"tags": [
|
||||
["auth_query", "whitelist"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Check Specific Pattern:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"query\":\"check_pattern\",\"description\":\"Check if pattern exists\"}",
|
||||
"tags": [
|
||||
["auth_query", "pattern", "deadbeef1234..."]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## System Management (Kind 23456 - Ephemeral)
|
||||
System administration commands using the same kind as auth rules.
|
||||
|
||||
**Clear All Auth Rules:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"action\":\"clear_all\",\"description\":\"Clear all auth rules\"}",
|
||||
"tags": [
|
||||
["system_command", "clear_all_auth_rules"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**System Status:**
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"action\":\"system_status\",\"description\":\"Get system status\"}",
|
||||
"tags": [
|
||||
["system_command", "system_status"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Response Format
|
||||
All admin commands return JSON responses via WebSocket:
|
||||
|
||||
**Success Response:**
|
||||
```json
|
||||
["OK", "event_id", true, "success_message"]
|
||||
```
|
||||
|
||||
**Error Response:**
|
||||
```json
|
||||
["OK", "event_id", false, "error_message"]
|
||||
```
|
||||
|
||||
## Configuration Keys
|
||||
- `relay_description`: Relay description text
|
||||
- `relay_contact`: Contact information
|
||||
- `auth_enabled`: Enable authentication system
|
||||
- `max_connections`: Maximum concurrent connections
|
||||
- `pow_min_difficulty`: Minimum proof-of-work difficulty
|
||||
- ... (full list of config keys)
|
||||
|
||||
## Examples
|
||||
|
||||
### Enable Authentication & Add Blacklist
|
||||
```bash
|
||||
# 1. Enable auth system
|
||||
nak event -k 23456 --content "base64_nip44_encrypted_command" \
|
||||
-t "auth_enabled=true" \
|
||||
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
||||
|
||||
# 2. Add user to blacklist
|
||||
nak event -k 23456 --content '{"action":"add","description":"Spam user"}' \
|
||||
-t "blacklist=pubkey;$SPAM_USER_PUBKEY" \
|
||||
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
||||
|
||||
# 3. Query all auth rules
|
||||
nak event -k 23456 --content '{"query":"list_auth_rules","description":"Get all rules"}' \
|
||||
-t "auth_query=all" \
|
||||
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
||||
|
||||
# 4. Clear all rules for testing
|
||||
nak event -k 23456 --content '{"action":"clear_all","description":"Clear all rules"}' \
|
||||
-t "system_command=clear_all_auth_rules" \
|
||||
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
||||
```
|
||||
|
||||
## Expected Response Formats
|
||||
|
||||
### Configuration Query Response
|
||||
```json
|
||||
["EVENT", "subscription_id", {
|
||||
"kind": 23457,
|
||||
"content": "base64_nip44_encrypted_response",
|
||||
"tags": [["p", "admin_pubkey"]]
|
||||
}]
|
||||
```
|
||||
|
||||
### Current Config Response
|
||||
```json
|
||||
["EVENT", "subscription_id", {
|
||||
"kind": 23457,
|
||||
"content": "base64_nip44_encrypted_response",
|
||||
"tags": [["p", "admin_pubkey"]]
|
||||
}]
|
||||
```
|
||||
|
||||
### Auth Rules Query Response
|
||||
```json
|
||||
["EVENT", "subscription_id", {
|
||||
"kind": 23456,
|
||||
"content": "{\"auth_rules\": [{\"rule_type\": \"blacklist\", \"pattern_type\": \"pubkey\", \"pattern_value\": \"deadbeef...\"}, {\"rule_type\": \"whitelist\", \"pattern_type\": \"pubkey\", \"pattern_value\": \"cafebabe...\"}]}",
|
||||
"tags": [["response_type", "auth_rules_list"], ["query_type", "all"]]
|
||||
}]
|
||||
```
|
||||
|
||||
### Pattern Check Response
|
||||
```json
|
||||
["EVENT", "subscription_id", {
|
||||
"kind": 23456,
|
||||
"content": "{\"pattern_exists\": true, \"rule_type\": \"blacklist\", \"pattern_value\": \"deadbeef...\"}",
|
||||
"tags": [["response_type", "pattern_check"], ["pattern", "deadbeef..."]]
|
||||
}]
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. **Document API** (this file) ✅
|
||||
2. **Update to ephemeral event kinds** ✅
|
||||
3. **Fix request_validator.c** schema mismatch
|
||||
4. **Update tests** to use unified Kind 23456
|
||||
5. **Add auth rule query functionality**
|
||||
6. **Add configuration discovery feature**
|
||||
7. **Test blacklist functionality**
|
||||
8. **Add remaining system commands**
|
||||
|
||||
## Testing Plan
|
||||
|
||||
1. Fix schema mismatch and test basic blacklist
|
||||
2. Add clear_auth_rules and test table cleanup
|
||||
3. Test whitelist/blacklist conflict scenarios
|
||||
4. Test all admin API commands end-to-end
|
||||
5. Update integration tests
|
||||
|
||||
This plan addresses the immediate blacklist issue while establishing a comprehensive admin API framework for future expansion.
|
||||
|
||||
## NIP-44 Encryption Implementation Details
|
||||
|
||||
### Server-Side Detection Logic
|
||||
```c
|
||||
// In admin event processing function
|
||||
bool is_encrypted_command(struct nostr_event *event) {
|
||||
// Check if Kind 23456 with NIP-44 encrypted content
|
||||
if (event->kind == 23456 &&
|
||||
event->tags_count == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
cJSON *decrypt_admin_tags(struct nostr_event *event) {
|
||||
cJSON *content_json = cJSON_Parse(event->content);
|
||||
if (!content_json) return NULL;
|
||||
|
||||
cJSON *encrypted_tags = cJSON_GetObjectItem(content_json, "encrypted_tags");
|
||||
if (!encrypted_tags) {
|
||||
cJSON_Delete(content_json);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Decrypt using NIP-44 with admin pubkey and relay privkey
|
||||
char *decrypted = nip44_decrypt(
|
||||
cJSON_GetStringValue(encrypted_tags),
|
||||
admin_pubkey, // Shared secret with admin
|
||||
relay_private_key // Our private key
|
||||
);
|
||||
|
||||
cJSON *decrypted_tags = cJSON_Parse(decrypted);
|
||||
free(decrypted);
|
||||
cJSON_Delete(content_json);
|
||||
|
||||
return decrypted_tags; // Returns tag array: [["key1", "val1"], ["key2", "val2"]]
|
||||
}
|
||||
```
|
||||
|
||||
### Admin Event Processing Flow
|
||||
1. **Receive Event**: Kind 23456 with admin signature
|
||||
2. **Check Mode**: Empty tags = encrypted, populated tags = standard
|
||||
3. **Decrypt if Needed**: Extract and decrypt `encrypted_tags` from content
|
||||
4. **Process Commands**: Use decrypted/standard tags for command processing
|
||||
5. **Execute**: Same logic for both modes after tag extraction
|
||||
6. **Respond**: Standard response format (optionally encrypt response)
|
||||
|
||||
### Security Benefits
|
||||
- **Command Privacy**: Admin operations invisible in event tags
|
||||
- **Replay Protection**: NIP-44 includes timestamp/randomness
|
||||
- **Key Management**: Uses existing admin/relay key pair
|
||||
- **Backward Compatible**: Standard mode still works
|
||||
- **Performance**: Only decrypt when needed (empty tags detection)
|
||||
|
||||
### NIP-44 Library Integration
|
||||
The relay will need to integrate a NIP-44 encryption/decryption library:
|
||||
|
||||
```c
|
||||
// Required NIP-44 functions
|
||||
char* nip44_encrypt(const char* plaintext, const char* sender_privkey, const char* recipient_pubkey);
|
||||
char* nip44_decrypt(const char* ciphertext, const char* recipient_privkey, const char* sender_pubkey);
|
||||
```
|
||||
|
||||
### Implementation Priority (Updated)
|
||||
|
||||
#### Phase 1: Core Infrastructure (Complete)
|
||||
- [x] Event-based admin authentication system
|
||||
- [x] Kind 23456 (Unified Admin API) processing
|
||||
- [x] Basic configuration parameter updates
|
||||
- [x] Auth rule add/remove/clear functionality
|
||||
- [x] Updated to ephemeral event kinds
|
||||
- [x] Designed NIP-44 encryption support
|
||||
|
||||
#### Phase 2: NIP-44 Encryption Support (Next Priority)
|
||||
- [ ] **Add NIP-44 library dependency** to project
|
||||
- [ ] **Implement encryption detection logic** (`is_encrypted_command()`)
|
||||
- [ ] **Add decrypt_admin_tags() function** with NIP-44 support
|
||||
- [ ] **Update admin command processing** to handle both modes
|
||||
- [ ] **Test encrypted admin commands** end-to-end
|
||||
|
||||
#### Phase 3: Enhanced Features
|
||||
- [ ] **Auth rule query functionality** (both standard and encrypted modes)
|
||||
- [ ] **Configuration discovery API** (list available config keys)
|
||||
- [ ] **Enhanced error messages** with encryption status
|
||||
- [ ] **Performance optimization** (caching, async decrypt)
|
||||
|
||||
#### Phase 4: Schema Fixes (Critical)
|
||||
- [ ] **Fix request_validator.c** schema mismatch
|
||||
- [ ] **Enable blacklist enforcement** with encrypted commands
|
||||
- [ ] **Update tests** to use both standard and encrypted modes
|
||||
|
||||
This enhanced admin API provides enterprise-grade security while maintaining ease of use for basic operations.
|
||||
@@ -198,25 +198,54 @@ fi
|
||||
|
||||
echo "Build successful. Proceeding with relay restart..."
|
||||
|
||||
# Kill existing relay if running
|
||||
# Kill existing relay if running - start aggressive immediately
|
||||
echo "Stopping any existing relay servers..."
|
||||
pkill -f "c_relay_" 2>/dev/null
|
||||
sleep 2 # Give time for shutdown
|
||||
|
||||
# Check if port is still bound
|
||||
if lsof -i :8888 >/dev/null 2>&1; then
|
||||
echo "Port 8888 still in use, force killing..."
|
||||
fuser -k 8888/tcp 2>/dev/null || echo "No process on port 8888"
|
||||
# Get all relay processes and kill them immediately with -9
|
||||
RELAY_PIDS=$(pgrep -f "c_relay_" || echo "")
|
||||
if [ -n "$RELAY_PIDS" ]; then
|
||||
echo "Force killing relay processes immediately: $RELAY_PIDS"
|
||||
kill -9 $RELAY_PIDS 2>/dev/null
|
||||
else
|
||||
echo "No existing relay processes found"
|
||||
fi
|
||||
|
||||
# Get any remaining processes
|
||||
REMAINING_PIDS=$(pgrep -f "c_relay_" || echo "")
|
||||
if [ -n "$REMAINING_PIDS" ]; then
|
||||
echo "Force killing remaining processes: $REMAINING_PIDS"
|
||||
kill -9 $REMAINING_PIDS 2>/dev/null
|
||||
# Ensure port 8888 is completely free with retry loop
|
||||
echo "Ensuring port 8888 is available..."
|
||||
for attempt in {1..15}; do
|
||||
if ! lsof -i :8888 >/dev/null 2>&1; then
|
||||
echo "Port 8888 is now free"
|
||||
break
|
||||
fi
|
||||
|
||||
echo "Attempt $attempt: Port 8888 still in use, force killing..."
|
||||
# Kill anything using port 8888
|
||||
fuser -k 8888/tcp 2>/dev/null || true
|
||||
|
||||
# Double-check for any remaining relay processes
|
||||
REMAINING_PIDS=$(pgrep -f "c_relay_" || echo "")
|
||||
if [ -n "$REMAINING_PIDS" ]; then
|
||||
echo "Killing remaining relay processes: $REMAINING_PIDS"
|
||||
kill -9 $REMAINING_PIDS 2>/dev/null || true
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
|
||||
if [ $attempt -eq 15 ]; then
|
||||
echo "ERROR: Could not free port 8888 after 15 attempts"
|
||||
echo "Current processes using port:"
|
||||
lsof -i :8888 2>/dev/null || echo "No process details available"
|
||||
echo "You may need to manually kill processes or reboot"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Final safety check - ensure no relay processes remain
|
||||
FINAL_PIDS=$(pgrep -f "c_relay_" || echo "")
|
||||
if [ -n "$FINAL_PIDS" ]; then
|
||||
echo "Final cleanup: killing processes $FINAL_PIDS"
|
||||
kill -9 $FINAL_PIDS 2>/dev/null || true
|
||||
sleep 1
|
||||
else
|
||||
echo "No existing relay found"
|
||||
fi
|
||||
|
||||
# Clean up PID file
|
||||
@@ -253,14 +282,14 @@ cd build
|
||||
# Start relay in background and capture its PID
|
||||
if [ "$USE_TEST_KEYS" = true ]; then
|
||||
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
|
||||
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
|
||||
# No command line arguments needed for 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
|
||||
RELAY_PID=$!
|
||||
# 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
|
||||
1920
src/config.c
1920
src/config.c
File diff suppressed because it is too large
Load Diff
20
src/config.h
20
src/config.h
@@ -6,6 +6,9 @@
|
||||
#include <time.h>
|
||||
#include <pthread.h>
|
||||
|
||||
// Forward declaration for WebSocket support
|
||||
struct lws;
|
||||
|
||||
// Configuration constants
|
||||
#define CONFIG_VALUE_MAX_LENGTH 1024
|
||||
#define RELAY_NAME_MAX_LENGTH 256
|
||||
@@ -95,6 +98,7 @@ typedef struct {
|
||||
int port_override; // -1 = not set, >0 = port value
|
||||
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
|
||||
int strict_port; // 0 = allow port increment, 1 = fail if exact port unavailable
|
||||
} cli_options_t;
|
||||
|
||||
// Global unified configuration cache
|
||||
@@ -160,10 +164,20 @@ int update_config_in_table(const char* key, const char* value);
|
||||
int populate_default_config_values(void);
|
||||
int add_pubkeys_to_config_table(void);
|
||||
|
||||
// Admin event processing functions
|
||||
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size);
|
||||
// Admin event processing functions (updated with WebSocket support)
|
||||
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||
int process_admin_config_event(cJSON* event, char* error_message, size_t error_size);
|
||||
int process_admin_auth_event(cJSON* event, char* error_message, size_t error_size);
|
||||
int process_admin_auth_event(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||
|
||||
// Unified Kind 23456 handler functions
|
||||
int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||
int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi);
|
||||
int handle_system_command_unified(cJSON* event, const char* command, char* error_message, size_t error_size, struct lws* wsi);
|
||||
int handle_auth_rule_modification_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||
|
||||
// Admin response functions
|
||||
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);
|
||||
|
||||
// Auth rules management functions
|
||||
int add_auth_rule_from_config(const char* rule_type, const char* pattern_type,
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
* Default Configuration Event Template
|
||||
*
|
||||
* This header contains the default configuration values for the C Nostr Relay.
|
||||
* These values are used to create the initial kind 33334 configuration event
|
||||
* during first-time startup.
|
||||
* These values are used to populate the config table during first-time startup.
|
||||
*
|
||||
* IMPORTANT: These values should never be accessed directly by other parts
|
||||
* of the program. They are only used during initial configuration event creation.
|
||||
|
||||
327
src/main.c
327
src/main.c
@@ -224,11 +224,11 @@ int handle_event_message(cJSON* event, char* error_message, size_t error_size);
|
||||
// Forward declaration for unified validation
|
||||
int nostr_validate_unified_request(const char* json_string, size_t json_length);
|
||||
|
||||
// Forward declaration for configuration event handling (kind 33334)
|
||||
int handle_configuration_event(cJSON* event, char* error_message, size_t error_size);
|
||||
// Forward declaration for admin event processing (kind 23456)
|
||||
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||
|
||||
// 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);
|
||||
// Forward declaration for enhanced admin event authorization
|
||||
int is_authorized_admin_event(cJSON* event, char* error_message, size_t error_size);
|
||||
|
||||
// Forward declaration for NOTICE message support
|
||||
void send_notice_message(struct lws* wsi, const char* message);
|
||||
@@ -972,28 +972,28 @@ static void get_timestamp_string(char* buffer, size_t buffer_size) {
|
||||
void log_info(const char* message) {
|
||||
char timestamp[32];
|
||||
get_timestamp_string(timestamp, sizeof(timestamp));
|
||||
printf("[%s] " BLUE "[INFO]" RESET " %s\n", timestamp, message);
|
||||
printf("[%s] [INFO] %s\n", timestamp, message);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void log_success(const char* message) {
|
||||
char timestamp[32];
|
||||
get_timestamp_string(timestamp, sizeof(timestamp));
|
||||
printf("[%s] " GREEN "[SUCCESS]" RESET " %s\n", timestamp, message);
|
||||
printf("[%s] [SUCCESS] %s\n", timestamp, message);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void log_error(const char* message) {
|
||||
char timestamp[32];
|
||||
get_timestamp_string(timestamp, sizeof(timestamp));
|
||||
printf("[%s] " RED "[ERROR]" RESET " %s\n", timestamp, message);
|
||||
printf("[%s] [ERROR] %s\n", timestamp, message);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void log_warning(const char* message) {
|
||||
char timestamp[32];
|
||||
get_timestamp_string(timestamp, sizeof(timestamp));
|
||||
printf("[%s] " YELLOW "[WARNING]" RESET " %s\n", timestamp, message);
|
||||
printf("[%s] [WARNING] %s\n", timestamp, message);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
@@ -3009,6 +3009,109 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
||||
|
||||
return events_sent;
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ADMIN EVENT AUTHORIZATION
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Enhanced admin event authorization function
|
||||
int is_authorized_admin_event(cJSON* event, char* error_buffer, size_t error_buffer_size) {
|
||||
if (!event || !error_buffer) {
|
||||
if (error_buffer && error_buffer_size > 0) {
|
||||
snprintf(error_buffer, error_buffer_size, "Invalid parameters for admin authorization");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Step 1: Verify event kind is admin type
|
||||
cJSON *kind_json = cJSON_GetObjectItem(event, "kind");
|
||||
if (!kind_json || !cJSON_IsNumber(kind_json)) {
|
||||
snprintf(error_buffer, error_buffer_size, "Missing or invalid event kind");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int event_kind = kind_json->valueint;
|
||||
if (event_kind != 23456) {
|
||||
snprintf(error_buffer, error_buffer_size, "Event kind %d is not an admin event type", event_kind);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Step 2: Check if event targets this relay (look for 'p' tag with our relay pubkey)
|
||||
cJSON *tags = cJSON_GetObjectItem(event, "tags");
|
||||
if (!tags || !cJSON_IsArray(tags)) {
|
||||
// No tags array - treat as regular event for different relay
|
||||
log_info("Admin event has no tags array - treating as event for different relay");
|
||||
snprintf(error_buffer, error_buffer_size, "Admin event not targeting this relay (no tags)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int targets_this_relay = 0;
|
||||
cJSON *tag;
|
||||
cJSON_ArrayForEach(tag, tags) {
|
||||
if (cJSON_IsArray(tag)) {
|
||||
cJSON *tag_name = cJSON_GetArrayItem(tag, 0);
|
||||
cJSON *tag_value = cJSON_GetArrayItem(tag, 1);
|
||||
|
||||
if (tag_name && cJSON_IsString(tag_name) &&
|
||||
tag_value && cJSON_IsString(tag_value) &&
|
||||
strcmp(tag_name->valuestring, "p") == 0) {
|
||||
|
||||
// Compare with our relay pubkey
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
if (relay_pubkey && strcmp(tag_value->valuestring, relay_pubkey) == 0) {
|
||||
targets_this_relay = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!targets_this_relay) {
|
||||
// Admin event for different relay - not an error, just not for us
|
||||
log_info("Admin event targets different relay - treating as regular event");
|
||||
snprintf(error_buffer, error_buffer_size, "Admin event not targeting this relay");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Step 3: Verify admin signature authorization
|
||||
cJSON *pubkey_json = cJSON_GetObjectItem(event, "pubkey");
|
||||
if (!pubkey_json || !cJSON_IsString(pubkey_json)) {
|
||||
log_warning("Unauthorized admin event attempt: missing or invalid pubkey");
|
||||
snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: missing pubkey");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get admin pubkey from configuration
|
||||
const char* admin_pubkey = get_config_value("admin_pubkey");
|
||||
if (!admin_pubkey || strlen(admin_pubkey) == 0) {
|
||||
log_warning("Unauthorized admin event attempt: no admin pubkey configured");
|
||||
snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: no admin configured");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Compare pubkeys
|
||||
if (strcmp(pubkey_json->valuestring, admin_pubkey) != 0) {
|
||||
log_warning("Unauthorized admin event attempt: pubkey mismatch");
|
||||
char warning_msg[256];
|
||||
snprintf(warning_msg, sizeof(warning_msg),
|
||||
"Unauthorized admin event attempt from pubkey: %.32s...", pubkey_json->valuestring);
|
||||
log_warning(warning_msg);
|
||||
snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: invalid admin pubkey");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Step 4: Verify event signature
|
||||
if (nostr_verify_event_signature(event) != 0) {
|
||||
log_warning("Unauthorized admin event attempt: invalid signature");
|
||||
snprintf(error_buffer, error_buffer_size, "Unauthorized admin event attempt: signature verification failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// All checks passed - authorized admin event
|
||||
log_info("Admin event authorization successful");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3097,11 +3200,18 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
memcpy(message, in, len);
|
||||
message[len] = '\0';
|
||||
|
||||
log_info("Received WebSocket message");
|
||||
|
||||
// Parse JSON message
|
||||
// Parse JSON message (this is the normal program flow)
|
||||
cJSON* json = cJSON_Parse(message);
|
||||
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
|
||||
cJSON* type = cJSON_GetArrayItem(json, 0);
|
||||
if (type && cJSON_IsString(type)) {
|
||||
@@ -3115,11 +3225,30 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event_obj, "kind");
|
||||
int event_kind = kind_obj && cJSON_IsNumber(kind_obj) ? (int)cJSON_GetNumberValue(kind_obj) : -1;
|
||||
|
||||
// Extract pubkey and event ID for debugging
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event_obj, "pubkey");
|
||||
cJSON* id_obj = cJSON_GetObjectItem(event_obj, "id");
|
||||
const char* event_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : "unknown";
|
||||
const char* event_id = id_obj ? cJSON_GetStringValue(id_obj) : "unknown";
|
||||
|
||||
char debug_event_msg[512];
|
||||
snprintf(debug_event_msg, sizeof(debug_event_msg),
|
||||
"DEBUG EVENT: Processing kind %d event from pubkey %.16s... ID %.16s...",
|
||||
event_kind, event_pubkey, event_id);
|
||||
log_info(debug_event_msg);
|
||||
|
||||
// Check if NIP-42 authentication is required for this event kind or globally
|
||||
int auth_required = is_nip42_auth_globally_required() || is_nip42_auth_required_for_kind(event_kind);
|
||||
|
||||
char debug_auth_msg[256];
|
||||
snprintf(debug_auth_msg, sizeof(debug_auth_msg),
|
||||
"DEBUG AUTH: auth_required=%d, pss->authenticated=%d, event_kind=%d",
|
||||
auth_required, pss ? pss->authenticated : -1, event_kind);
|
||||
log_info(debug_auth_msg);
|
||||
|
||||
if (pss && auth_required && !pss->authenticated) {
|
||||
if (!pss->auth_challenge_sent) {
|
||||
log_info("DEBUG AUTH: Sending NIP-42 authentication challenge");
|
||||
send_nip42_auth_challenge(wsi, pss);
|
||||
} else {
|
||||
char auth_msg[256];
|
||||
@@ -3170,6 +3299,8 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_info("DEBUG VALIDATION: Starting unified validator");
|
||||
|
||||
// Call unified validator with JSON string
|
||||
size_t event_json_len = strlen(event_json_str);
|
||||
int validation_result = nostr_validate_unified_request(event_json_str, event_json_len);
|
||||
@@ -3177,6 +3308,11 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Map validation result to old result format (0 = success, -1 = failure)
|
||||
int result = (validation_result == NOSTR_SUCCESS) ? 0 : -1;
|
||||
|
||||
char debug_validation_msg[256];
|
||||
snprintf(debug_validation_msg, sizeof(debug_validation_msg),
|
||||
"DEBUG VALIDATION: validation_result=%d, result=%d", validation_result, result);
|
||||
log_info(debug_validation_msg);
|
||||
|
||||
// Generate error message based on validation result
|
||||
char error_message[512] = {0};
|
||||
if (result != 0) {
|
||||
@@ -3206,55 +3342,129 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
strncpy(error_message, "error: validation failed", sizeof(error_message) - 1);
|
||||
break;
|
||||
}
|
||||
char debug_error_msg[256];
|
||||
snprintf(debug_error_msg, sizeof(debug_error_msg),
|
||||
"DEBUG VALIDATION ERROR: %s", error_message);
|
||||
log_warning(debug_error_msg);
|
||||
} else {
|
||||
log_info("Event validated successfully using unified validator");
|
||||
log_info("DEBUG VALIDATION: Event validated successfully using unified validator");
|
||||
}
|
||||
|
||||
// Cleanup event JSON string
|
||||
free(event_json_str);
|
||||
|
||||
// Check for admin events (kinds 33334 and 33335) and intercept them
|
||||
// Check for admin events (kind 23456) and intercept them
|
||||
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);
|
||||
|
||||
if (event_kind == 33334 || event_kind == 33335) {
|
||||
// This is an admin event - process it through the admin API instead of normal storage
|
||||
log_info("Admin event detected, processing through admin API");
|
||||
log_info("DEBUG ADMIN: Checking if admin event processing is needed");
|
||||
|
||||
// Log reception of Kind 23456 events
|
||||
if (event_kind == 23456) {
|
||||
char* event_json_debug = cJSON_Print(event);
|
||||
char debug_received_msg[1024];
|
||||
snprintf(debug_received_msg, sizeof(debug_received_msg),
|
||||
"RECEIVED Kind %d event: %s", event_kind,
|
||||
event_json_debug ? event_json_debug : "Failed to serialize");
|
||||
log_info(debug_received_msg);
|
||||
|
||||
char admin_error[512] = {0};
|
||||
if (process_admin_event_in_config(event, admin_error, sizeof(admin_error)) != 0) {
|
||||
log_error("Failed to process admin event through admin API");
|
||||
if (event_json_debug) {
|
||||
free(event_json_debug);
|
||||
}
|
||||
}
|
||||
|
||||
if (event_kind == 23456) {
|
||||
// Enhanced admin event security - check authorization first
|
||||
log_info("DEBUG ADMIN: Admin event detected, checking authorization");
|
||||
|
||||
char auth_error[512] = {0};
|
||||
int auth_result = is_authorized_admin_event(event, auth_error, sizeof(auth_error));
|
||||
|
||||
if (auth_result != 0) {
|
||||
// Authorization failed - log and reject
|
||||
log_warning("DEBUG ADMIN: Admin event authorization failed");
|
||||
result = -1;
|
||||
size_t error_len = strlen(admin_error);
|
||||
size_t error_len = strlen(auth_error);
|
||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||
memcpy(error_message, admin_error, copy_len);
|
||||
memcpy(error_message, auth_error, copy_len);
|
||||
error_message[copy_len] = '\0';
|
||||
|
||||
char debug_auth_error_msg[600];
|
||||
snprintf(debug_auth_error_msg, sizeof(debug_auth_error_msg),
|
||||
"DEBUG ADMIN AUTH ERROR: %.400s", auth_error);
|
||||
log_warning(debug_auth_error_msg);
|
||||
} else {
|
||||
log_success("Admin event processed successfully through admin API");
|
||||
// Admin events are processed by the admin API, not broadcast to subscriptions
|
||||
// Authorization successful - process through admin API
|
||||
log_info("DEBUG ADMIN: Admin event authorized, processing through admin API");
|
||||
|
||||
char admin_error[512] = {0};
|
||||
int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
|
||||
|
||||
char debug_admin_msg[256];
|
||||
snprintf(debug_admin_msg, sizeof(debug_admin_msg),
|
||||
"DEBUG ADMIN: process_admin_event_in_config returned %d", admin_result);
|
||||
log_info(debug_admin_msg);
|
||||
|
||||
// Log results for Kind 23456 events
|
||||
if (event_kind == 23456) {
|
||||
if (admin_result == 0) {
|
||||
char success_result_msg[256];
|
||||
snprintf(success_result_msg, sizeof(success_result_msg),
|
||||
"SUCCESS: Kind %d event processed successfully", event_kind);
|
||||
log_success(success_result_msg);
|
||||
} else {
|
||||
char error_result_msg[512];
|
||||
snprintf(error_result_msg, sizeof(error_result_msg),
|
||||
"ERROR: Kind %d event processing failed: %s", event_kind, admin_error);
|
||||
log_error(error_result_msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (admin_result != 0) {
|
||||
log_error("DEBUG ADMIN: Failed to process admin event through admin API");
|
||||
result = -1;
|
||||
size_t error_len = strlen(admin_error);
|
||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||
memcpy(error_message, admin_error, copy_len);
|
||||
error_message[copy_len] = '\0';
|
||||
|
||||
char debug_admin_error_msg[600];
|
||||
snprintf(debug_admin_error_msg, sizeof(debug_admin_error_msg),
|
||||
"DEBUG ADMIN ERROR: %.400s", admin_error);
|
||||
log_error(debug_admin_error_msg);
|
||||
} else {
|
||||
log_success("DEBUG ADMIN: Admin event processed successfully through admin API");
|
||||
// Admin events are processed by the admin API, not broadcast to subscriptions
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Regular event - store in database and broadcast
|
||||
log_info("DEBUG STORAGE: Regular event - storing in database");
|
||||
if (store_event(event) != 0) {
|
||||
log_error("Failed to store event in database");
|
||||
log_error("DEBUG STORAGE: Failed to store event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
log_info("Event stored successfully in database");
|
||||
log_info("DEBUG STORAGE: Event stored successfully in database");
|
||||
// Broadcast event to matching persistent subscriptions
|
||||
broadcast_event_to_subscriptions(event);
|
||||
int broadcast_count = broadcast_event_to_subscriptions(event);
|
||||
char debug_broadcast_msg[128];
|
||||
snprintf(debug_broadcast_msg, sizeof(debug_broadcast_msg),
|
||||
"DEBUG BROADCAST: Event broadcast to %d subscriptions", broadcast_count);
|
||||
log_info(debug_broadcast_msg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Event without valid kind - try normal storage
|
||||
log_warning("DEBUG STORAGE: Event without valid kind - trying normal storage");
|
||||
if (store_event(event) != 0) {
|
||||
log_error("Failed to store event in database");
|
||||
log_error("DEBUG STORAGE: Failed to store event without kind in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
log_info("Event stored successfully in database");
|
||||
log_info("DEBUG STORAGE: Event without kind stored successfully in database");
|
||||
broadcast_event_to_subscriptions(event);
|
||||
}
|
||||
}
|
||||
@@ -3272,11 +3482,22 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// TODO: REPLACE - Remove wasteful cJSON_Print conversion
|
||||
char *response_str = cJSON_Print(response);
|
||||
if (response_str) {
|
||||
char debug_response_msg[512];
|
||||
snprintf(debug_response_msg, sizeof(debug_response_msg),
|
||||
"DEBUG RESPONSE: Sending OK response: %s", response_str);
|
||||
log_info(debug_response_msg);
|
||||
|
||||
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);
|
||||
int write_result = lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
|
||||
|
||||
char debug_write_msg[128];
|
||||
snprintf(debug_write_msg, sizeof(debug_write_msg),
|
||||
"DEBUG RESPONSE: lws_write returned %d", write_result);
|
||||
log_info(debug_write_msg);
|
||||
|
||||
free(buf);
|
||||
}
|
||||
free(response_str);
|
||||
@@ -3449,6 +3670,7 @@ int check_port_available(int port) {
|
||||
int sockfd;
|
||||
struct sockaddr_in addr;
|
||||
int result;
|
||||
int reuse = 1;
|
||||
|
||||
// Create a socket
|
||||
sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
@@ -3456,6 +3678,13 @@ int check_port_available(int port) {
|
||||
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
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
@@ -3473,7 +3702,7 @@ int check_port_available(int port) {
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
log_info("Starting libwebsockets-based Nostr relay server...");
|
||||
@@ -3483,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 actual_port = configured_port;
|
||||
int port_attempts = 0;
|
||||
const int max_port_attempts = 5;
|
||||
const int max_port_attempts = 10; // Increased from 5 to 10
|
||||
|
||||
// Minimal libwebsockets configuration
|
||||
info.protocols = protocols;
|
||||
@@ -3502,8 +3731,8 @@ int start_websocket_relay(int port_override) {
|
||||
// Max payload size for Nostr events
|
||||
info.max_http_header_data = 4096;
|
||||
|
||||
// Find an available port with pre-checking
|
||||
while (port_attempts < max_port_attempts) {
|
||||
// Find an available port with pre-checking (or fail immediately in strict mode)
|
||||
while (port_attempts < (strict_port ? 1 : max_port_attempts)) {
|
||||
char attempt_msg[256];
|
||||
snprintf(attempt_msg, sizeof(attempt_msg), "Checking port availability: %d", actual_port);
|
||||
log_info(attempt_msg);
|
||||
@@ -3511,7 +3740,13 @@ int start_websocket_relay(int port_override) {
|
||||
// Pre-check if port is available
|
||||
if (!check_port_available(actual_port)) {
|
||||
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];
|
||||
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);
|
||||
@@ -3550,7 +3785,13 @@ int start_websocket_relay(int port_override) {
|
||||
log_warning(lws_error_msg);
|
||||
|
||||
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++;
|
||||
continue;
|
||||
}
|
||||
@@ -3616,6 +3857,7 @@ void print_usage(const char* program_name) {
|
||||
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(" -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("Configuration:\n");
|
||||
printf(" This relay uses event-based configuration stored in the database.\n");
|
||||
@@ -3624,10 +3866,16 @@ void print_usage(const char* program_name) {
|
||||
printf(" After initial setup, all configuration is managed via database events.\n");
|
||||
printf(" Database file: <relay_pubkey>.db (created automatically)\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(" %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 --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 --version # Show version info\n", program_name);
|
||||
printf("\n");
|
||||
@@ -3646,7 +3894,8 @@ int main(int argc, char* argv[]) {
|
||||
cli_options_t cli_options = {
|
||||
.port_override = -1, // -1 = 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
|
||||
@@ -3741,6 +3990,10 @@ int main(int argc, char* argv[]) {
|
||||
i++; // Skip the key argument
|
||||
|
||||
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 {
|
||||
log_error("Unknown argument. Use --help for usage information.");
|
||||
print_usage(argv[0]);
|
||||
@@ -3962,7 +4215,7 @@ int main(int argc, char* argv[]) {
|
||||
log_info("Starting relay server...");
|
||||
|
||||
// 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_relay_info();
|
||||
|
||||
@@ -629,28 +629,26 @@ static int check_database_auth_rules(const char *pubkey, const char *operation,
|
||||
|
||||
// Step 1: Check pubkey blacklist (highest priority)
|
||||
const char *blacklist_sql =
|
||||
"SELECT rule_type, description FROM auth_rules WHERE rule_type = "
|
||||
"'pubkey_blacklist' AND rule_target = ? AND operation = ? AND enabled = "
|
||||
"1 ORDER BY priority LIMIT 1";
|
||||
"SELECT rule_type, action FROM auth_rules WHERE rule_type = "
|
||||
"'blacklist' AND pattern_type = 'pubkey' AND pattern_value = ? LIMIT 1";
|
||||
rc = sqlite3_prepare_v2(db, blacklist_sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, operation ? operation : "", -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char *description = (const char *)sqlite3_column_text(stmt, 1);
|
||||
const char *action = (const char *)sqlite3_column_text(stmt, 1);
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 1 FAILED - "
|
||||
"Pubkey blacklisted\n");
|
||||
char blacklist_msg[256];
|
||||
sprintf(blacklist_msg,
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Blacklist rule matched: %s\n",
|
||||
description ? description : "Unknown");
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Blacklist rule matched: action=%s\n",
|
||||
action ? action : "deny");
|
||||
validator_debug_log(blacklist_msg);
|
||||
|
||||
// Set specific violation details for status code mapping
|
||||
strcpy(g_last_rule_violation.violation_type, "pubkey_blacklist");
|
||||
sprintf(g_last_rule_violation.reason, "%s: Public key blacklisted",
|
||||
description ? description : "TEST_PUBKEY_BLACKLIST");
|
||||
sprintf(g_last_rule_violation.reason, "Public key blacklisted: %s",
|
||||
action ? action : "PUBKEY_BLACKLIST");
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close(db);
|
||||
@@ -664,29 +662,27 @@ static int check_database_auth_rules(const char *pubkey, const char *operation,
|
||||
// Step 2: Check hash blacklist
|
||||
if (resource_hash) {
|
||||
const char *hash_blacklist_sql =
|
||||
"SELECT rule_type, description FROM auth_rules WHERE rule_type = "
|
||||
"'hash_blacklist' AND rule_target = ? AND operation = ? AND enabled = "
|
||||
"1 ORDER BY priority LIMIT 1";
|
||||
"SELECT rule_type, action FROM auth_rules WHERE rule_type = "
|
||||
"'blacklist' AND pattern_type = 'hash' AND pattern_value = ? LIMIT 1";
|
||||
rc = sqlite3_prepare_v2(db, hash_blacklist_sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, resource_hash, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, operation ? operation : "", -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char *description = (const char *)sqlite3_column_text(stmt, 1);
|
||||
const char *action = (const char *)sqlite3_column_text(stmt, 1);
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 FAILED - "
|
||||
"Hash blacklisted\n");
|
||||
char hash_blacklist_msg[256];
|
||||
sprintf(
|
||||
hash_blacklist_msg,
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Hash blacklist rule matched: %s\n",
|
||||
description ? description : "Unknown");
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Hash blacklist rule matched: action=%s\n",
|
||||
action ? action : "deny");
|
||||
validator_debug_log(hash_blacklist_msg);
|
||||
|
||||
// Set specific violation details for status code mapping
|
||||
strcpy(g_last_rule_violation.violation_type, "hash_blacklist");
|
||||
sprintf(g_last_rule_violation.reason, "%s: File hash blacklisted",
|
||||
description ? description : "TEST_HASH_BLACKLIST");
|
||||
sprintf(g_last_rule_violation.reason, "File hash blacklisted: %s",
|
||||
action ? action : "HASH_BLACKLIST");
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close(db);
|
||||
@@ -703,22 +699,20 @@ static int check_database_auth_rules(const char *pubkey, const char *operation,
|
||||
|
||||
// Step 3: Check pubkey whitelist
|
||||
const char *whitelist_sql =
|
||||
"SELECT rule_type, description FROM auth_rules WHERE rule_type = "
|
||||
"'pubkey_whitelist' AND rule_target = ? AND operation = ? AND enabled = "
|
||||
"1 ORDER BY priority LIMIT 1";
|
||||
"SELECT rule_type, action FROM auth_rules WHERE rule_type = "
|
||||
"'whitelist' AND pattern_type = 'pubkey' AND pattern_value = ? LIMIT 1";
|
||||
rc = sqlite3_prepare_v2(db, whitelist_sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, operation ? operation : "", -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char *description = (const char *)sqlite3_column_text(stmt, 1);
|
||||
const char *action = (const char *)sqlite3_column_text(stmt, 1);
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 PASSED - "
|
||||
"Pubkey whitelisted\n");
|
||||
char whitelist_msg[256];
|
||||
sprintf(whitelist_msg,
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Whitelist rule matched: %s\n",
|
||||
description ? description : "Unknown");
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Whitelist rule matched: action=%s\n",
|
||||
action ? action : "allow");
|
||||
validator_debug_log(whitelist_msg);
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close(db);
|
||||
@@ -731,12 +725,10 @@ static int check_database_auth_rules(const char *pubkey, const char *operation,
|
||||
|
||||
// Step 4: Check if any whitelist rules exist - if yes, deny by default
|
||||
const char *whitelist_exists_sql =
|
||||
"SELECT COUNT(*) FROM auth_rules WHERE rule_type = 'pubkey_whitelist' "
|
||||
"AND operation = ? AND enabled = 1 LIMIT 1";
|
||||
"SELECT COUNT(*) FROM auth_rules WHERE rule_type = 'whitelist' "
|
||||
"AND pattern_type = 'pubkey' LIMIT 1";
|
||||
rc = sqlite3_prepare_v2(db, whitelist_exists_sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, operation ? operation : "", -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int whitelist_count = sqlite3_column_int(stmt, 0);
|
||||
if (whitelist_count > 0) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
static const char* const EMBEDDED_SCHEMA_SQL =
|
||||
"-- C Nostr Relay Database Schema\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\
|
||||
-- Schema version tracking\n\
|
||||
PRAGMA user_version = 7;\n\
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
=== NIP-42 Authentication Test Started ===
|
||||
2025-09-13 08:48:02 - Starting NIP-42 authentication tests
|
||||
[34m[1m[INFO][0m === Starting NIP-42 Authentication Tests ===
|
||||
[34m[1m[INFO][0m Checking dependencies...
|
||||
[32m[1m[SUCCESS][0m Dependencies check complete
|
||||
[34m[1m[INFO][0m Test 1: Checking NIP-42 support in relay info
|
||||
[32m[1m[SUCCESS][0m NIP-42 is advertised in supported NIPs
|
||||
2025-09-13 08:48:02 - Supported NIPs: 1,9,11,13,15,20,40,42
|
||||
[34m[1m[INFO][0m Test 2: Testing AUTH challenge generation
|
||||
[34m[1m[INFO][0m Found admin private key, configuring NIP-42 authentication...
|
||||
[33m[1m[WARNING][0m Failed to create configuration event - proceeding with manual test
|
||||
[34m[1m[INFO][0m Test 3: Testing complete NIP-42 authentication flow
|
||||
[34m[1m[INFO][0m Generated test keypair: test_pubkey
|
||||
[34m[1m[INFO][0m Attempting to publish event without authentication...
|
||||
[34m[1m[INFO][0m Publishing test event to relay...
|
||||
2025-09-13 08:48:03 - Event publish result: connecting to ws://localhost:8888... ok.
|
||||
{"kind":1,"id":"c42a8cbdd1cc6ea3e7fd060919c57386aef0c35da272ba2fa34b45f80934cfca","pubkey":"d0111448b3bd0da6aa699b92163f684291bb43bc213aa54a2ee726c2acde76e8","created_at":1757767683,"tags":[],"content":"NIP-42 test event - should require auth","sig":"d2a2c7efc00e06d8d8582fa05b2ec8cb96979525770dff9ef36a91df6d53807c86115581de2d6058d7d64eebe3b7d7404cc03dbb2ad1e91d140283703c2dec53"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
[32m[1m[SUCCESS][0m Relay requested authentication as expected
|
||||
[34m[1m[INFO][0m Test 4: Testing WebSocket AUTH message handling
|
||||
[34m[1m[INFO][0m Testing WebSocket connection and AUTH message...
|
||||
[34m[1m[INFO][0m Sending test message via WebSocket...
|
||||
2025-09-13 08:48:03 - WebSocket response:
|
||||
[34m[1m[INFO][0m No AUTH challenge in WebSocket response
|
||||
[34m[1m[INFO][0m Test 5: Testing NIP-42 configuration options
|
||||
[34m[1m[INFO][0m Retrieving current relay configuration...
|
||||
[32m[1m[SUCCESS][0m Retrieved configuration events from relay
|
||||
[32m[1m[SUCCESS][0m Found NIP-42 configuration:
|
||||
2025-09-13 08:48:04 - nip42_auth_required_events=false
|
||||
2025-09-13 08:48:04 - nip42_auth_required_subscriptions=false
|
||||
2025-09-13 08:48:04 - nip42_auth_required_kinds=4,14
|
||||
2025-09-13 08:48:04 - nip42_challenge_expiration=600
|
||||
[34m[1m[INFO][0m Test 6: Testing NIP-42 performance and stability
|
||||
[34m[1m[INFO][0m Testing multiple authentication attempts...
|
||||
2025-09-13 08:48:05 - Attempt 1: .271641300s - connecting to ws://localhost:8888... ok.
|
||||
{"kind":1,"id":"916049dbd6835443e8fd553bd12a37ef03060a01fedb099b414ea2cc18b597eb","pubkey":"b383f405d81860ec9b0eebf88612093ab18dc6abd322639b19ac79969599c8c4","created_at":1757767685,"tags":[],"content":"Performance test event 1","sig":"b04e0b38bbb49e0aa3c8a69530071bb08d917c4ba12eae38045a487c43e83f6dc1389ac4640453b0492d9c991df37f71e25ef501fd48c4c11c878e6cb3fa7a84"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
2025-09-13 08:48:05 - Attempt 2: .259343520s - connecting to ws://localhost:8888... ok.
|
||||
{"kind":1,"id":"e4495a56ec6f1ba2759eabbf0128aec615c53acf3e4720be7726dcd7163da703","pubkey":"b383f405d81860ec9b0eebf88612093ab18dc6abd322639b19ac79969599c8c4","created_at":1757767685,"tags":[],"content":"Performance test event 2","sig":"d1efe3f576eeded4e292ec22f2fea12296fa17ed2f87a8cd2dde0444b594ef55f7d74b680aeca11295a16397df5ccc53a938533947aece27efb965e6c643b62c"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
2025-09-13 08:48:06 - Attempt 3: .221167032s - connecting to ws://localhost:8888... ok.
|
||||
{"kind":1,"id":"55035b4c95a2c93a169236c7f5f5bd627838ec13522c88cf82d8b55516560cd9","pubkey":"b383f405d81860ec9b0eebf88612093ab18dc6abd322639b19ac79969599c8c4","created_at":1757767686,"tags":[],"content":"Performance test event 3","sig":"4bd581580a5a2416e6a9af44c055333635832dbf21793517f16100f1366c73437659545a8a712dcc4623a801b9deccd372b36b658309e7102a4300c3f481facb"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
2025-09-13 08:48:06 - Attempt 4: .260219496s - connecting to ws://localhost:8888... ok.
|
||||
{"kind":1,"id":"58dee587a1a0f085ff44441b3074f5ff42715088ee24e694107100df3c63ff2b","pubkey":"b383f405d81860ec9b0eebf88612093ab18dc6abd322639b19ac79969599c8c4","created_at":1757767686,"tags":[],"content":"Performance test event 4","sig":"b6174b0c56138466d3bb228ef2ced1d917f7253b76c624235fa3b661c9fa109c78ae557c4ddaf0e6232aa597608916f0dfba1c192f8b90ffb819c36ac1e4e516"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
2025-09-13 08:48:07 - Attempt 5: .260125188s - connecting to ws://localhost:8888... ok.
|
||||
{"kind":1,"id":"b8069c80f98fff3780eaeb605baf1a5818c9ab05185c1776a28469d2b0b32c6a","pubkey":"b383f405d81860ec9b0eebf88612093ab18dc6abd322639b19ac79969599c8c4","created_at":1757767687,"tags":[],"content":"Performance test event 5","sig":"5130d3a0c778728747b12aae77f2516db5b055d8ec43f413a4b117fcadb6025a49b6f602307bbe758bd97557e326e8735631fd03dc45c9296509e94aa305adf2"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
[32m[1m[SUCCESS][0m Performance test completed: 5/5 successful responses
|
||||
[34m[1m[INFO][0m Test 7: Testing kind-specific NIP-42 authentication requirements
|
||||
[34m[1m[INFO][0m Generated test keypair for kind-specific tests: test_pubkey
|
||||
[34m[1m[INFO][0m Testing kind 1 event (regular note) - should work without authentication...
|
||||
2025-09-13 08:48:08 - Kind 1 event result: connecting to ws://localhost:8888... ok.
|
||||
{"kind":1,"id":"f2ac02a5290db3797c0b7b38435920d5db593d333e582454d8ed32da4c141b74","pubkey":"da031504ff61656d1829f723c52f526d7591400fb9e2aecb7b4ef5aeeea66fc7","created_at":1757767688,"tags":[],"content":"Regular note - should not require auth","sig":"8e4272d9cb258fc4b140eb8e8c2e802c3e8b62e34c17c9e545d83c68dfb86ffd2cdd4a8153660b663a46906459aa67719257ac263f21d1f8a6185806e055dcfd"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
[32m[1m[SUCCESS][0m Kind 1 event accepted without authentication (correct behavior)
|
||||
[34m[1m[INFO][0m Testing kind 4 event (direct message) - should require authentication...
|
||||
2025-09-13 08:48:18 - Kind 4 event result: connecting to ws://localhost:8888... ok.
|
||||
{"kind":4,"id":"935af23e2bf7efd324d86a0c82631e5ebe492edf21920ed0f548faa73a18ac1d","pubkey":"da031504ff61656d1829f723c52f526d7591400fb9e2aecb7b4ef5aeeea66fc7","created_at":1757767688,"tags":[["p,test_pubkey"]],"content":"This is a direct message - should require auth","sig":"b2b86ee394b41505ddbd787c22f4223665770d84a21dd03e74bf4e8fa879ff82dd6b1f7d6921d93f8d89787102c3dc3012e6270d66ca5b5d4b87f1a545481e76"}
|
||||
publishing to ws://localhost:8888...
|
||||
[32m[1m[SUCCESS][0m Kind 4 event requested authentication (correct behavior for DMs)
|
||||
[34m[1m[INFO][0m Testing kind 14 event (chat message) - should require authentication...
|
||||
2025-09-13 08:48:28 - Kind 14 event result: connecting to ws://localhost:8888... ok.
|
||||
{"kind":14,"id":"aeb1ac58dd465c90ce5a70c7b16e3cc32fae86c221bb2e86ca29934333604669","pubkey":"da031504ff61656d1829f723c52f526d7591400fb9e2aecb7b4ef5aeeea66fc7","created_at":1757767698,"tags":[["p,test_pubkey"]],"content":"Chat message - should require auth","sig":"24e23737e6684e4ef01c08d72304e6f235ce75875b94b37460065f9ead986438435585818ba104e7f78f14345406b5d03605c925042e9c06fed8c99369cd8694"}
|
||||
publishing to ws://localhost:8888...
|
||||
[32m[1m[SUCCESS][0m Kind 14 event requested authentication (correct behavior for DMs)
|
||||
[34m[1m[INFO][0m Testing other event kinds - should work without authentication...
|
||||
2025-09-13 08:48:29 - Kind 0 event result: connecting to ws://localhost:8888... ok.
|
||||
{"kind":0,"id":"3b2cc834dd874ebbe07c2da9e41c07b3f0c61a57b4d6b7299c2243dbad29f2ca","pubkey":"da031504ff61656d1829f723c52f526d7591400fb9e2aecb7b4ef5aeeea66fc7","created_at":1757767709,"tags":[],"content":"Test event kind 0 - should not require auth","sig":"4f2016fde84d72cf5a5aa4c0ec5de677ef06c7971ca2dd756b02a94c47604fae1c67254703a2df3d17b13fee2d9c45661b76086f29ac93820a4c062fc52dea74"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
[32m[1m[SUCCESS][0m Kind 0 event accepted without authentication (correct)
|
||||
2025-09-13 08:48:29 - Kind 3 event result: connecting to ws://localhost:8888... ok.
|
||||
{"kind":3,"id":"6e1ea0b1cbf342feea030fa39226c316e730c5d333fa8333495748afd386ec80","pubkey":"da031504ff61656d1829f723c52f526d7591400fb9e2aecb7b4ef5aeeea66fc7","created_at":1757767709,"tags":[],"content":"Test event kind 3 - should not require auth","sig":"e5f66c5f022497f8888f003a8bfbb5e807a2520d314c80889548efa267f9d6de28d5ee7b0588cc8660f2963ab44e530c8a74d71a227148e5a6843fcef4de2197"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
[32m[1m[SUCCESS][0m Kind 3 event accepted without authentication (correct)
|
||||
2025-09-13 08:48:30 - Kind 7 event result: connecting to ws://localhost:8888... ok.
|
||||
{"kind":7,"id":"a64466b9899cad257313e2dced357fd3f87f40bd7e13e29372689aae7c718919","pubkey":"da031504ff61656d1829f723c52f526d7591400fb9e2aecb7b4ef5aeeea66fc7","created_at":1757767710,"tags":[],"content":"Test event kind 7 - should not require auth","sig":"78d18bcb0c2b11b4e2b74bcdfb140564b4563945e983014a279977356e50b57f3c5a262fa55de26dbd4c8d8b9f5beafbe21af869be64079f54a712284f03d9ac"}
|
||||
publishing to ws://localhost:8888... success.
|
||||
[32m[1m[SUCCESS][0m Kind 7 event accepted without authentication (correct)
|
||||
[34m[1m[INFO][0m Kind-specific authentication test completed
|
||||
[34m[1m[INFO][0m === NIP-42 Test Results Summary ===
|
||||
[32m[1m[SUCCESS][0m Dependencies: PASS
|
||||
[32m[1m[SUCCESS][0m NIP-42 Support: PASS
|
||||
[32m[1m[SUCCESS][0m Auth Challenge: PASS
|
||||
[32m[1m[SUCCESS][0m Auth Flow: PASS
|
||||
[32m[1m[SUCCESS][0m WebSocket AUTH: PASS
|
||||
[32m[1m[SUCCESS][0m Configuration: PASS
|
||||
[32m[1m[SUCCESS][0m Performance: PASS
|
||||
[32m[1m[SUCCESS][0m Kind-Specific Auth: PASS
|
||||
[32m[1m[SUCCESS][0m All NIP-42 tests completed successfully!
|
||||
[32m[1m[SUCCESS][0m NIP-42 authentication implementation is working correctly
|
||||
[34m[1m[INFO][0m === NIP-42 Authentication Tests Complete ===
|
||||
1054
tests/white_black_list_test.sh
Executable file
1054
tests/white_black_list_test.sh
Executable file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user