diff --git a/api/index.html b/api/index.html index d474cec..e37e414 100644 --- a/api/index.html +++ b/api/index.html @@ -932,7 +932,7 @@ description: 'C-Relay instance - pubkey provided manually', pubkey: manualPubkey, contact: 'admin@manual.config.relay', - supported_nips: [1, 2, 4, 9, 11, 12, 15, 16, 20, 22], + supported_nips: [1, 9, 11, 13, 15, 20, 33, 40, 42], software: 'https://github.com/0xtrr/c-relay', version: '1.0.0' }; @@ -958,7 +958,7 @@ description: 'C-Relay instance - pubkey provided manually', pubkey: manualPubkey, contact: 'admin@manual.config.relay', - supported_nips: [1, 2, 4, 9, 11, 12, 15, 16, 20, 22], + supported_nips: [1, 9, 11, 13, 15, 20, 33, 40, 42], software: 'https://github.com/0xtrr/c-relay', version: '1.0.0' }; @@ -1286,18 +1286,6 @@ console.log('Logout event handled successfully'); } - // Disconnect from relay and clean up connections - function disconnectFromRelay() { - if (relayPool) { - console.log('Cleaning up relay pool connection...'); - const url = relayConnectionUrl.value.trim(); - if (url) { - relayPool.close([url]); - } - relayPool = null; - subscriptionId = null; - } - } // Update visibility of admin sections based on login and relay connection status function updateAdminSectionsVisibility() { @@ -2030,56 +2018,33 @@ configForm.innerHTML = ''; - // Define field types and validation for different config parameters + // Define field types and validation for different config parameters (aligned with README.md) const fieldTypes = { 'auth_enabled': 'boolean', - 'nip42_auth_required_events': 'boolean', - 'nip42_auth_required_subscriptions': 'boolean', + 'nip42_auth_required': 'boolean', 'nip40_expiration_enabled': 'boolean', - 'nip40_expiration_strict': 'boolean', - 'nip40_expiration_filter': 'boolean', - 'relay_port': 'number', 'max_connections': 'number', 'pow_min_difficulty': 'number', - 'nip42_challenge_expiration': 'number', - 'nip40_expiration_grace_period': 'number', + 'nip42_challenge_timeout': 'number', 'max_subscriptions_per_client': 'number', - 'max_total_subscriptions': 'number', - 'max_filters_per_subscription': 'number', 'max_event_tags': 'number', - 'max_content_length': 'number', - 'max_message_length': 'number', - 'default_limit': 'number', - 'max_limit': 'number' + 'max_content_length': 'number' }; const descriptions = { 'relay_pubkey': 'Relay Public Key (Read-only)', 'auth_enabled': 'Enable Authentication', - 'nip42_auth_required_events': 'Require Auth for Events', - 'nip42_auth_required_subscriptions': 'Require Auth for Subscriptions', - 'nip42_auth_required_kinds': 'Auth Required Event Kinds', - 'nip42_challenge_expiration': 'Auth Challenge Expiration (seconds)', - 'relay_port': 'Relay Port', + 'nip42_auth_required': 'Enable NIP-42 Cryptographic Authentication', + 'nip42_auth_required_kinds': 'Event Kinds Requiring NIP-42 Auth', + 'nip42_challenge_timeout': 'NIP-42 Challenge Expiration Seconds', 'max_connections': 'Maximum Connections', 'relay_description': 'Relay Description', 'relay_contact': 'Relay Contact', - 'relay_software': 'Relay Software URL', - 'relay_version': 'Relay Version', - 'pow_min_difficulty': 'Minimum PoW Difficulty', - 'pow_mode': 'PoW Mode', + 'pow_min_difficulty': 'Minimum Proof-of-Work Difficulty', 'nip40_expiration_enabled': 'Enable Event Expiration', - 'nip40_expiration_strict': 'Strict Expiration Mode', - 'nip40_expiration_filter': 'Filter Expired Events', - 'nip40_expiration_grace_period': 'Expiration Grace Period (seconds)', 'max_subscriptions_per_client': 'Max Subscriptions per Client', - 'max_total_subscriptions': 'Max Total Subscriptions', - 'max_filters_per_subscription': 'Max Filters per Subscription', - 'max_event_tags': 'Max Event Tags', - 'max_content_length': 'Max Content Length', - 'max_message_length': 'Max Message Length', - 'default_limit': 'Default Query Limit', - 'max_limit': 'Maximum Query Limit' + 'max_event_tags': 'Maximum Tags per Event', + 'max_content_length': 'Maximum Event Content Length' }; // Process configuration tags (no d tag filtering for ephemeral events) @@ -3452,7 +3417,7 @@ logTestEvent('SENT', `Add Whitelist event: ${JSON.stringify(signedEvent)}`, 'EVENT'); // Publish via SimplePool - const url = relayUrl.value.trim(); + const url = relayConnectionUrl.value.trim(); const publishPromises = relayPool.publish([url], signedEvent); // Use Promise.allSettled to capture per-relay outcomes instead of Promise.any @@ -3594,7 +3559,7 @@ logTestEvent('SENT', `Signed test event: ${JSON.stringify(signedEvent)}`, 'EVENT'); // Publish via SimplePool to the same relay with detailed error diagnostics - const url = relayUrl.value.trim(); + const url = relayConnectionUrl.value.trim(); logTestEvent('INFO', `Publishing to relay: ${url}`, 'INFO'); const publishPromises = relayPool.publish([url], signedEvent); diff --git a/e1d3c52a4a330293e4de1327403a9d11057e9afabf61a89cd5a31375c96eacde.db b/e1d3c52a4a330293e4de1327403a9d11057e9afabf61a89cd5a31375c96eacde.db new file mode 100644 index 0000000..d1c5ad4 Binary files /dev/null and b/e1d3c52a4a330293e4de1327403a9d11057e9afabf61a89cd5a31375c96eacde.db differ diff --git a/nip11_relay_connection_implementation_plan.md b/nip11_relay_connection_implementation_plan.md deleted file mode 100644 index a860df3..0000000 --- a/nip11_relay_connection_implementation_plan.md +++ /dev/null @@ -1,455 +0,0 @@ -# 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

-
- - -
-
- - -
-
NOT CONNECTED
- - - -
-``` - -### 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 \ No newline at end of file diff --git a/relay.pid b/relay.pid index 7ccf87c..4a842f6 100644 --- a/relay.pid +++ b/relay.pid @@ -1 +1 @@ -1796483 +1878384 diff --git a/src/config.c b/src/config.c index f7c69be..1706337 100644 --- a/src/config.c +++ b/src/config.c @@ -917,10 +917,11 @@ cJSON* create_default_config_event(const unsigned char* admin_privkey_bytes, int first_time_startup_sequence(const cli_options_t* cli_options) { log_info("Starting first-time startup sequence..."); - + // 1. Generate or use provided admin keypair unsigned char admin_privkey_bytes[32]; char admin_privkey[65], admin_pubkey[65]; + int generated_admin_key = 0; // Track if we generated a new admin key if (cli_options && strlen(cli_options->admin_pubkey_override) == 64) { // Use provided admin public key directly - skip private key generation entirely @@ -943,6 +944,7 @@ int first_time_startup_sequence(const cli_options_t* cli_options) { // Set a dummy private key that will never be used (not displayed or stored) memset(admin_privkey_bytes, 0, 32); // Zero out for security memset(admin_privkey, 0, sizeof(admin_privkey)); // Zero out the hex string + generated_admin_key = 0; // Did not generate a new key } else { // Generate random admin keypair using /dev/urandom + nostr_core_lib log_info("Generating random admin keypair"); @@ -959,6 +961,7 @@ int first_time_startup_sequence(const cli_options_t* cli_options) { return -1; } nostr_bytes_to_hex(admin_pubkey_bytes, 32, admin_pubkey); + generated_admin_key = 1; // Generated a new key } // 2. Generate or use provided relay keypair @@ -1017,57 +1020,40 @@ int first_time_startup_sequence(const cli_options_t* cli_options) { g_temp_relay_privkey[sizeof(g_temp_relay_privkey) - 1] = '\0'; log_info("Relay private key cached for secure storage after database initialization"); - // 6. Handle configuration setup based on admin key availability - if (cli_options && strlen(cli_options->admin_pubkey_override) == 64) { - // Admin pubkey provided - will populate config table after database initialization - log_info("Admin pubkey provided - config table will be populated after database initialization"); + // 6. Handle configuration setup - defaults will be populated after database initialization + log_info("Configuration setup prepared - defaults will be populated after database initialization"); + + + // CLI overrides will be applied after database initialization in main.c + // This prevents "g_db is NULL" errors during first-time startup + + // 10. Print admin private key for user to save (only if we generated a new key) + if (generated_admin_key) { + printf("\n"); + printf("=================================================================\n"); + printf("IMPORTANT: SAVE THIS ADMIN PRIVATE KEY SECURELY!\n"); + printf("=================================================================\n"); + printf("Admin Private Key: %s\n", admin_privkey); + printf("Admin Public Key: %s\n", admin_pubkey); + printf("Relay Public Key: %s\n", relay_pubkey); + printf("\nDatabase: %s\n", g_database_path); + printf("\nThis admin private key is needed to update configuration!\n"); + printf("Store it safely - it will not be displayed again.\n"); + printf("=================================================================\n"); + printf("\n"); } else { - // Admin private key available - create signed configuration event - log_info("Admin private key available - creating signed configuration event"); - - // Create initial configuration event using defaults - cJSON* config_event = create_default_config_event(admin_privkey_bytes, relay_privkey, relay_pubkey, cli_options); - if (!config_event) { - log_error("Failed to create default configuration event"); - return -1; - } - - // Process configuration through admin API instead of storing in events table - if (process_startup_config_event_with_fallback(config_event) == 0) { - log_success("Initial configuration processed successfully through admin API"); - } else { - log_warning("Failed to process initial configuration - will retry after database init"); - // Cache the event for later processing - if (g_pending_config_event) { - cJSON_Delete(g_pending_config_event); - } - g_pending_config_event = cJSON_Duplicate(config_event, 1); - } - - // Cache the current config - if (g_current_config) { - cJSON_Delete(g_current_config); - } - g_current_config = cJSON_Duplicate(config_event, 1); - - // Clean up - cJSON_Delete(config_event); + printf("\n"); + printf("=================================================================\n"); + printf("RELAY STARTUP COMPLETE\n"); + printf("=================================================================\n"); + printf("Using provided admin public key for authentication\n"); + printf("Admin Public Key: %s\n", admin_pubkey); + printf("Relay Public Key: %s\n", relay_pubkey); + printf("\nDatabase: %s\n", g_database_path); + printf("=================================================================\n"); + printf("\n"); } - // 10. Print admin private key for user to save - printf("\n"); - printf("=================================================================\n"); - printf("IMPORTANT: SAVE THIS ADMIN PRIVATE KEY SECURELY!\n"); - printf("=================================================================\n"); - printf("Admin Private Key: %s\n", admin_privkey); - printf("Admin Public Key: %s\n", admin_pubkey); - printf("Relay Public Key: %s\n", relay_pubkey); - printf("\nDatabase: %s\n", g_database_path); - printf("\nThis admin private key is needed to update configuration!\n"); - printf("Store it safely - it will not be displayed again.\n"); - printf("=================================================================\n"); - printf("\n"); - log_success("First-time startup sequence completed"); return 0; } diff --git a/src/main.c b/src/main.c index eb57682..669dc55 100644 --- a/src/main.c +++ b/src/main.c @@ -348,18 +348,18 @@ int init_database(const char* database_path_override) { } if (!has_auth_rules) { - // Add auth_rules table + // Add auth_rules table matching sql_schema.h const char* create_auth_rules_sql = "CREATE TABLE IF NOT EXISTS auth_rules (" " id INTEGER PRIMARY KEY AUTOINCREMENT," - " rule_type TEXT NOT NULL," // 'pubkey_whitelist', 'pubkey_blacklist', 'hash_blacklist' - " operation TEXT NOT NULL," // 'event', 'event_kind_1', etc. - " rule_target TEXT NOT NULL," // pubkey, hash, or other identifier - " enabled INTEGER DEFAULT 1," // 0 = disabled, 1 = enabled - " priority INTEGER DEFAULT 1000," // Lower numbers = higher priority - " description TEXT," // Optional description - " created_at INTEGER DEFAULT (strftime('%s', 'now'))," - " UNIQUE(rule_type, operation, rule_target)" + " rule_type TEXT NOT NULL CHECK (rule_type IN ('whitelist', 'blacklist', 'rate_limit', 'auth_required'))," + " pattern_type TEXT NOT NULL CHECK (pattern_type IN ('pubkey', 'kind', 'ip', 'global'))," + " pattern_value TEXT," + " action TEXT NOT NULL CHECK (action IN ('allow', 'deny', 'require_auth', 'rate_limit'))," + " parameters TEXT," + " active INTEGER NOT NULL DEFAULT 1," + " created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))," + " updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))" ");"; char* error_msg = NULL; @@ -373,6 +373,24 @@ int init_database(const char* database_path_override) { return -1; } log_success("Created auth_rules table"); + + // Add indexes for auth_rules table + const char* create_auth_rules_indexes_sql = + "CREATE INDEX IF NOT EXISTS idx_auth_rules_pattern ON auth_rules(pattern_type, pattern_value);" + "CREATE INDEX IF NOT EXISTS idx_auth_rules_type ON auth_rules(rule_type);" + "CREATE INDEX IF NOT EXISTS idx_auth_rules_active ON auth_rules(active);"; + + char* index_error_msg = NULL; + int index_rc = sqlite3_exec(g_db, create_auth_rules_indexes_sql, NULL, NULL, &index_error_msg); + if (index_rc != SQLITE_OK) { + char index_error_log[512]; + snprintf(index_error_log, sizeof(index_error_log), "Failed to create auth_rules indexes: %s", + index_error_msg ? index_error_msg : "unknown error"); + log_error(index_error_log); + if (index_error_msg) sqlite3_free(index_error_msg); + return -1; + } + log_success("Created auth_rules indexes"); } else { log_info("auth_rules table already exists, skipping creation"); } @@ -1408,35 +1426,43 @@ int main(int argc, char* argv[]) { } // Handle configuration setup after database is initialized - if (cli_options.admin_pubkey_override && strlen(cli_options.admin_pubkey_override) == 64) { - // Admin pubkey provided - populate config table directly - log_info("Populating config table for admin pubkey override after database initialization"); + // Always populate defaults directly in config table (abandoning legacy event signing) + log_info("Populating config table with defaults after database initialization"); - // Populate default config values in table - if (populate_default_config_values() != 0) { - log_error("Failed to populate default config values"); - cleanup_configuration_system(); - nostr_cleanup(); - close_database(); - return 1; - } - - // Add pubkeys to config table - if (add_pubkeys_to_config_table() != 0) { - log_error("Failed to add pubkeys to config table"); - cleanup_configuration_system(); - nostr_cleanup(); - close_database(); - return 1; - } - - log_success("Configuration populated directly in config table after database initialization"); - } else { - // Admin private key available - retry storing initial config event - if (retry_store_initial_config_event() != 0) { - log_warning("Failed to store initial config event - will retry later"); - } + // Populate default config values in table + if (populate_default_config_values() != 0) { + log_error("Failed to populate default config values"); + cleanup_configuration_system(); + nostr_cleanup(); + close_database(); + return 1; } + + // Apply CLI overrides now that database is available + if (cli_options.port_override > 0) { + char port_str[16]; + snprintf(port_str, sizeof(port_str), "%d", cli_options.port_override); + if (update_config_in_table("relay_port", port_str) != 0) { + log_error("Failed to update relay port override in config table"); + cleanup_configuration_system(); + nostr_cleanup(); + close_database(); + return 1; + } + log_info("Applied port override from command line"); + printf(" Port: %d (overriding default)\n", cli_options.port_override); + } + + // Add pubkeys to config table + if (add_pubkeys_to_config_table() != 0) { + log_error("Failed to add pubkeys to config table"); + cleanup_configuration_system(); + nostr_cleanup(); + close_database(); + return 1; + } + + log_success("Configuration populated directly in config table after database initialization"); // Now store the pubkeys in config table since database is available const char* admin_pubkey = get_admin_pubkey_cached(); @@ -1539,6 +1565,21 @@ int main(int argc, char* argv[]) { log_warning("No configuration event found in existing database"); } + // Apply CLI overrides for existing relay (port override should work even for existing relays) + if (cli_options.port_override > 0) { + char port_str[16]; + snprintf(port_str, sizeof(port_str), "%d", cli_options.port_override); + if (update_config_in_table("relay_port", port_str) != 0) { + log_error("Failed to update relay port override in config table for existing relay"); + cleanup_configuration_system(); + nostr_cleanup(); + close_database(); + return 1; + } + log_info("Applied port override from command line for existing relay"); + printf(" Port: %d (overriding configured port)\n", cli_options.port_override); + } + // Free memory free(relay_pubkey); for (int i = 0; existing_files[i]; i++) { diff --git a/tests/event_config_tests.sh b/tests/event_config_tests.sh index 7f64c91..11e4bdc 100755 --- a/tests/event_config_tests.sh +++ b/tests/event_config_tests.sh @@ -310,8 +310,51 @@ else print_failure "Relay failed to start for network test" fi -# TEST 10: Multiple Startup Attempts (Port Conflict) -print_test_header "Test 10: Port Conflict Handling" +# TEST 10: Port Override with Admin/Relay Key Overrides +print_test_header "Test 10: Port Override with -a/-r Flags" + +cleanup_test_files + +# Generate test keys (64 hex chars each) +TEST_ADMIN_PUBKEY="1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" +TEST_RELAY_PRIVKEY="abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" + +print_info "Testing port override with -p 9999 -a $TEST_ADMIN_PUBKEY -r $TEST_RELAY_PRIVKEY" + +# Start relay with port override and key overrides +timeout 15 $RELAY_BINARY -p 9999 -a $TEST_ADMIN_PUBKEY -r $TEST_RELAY_PRIVKEY > "test_port_override.log" 2>&1 & +relay_pid=$! +sleep 5 + +if kill -0 $relay_pid 2>/dev/null; then + # Check if relay bound to port 9999 (not default 8888) + if netstat -tln 2>/dev/null | grep -q ":9999"; then + print_success "Relay successfully bound to overridden port 9999" + else + print_failure "Relay not bound to overridden port 9999" + fi + + # Check that relay started successfully + if check_relay_startup "test_port_override.log"; then + print_success "Relay startup completed with overrides" + else + print_failure "Relay failed to complete startup with overrides" + fi + + # Check that admin keys were NOT generated (since -a was provided) + if ! check_admin_keys "test_port_override.log"; then + print_success "Admin keys not generated (correctly using provided -a key)" + else + print_failure "Admin keys generated despite -a override" + fi + + stop_relay_test $relay_pid +else + print_failure "Relay failed to start with port/key overrides" +fi + +# TEST 11: Multiple Startup Attempts (Port Conflict) +print_test_header "Test 11: Port Conflict Handling" relay_pid1=$(start_relay_test "port_conflict_1" 10) sleep 2 @@ -320,14 +363,14 @@ if kill -0 $relay_pid1 2>/dev/null; then # Try to start a second relay (should fail due to port conflict) relay_pid2=$(start_relay_test "port_conflict_2" 5) sleep 1 - + if [ "$relay_pid2" = "0" ] || ! kill -0 $relay_pid2 2>/dev/null; then print_success "Port conflict properly handled (second instance failed to start)" else print_failure "Multiple relay instances started (port conflict not handled)" stop_relay_test $relay_pid2 fi - + stop_relay_test $relay_pid1 else print_failure "First relay instance failed to start"