Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3bab033ed | ||
|
|
524f9bd84f | ||
|
|
4658ede9d6 |
32
07.md
32
07.md
@@ -1,32 +0,0 @@
|
|||||||
NIP-07
|
|
||||||
======
|
|
||||||
|
|
||||||
`window.nostr` capability for web browsers
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
`draft` `optional`
|
|
||||||
|
|
||||||
The `window.nostr` object may be made available by web browsers or extensions and websites or web-apps may make use of it after checking its availability.
|
|
||||||
|
|
||||||
That object must define the following methods:
|
|
||||||
|
|
||||||
```
|
|
||||||
async window.nostr.getPublicKey(): string // returns a public key as hex
|
|
||||||
async window.nostr.signEvent(event: { created_at: number, kind: number, tags: string[][], content: string }): Event // takes an event object, adds `id`, `pubkey` and `sig` and returns it
|
|
||||||
```
|
|
||||||
|
|
||||||
Aside from these two basic above, the following functions can also be implemented optionally:
|
|
||||||
```
|
|
||||||
async window.nostr.nip04.encrypt(pubkey, plaintext): string // returns ciphertext and iv as specified in nip-04 (deprecated)
|
|
||||||
async window.nostr.nip04.decrypt(pubkey, ciphertext): string // takes ciphertext and iv as specified in nip-04 (deprecated)
|
|
||||||
async window.nostr.nip44.encrypt(pubkey, plaintext): string // returns ciphertext as specified in nip-44
|
|
||||||
async window.nostr.nip44.decrypt(pubkey, ciphertext): string // takes ciphertext as specified in nip-44
|
|
||||||
```
|
|
||||||
|
|
||||||
### Recommendation to Extension Authors
|
|
||||||
To make sure that the `window.nostr` is available to nostr clients on page load, the authors who create Chromium and Firefox extensions should load their scripts by specifying `"run_at": "document_end"` in the extension's manifest.
|
|
||||||
|
|
||||||
|
|
||||||
### Implementation
|
|
||||||
|
|
||||||
See https://github.com/aljazceru/awesome-nostr#nip-07-browser-extensions.
|
|
||||||
94
FIX_ME.md
Normal file
94
FIX_ME.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
Inconsistency audit with exact fixes (treating README.md as authoritative)
|
||||||
|
|
||||||
|
Backend auth_rules schema mismatch
|
||||||
|
Evidence:
|
||||||
|
Migration (creates mismatched columns):
|
||||||
|
See "CREATE TABLE IF NOT EXISTS auth_rules ... UNIQUE(rule_type, operation, rule_target)".
|
||||||
|
Active code uses rule_type, pattern_type, pattern_value, action:
|
||||||
|
Insert: "INSERT INTO auth_rules (rule_type, pattern_type, pattern_value, action)"
|
||||||
|
Delete: "DELETE FROM auth_rules WHERE rule_type = ? AND pattern_type = ? AND pattern_value = ?"
|
||||||
|
Query mapping: map_auth_query_type_to_response()
|
||||||
|
Queries: "... WHERE rule_type LIKE '%blacklist%'"
|
||||||
|
Validator checks:
|
||||||
|
"... WHERE rule_type = 'blacklist' AND pattern_type = 'pubkey' AND pattern_value = ?"
|
||||||
|
"... WHERE rule_type = 'blacklist' AND pattern_type = 'hash' AND pattern_value = ?"
|
||||||
|
"... WHERE rule_type = 'whitelist' AND pattern_type = 'pubkey' AND pattern_value = ?"
|
||||||
|
Embedded schema expects pattern columns and active/indexes:
|
||||||
|
"CREATE TABLE auth_rules ( ... )"
|
||||||
|
"CREATE INDEX idx_auth_rules_pattern ON auth_rules(pattern_type, pattern_value)"
|
||||||
|
"CREATE INDEX idx_auth_rules_active ON auth_rules(active)"
|
||||||
|
Fix (update migration to align with sql_schema.h/config.c):
|
||||||
|
Replace the DDL at "create_auth_rules_sql" with: CREATE TABLE IF NOT EXISTS auth_rules ( id INTEGER PRIMARY KEY AUTOINCREMENT, rule_type TEXT NOT NULL, -- 'whitelist' | 'blacklist' pattern_type TEXT NOT NULL, -- 'pubkey' | 'hash' | future pattern_value TEXT NOT NULL, -- hex pubkey/hash action TEXT NOT NULL, -- 'allow' | 'deny' active INTEGER DEFAULT 1, created_at INTEGER DEFAULT (strftime('%s','now')), UNIQUE(rule_type, pattern_type, pattern_value) );
|
||||||
|
After creation, also create indexes as in "sql_schema.h":
|
||||||
|
CREATE INDEX idx_auth_rules_pattern ON auth_rules(pattern_type, pattern_value);
|
||||||
|
CREATE INDEX idx_auth_rules_type ON auth_rules(rule_type);
|
||||||
|
CREATE INDEX idx_auth_rules_active ON auth_rules(active);
|
||||||
|
Duplicate UI function + stale DOM id usage
|
||||||
|
Evidence:
|
||||||
|
Duplicate definition of disconnectFromRelay() and disconnectFromRelay(); the second overwrites the first and uses legacy element access paths.
|
||||||
|
Stale variable: "const relayUrl = document.getElementById('relay-url');" — no element with id="relay-url" exists; the real input is "relay-connection-url" and is referenced as "relayConnectionUrl".
|
||||||
|
Calls using relayUrl.value.trim() (must use relayConnectionUrl):
|
||||||
|
"sendConfigUpdateCommand() publish URL"
|
||||||
|
"loadAuthRules() publish URL"
|
||||||
|
"deleteAuthRule() publish URL"
|
||||||
|
Tests:
|
||||||
|
"testGetAuthRules()"
|
||||||
|
"testClearAuthRules()"
|
||||||
|
"testAddBlacklist()"
|
||||||
|
"testAddWhitelist()"
|
||||||
|
"testConfigQuery()"
|
||||||
|
"testPostEvent()"
|
||||||
|
Fix:
|
||||||
|
Remove the duplicate legacy function entirely: delete the second disconnectFromRelay().
|
||||||
|
Remove stale variable: delete "const relayUrl = document.getElementById('relay-url');".
|
||||||
|
Replace every relayUrl.value.trim() occurrence with relayConnectionUrl.value.trim() at the lines listed above.
|
||||||
|
Supported NIPs inconsistency (README vs UI fallback)
|
||||||
|
Evidence:
|
||||||
|
README implemented NIPs checklist (authoritative): "NIPs list" shows: 1, 9, 11, 13, 15, 20, 33, 40, 42 implemented.
|
||||||
|
UI fallback for manual relay info includes unsupported/undocumented NIPs and misses implemented ones:
|
||||||
|
"supported_nips: [1, 2, 4, 9, 11, 12, 15, 16, 20, 22]"
|
||||||
|
"supported_nips: [1, 2, 4, 9, 11, 12, 15, 16, 20, 22]"
|
||||||
|
Fix:
|
||||||
|
Replace both arrays with: [1, 9, 11, 13, 15, 20, 33, 40, 42]
|
||||||
|
Config key mismatches (README vs UI edit form)
|
||||||
|
Evidence:
|
||||||
|
README keys (authoritative): "Available Configuration Keys"–(README.md:110)
|
||||||
|
relay_description, relay_contact, max_connections, max_subscriptions_per_client, max_event_tags, max_content_length, auth_enabled, nip42_auth_required, nip42_auth_required_kinds, nip42_challenge_timeout, pow_min_difficulty, nip40_expiration_enabled
|
||||||
|
UI currently declares/uses many non-README keys:
|
||||||
|
Field types: "fieldTypes" include nip42_auth_required_events, nip42_auth_required_subscriptions, relay_port, pow_mode, nip40_expiration_strict, nip40_expiration_filter, nip40_expiration_grace_period, max_total_subscriptions, max_filters_per_subscription, max_message_length, default_limit, max_limit.
|
||||||
|
Descriptions: "descriptions" reflect the same non-README keys.
|
||||||
|
Fix:
|
||||||
|
Restrict UI form generation to README keys and rename mismatches:
|
||||||
|
Combine nip42_auth_required_events/subscriptions into README’s "nip42_auth_required" (boolean).
|
||||||
|
Rename nip42_challenge_expiration to "nip42_challenge_timeout".
|
||||||
|
Remove or hide (advanced section) non-README keys: relay_port, pow_mode, nip40_expiration_strict, nip40_expiration_filter, nip40_expiration_grace_period, max_total_subscriptions, max_filters_per_subscription, max_message_length, default_limit, max_limit.
|
||||||
|
Update both "fieldTypes" and "descriptions" to reflect only README keys (data types and labels consistent).
|
||||||
|
First-time startup port override (-p) ignored when -a and -r are also provided
|
||||||
|
Observation:
|
||||||
|
You confirmed: first run with -p 7777 works, but with -p plus -a and -r the override isn’t honored.
|
||||||
|
Likely cause:
|
||||||
|
The code path that handles admin/relay key overrides on first-time setup bypasses persisting the CLI port override to config/unified cache before server start, so "start_websocket_relay(-1, ...)" falls back to default.
|
||||||
|
Fix:
|
||||||
|
Ensure first_time_startup_sequence applies cli_options.port_override to persistent config and cache BEFORE default config insertion and before starting the server. Specifically:
|
||||||
|
In the first-time path (main):
|
||||||
|
After "first_time_startup_sequence(&cli_options)" and before creating defaults on the -a/-r path at "populate_default_config_values()", write the port override:
|
||||||
|
set_config_value_in_table("relay_port", "<port>", "integer", "WebSocket port", "relay", 0);
|
||||||
|
and update unified cache if required by the port resolution code.
|
||||||
|
Verify the code path where -a/-r trigger direct table population also applies/overwrites the port with the CLI-provided value.
|
||||||
|
Add a regression test to assert that -p is honored with and without -a/-r on first run.
|
||||||
|
Minor consistency recommendations
|
||||||
|
UI NIP-11 fallback version string:
|
||||||
|
Consider aligning with backend version source (e.g., src/version.h). The UI currently hardcodes "1.0.0" at "version: '1.0.0'".
|
||||||
|
UI hardcoded relay pubkey fallback:
|
||||||
|
"getRelayPubkey()" returns a constant when not connected. Safe for dev, but should not leak into production paths.
|
||||||
|
Added TODO items (as requested)
|
||||||
|
|
||||||
|
The following todos were added/organized:
|
||||||
|
Remove duplicate disconnectFromRelay() and standardize to relay-connection-url
|
||||||
|
Replace all relayUrl.value references with relayConnectionUrl.value in api/index.html
|
||||||
|
Align Supported NIPs fallback arrays in api/index.html with README (1,9,11,13,15,20,33,40,42)
|
||||||
|
Update config form keys/descriptions in api/index.html to match README keys
|
||||||
|
Fix backend auth_rules migration in src/main.c to match src/sql_schema.h/src/config.c
|
||||||
|
Investigate and fix first-time startup port override ignored when -a and -r are provided
|
||||||
|
Add tests for port override and auth_rules flows
|
||||||
|
Rebuild via ./make_and_restart_relay.sh and validate against README
|
||||||
@@ -932,7 +932,7 @@
|
|||||||
description: 'C-Relay instance - pubkey provided manually',
|
description: 'C-Relay instance - pubkey provided manually',
|
||||||
pubkey: manualPubkey,
|
pubkey: manualPubkey,
|
||||||
contact: 'admin@manual.config.relay',
|
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',
|
software: 'https://github.com/0xtrr/c-relay',
|
||||||
version: '1.0.0'
|
version: '1.0.0'
|
||||||
};
|
};
|
||||||
@@ -958,7 +958,7 @@
|
|||||||
description: 'C-Relay instance - pubkey provided manually',
|
description: 'C-Relay instance - pubkey provided manually',
|
||||||
pubkey: manualPubkey,
|
pubkey: manualPubkey,
|
||||||
contact: 'admin@manual.config.relay',
|
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',
|
software: 'https://github.com/0xtrr/c-relay',
|
||||||
version: '1.0.0'
|
version: '1.0.0'
|
||||||
};
|
};
|
||||||
@@ -1286,18 +1286,6 @@
|
|||||||
console.log('Logout event handled successfully');
|
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
|
// Update visibility of admin sections based on login and relay connection status
|
||||||
function updateAdminSectionsVisibility() {
|
function updateAdminSectionsVisibility() {
|
||||||
@@ -2030,56 +2018,33 @@
|
|||||||
|
|
||||||
configForm.innerHTML = '';
|
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 = {
|
const fieldTypes = {
|
||||||
'auth_enabled': 'boolean',
|
'auth_enabled': 'boolean',
|
||||||
'nip42_auth_required_events': 'boolean',
|
'nip42_auth_required': 'boolean',
|
||||||
'nip42_auth_required_subscriptions': 'boolean',
|
|
||||||
'nip40_expiration_enabled': 'boolean',
|
'nip40_expiration_enabled': 'boolean',
|
||||||
'nip40_expiration_strict': 'boolean',
|
|
||||||
'nip40_expiration_filter': 'boolean',
|
|
||||||
'relay_port': 'number',
|
|
||||||
'max_connections': 'number',
|
'max_connections': 'number',
|
||||||
'pow_min_difficulty': 'number',
|
'pow_min_difficulty': 'number',
|
||||||
'nip42_challenge_expiration': 'number',
|
'nip42_challenge_timeout': 'number',
|
||||||
'nip40_expiration_grace_period': 'number',
|
|
||||||
'max_subscriptions_per_client': 'number',
|
'max_subscriptions_per_client': 'number',
|
||||||
'max_total_subscriptions': 'number',
|
|
||||||
'max_filters_per_subscription': 'number',
|
|
||||||
'max_event_tags': 'number',
|
'max_event_tags': 'number',
|
||||||
'max_content_length': 'number',
|
'max_content_length': 'number'
|
||||||
'max_message_length': 'number',
|
|
||||||
'default_limit': 'number',
|
|
||||||
'max_limit': 'number'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const descriptions = {
|
const descriptions = {
|
||||||
'relay_pubkey': 'Relay Public Key (Read-only)',
|
'relay_pubkey': 'Relay Public Key (Read-only)',
|
||||||
'auth_enabled': 'Enable Authentication',
|
'auth_enabled': 'Enable Authentication',
|
||||||
'nip42_auth_required_events': 'Require Auth for Events',
|
'nip42_auth_required': 'Enable NIP-42 Cryptographic Authentication',
|
||||||
'nip42_auth_required_subscriptions': 'Require Auth for Subscriptions',
|
'nip42_auth_required_kinds': 'Event Kinds Requiring NIP-42 Auth',
|
||||||
'nip42_auth_required_kinds': 'Auth Required Event Kinds',
|
'nip42_challenge_timeout': 'NIP-42 Challenge Expiration Seconds',
|
||||||
'nip42_challenge_expiration': 'Auth Challenge Expiration (seconds)',
|
|
||||||
'relay_port': 'Relay Port',
|
|
||||||
'max_connections': 'Maximum Connections',
|
'max_connections': 'Maximum Connections',
|
||||||
'relay_description': 'Relay Description',
|
'relay_description': 'Relay Description',
|
||||||
'relay_contact': 'Relay Contact',
|
'relay_contact': 'Relay Contact',
|
||||||
'relay_software': 'Relay Software URL',
|
'pow_min_difficulty': 'Minimum Proof-of-Work Difficulty',
|
||||||
'relay_version': 'Relay Version',
|
|
||||||
'pow_min_difficulty': 'Minimum PoW Difficulty',
|
|
||||||
'pow_mode': 'PoW Mode',
|
|
||||||
'nip40_expiration_enabled': 'Enable Event Expiration',
|
'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_subscriptions_per_client': 'Max Subscriptions per Client',
|
||||||
'max_total_subscriptions': 'Max Total Subscriptions',
|
'max_event_tags': 'Maximum Tags per Event',
|
||||||
'max_filters_per_subscription': 'Max Filters per Subscription',
|
'max_content_length': 'Maximum Event Content Length'
|
||||||
'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'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Process configuration tags (no d tag filtering for ephemeral events)
|
// Process configuration tags (no d tag filtering for ephemeral events)
|
||||||
@@ -3452,7 +3417,7 @@
|
|||||||
logTestEvent('SENT', `Add Whitelist event: ${JSON.stringify(signedEvent)}`, 'EVENT');
|
logTestEvent('SENT', `Add Whitelist event: ${JSON.stringify(signedEvent)}`, 'EVENT');
|
||||||
|
|
||||||
// Publish via SimplePool
|
// Publish via SimplePool
|
||||||
const url = relayUrl.value.trim();
|
const url = relayConnectionUrl.value.trim();
|
||||||
const publishPromises = relayPool.publish([url], signedEvent);
|
const publishPromises = relayPool.publish([url], signedEvent);
|
||||||
|
|
||||||
// Use Promise.allSettled to capture per-relay outcomes instead of Promise.any
|
// 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');
|
logTestEvent('SENT', `Signed test event: ${JSON.stringify(signedEvent)}`, 'EVENT');
|
||||||
|
|
||||||
// Publish via SimplePool to the same relay with detailed error diagnostics
|
// 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');
|
logTestEvent('INFO', `Publishing to relay: ${url}`, 'INFO');
|
||||||
|
|
||||||
const publishPromises = relayPool.publish([url], signedEvent);
|
const publishPromises = relayPool.publish([url], signedEvent);
|
||||||
|
|||||||
Binary file not shown.
@@ -282,7 +282,7 @@ cd build
|
|||||||
# Start relay in background and capture its PID
|
# Start relay in background and capture its PID
|
||||||
if [ "$USE_TEST_KEYS" = true ]; then
|
if [ "$USE_TEST_KEYS" = true ]; then
|
||||||
echo "Using deterministic test keys for development..."
|
echo "Using deterministic test keys for development..."
|
||||||
./$(basename $BINARY_PATH) -a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -r 1111111111111111111111111111111111111111111111111111111111111111 --strict-port > ../relay.log 2>&1 &
|
./$(basename $BINARY_PATH) -a 6a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3 -r 1111111111111111111111111111111111111111111111111111111111111111 --strict-port > ../relay.log 2>&1 &
|
||||||
elif [ -n "$RELAY_ARGS" ]; then
|
elif [ -n "$RELAY_ARGS" ]; then
|
||||||
echo "Starting relay with custom configuration..."
|
echo "Starting relay with custom configuration..."
|
||||||
./$(basename $BINARY_PATH) $RELAY_ARGS --strict-port > ../relay.log 2>&1 &
|
./$(basename $BINARY_PATH) $RELAY_ARGS --strict-port > ../relay.log 2>&1 &
|
||||||
|
|||||||
@@ -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 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
|
|
||||||
117
src/config.c
117
src/config.c
@@ -921,39 +921,48 @@ int first_time_startup_sequence(const cli_options_t* cli_options) {
|
|||||||
// 1. Generate or use provided admin keypair
|
// 1. Generate or use provided admin keypair
|
||||||
unsigned char admin_privkey_bytes[32];
|
unsigned char admin_privkey_bytes[32];
|
||||||
char admin_privkey[65], admin_pubkey[65];
|
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_privkey_override) == 64) {
|
if (cli_options && strlen(cli_options->admin_pubkey_override) == 64) {
|
||||||
// Use provided admin private key
|
// Use provided admin public key directly - skip private key generation entirely
|
||||||
log_info("Using provided admin private key override");
|
log_info("Using provided admin public key override - skipping private key generation");
|
||||||
strncpy(admin_privkey, cli_options->admin_privkey_override, sizeof(admin_privkey) - 1);
|
strncpy(admin_pubkey, cli_options->admin_pubkey_override, sizeof(admin_pubkey) - 1);
|
||||||
admin_privkey[sizeof(admin_privkey) - 1] = '\0';
|
admin_pubkey[sizeof(admin_pubkey) - 1] = '\0';
|
||||||
|
|
||||||
// Convert hex string to bytes
|
// Validate the public key format (must be 64 hex characters)
|
||||||
if (nostr_hex_to_bytes(admin_privkey, admin_privkey_bytes, 32) != NOSTR_SUCCESS) {
|
for (int i = 0; i < 64; i++) {
|
||||||
log_error("Failed to convert admin private key hex to bytes");
|
char c = admin_pubkey[i];
|
||||||
return -1;
|
if (!((c >= '0' && c <= '9') ||
|
||||||
|
(c >= 'a' && c <= 'f') ||
|
||||||
|
(c >= 'A' && c <= 'F'))) {
|
||||||
|
log_error("Invalid admin public key format - must contain only hex characters");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the private key
|
// Skip private key generation - we only need the pubkey for admin verification
|
||||||
if (nostr_ec_private_key_verify(admin_privkey_bytes) != NOSTR_SUCCESS) {
|
// Set a dummy private key that will never be used (not displayed or stored)
|
||||||
log_error("Provided admin private key is invalid");
|
memset(admin_privkey_bytes, 0, 32); // Zero out for security
|
||||||
return -1;
|
memset(admin_privkey, 0, sizeof(admin_privkey)); // Zero out the hex string
|
||||||
}
|
generated_admin_key = 0; // Did not generate a new key
|
||||||
} else {
|
} else {
|
||||||
// Generate random admin keypair using /dev/urandom + nostr_core_lib
|
// Generate random admin keypair using /dev/urandom + nostr_core_lib
|
||||||
|
log_info("Generating random admin keypair");
|
||||||
if (generate_random_private_key_bytes(admin_privkey_bytes) != 0) {
|
if (generate_random_private_key_bytes(admin_privkey_bytes) != 0) {
|
||||||
log_error("Failed to generate admin private key");
|
log_error("Failed to generate admin private key");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
nostr_bytes_to_hex(admin_privkey_bytes, 32, admin_privkey);
|
nostr_bytes_to_hex(admin_privkey_bytes, 32, admin_privkey);
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char admin_pubkey_bytes[32];
|
// Derive public key from private key
|
||||||
if (nostr_ec_public_key_from_private_key(admin_privkey_bytes, admin_pubkey_bytes) != NOSTR_SUCCESS) {
|
unsigned char admin_pubkey_bytes[32];
|
||||||
log_error("Failed to derive admin public key");
|
if (nostr_ec_public_key_from_private_key(admin_privkey_bytes, admin_pubkey_bytes) != NOSTR_SUCCESS) {
|
||||||
return -1;
|
log_error("Failed to derive admin public key");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
nostr_bytes_to_hex(admin_pubkey_bytes, 32, admin_pubkey);
|
||||||
|
generated_admin_key = 1; // Generated a new key
|
||||||
}
|
}
|
||||||
nostr_bytes_to_hex(admin_pubkey_bytes, 32, admin_pubkey);
|
|
||||||
|
|
||||||
// 2. Generate or use provided relay keypair
|
// 2. Generate or use provided relay keypair
|
||||||
unsigned char relay_privkey_bytes[32];
|
unsigned char relay_privkey_bytes[32];
|
||||||
@@ -1011,48 +1020,40 @@ int first_time_startup_sequence(const cli_options_t* cli_options) {
|
|||||||
g_temp_relay_privkey[sizeof(g_temp_relay_privkey) - 1] = '\0';
|
g_temp_relay_privkey[sizeof(g_temp_relay_privkey) - 1] = '\0';
|
||||||
log_info("Relay private key cached for secure storage after database initialization");
|
log_info("Relay private key cached for secure storage after database initialization");
|
||||||
|
|
||||||
// 6. Create initial configuration event using defaults (without private key)
|
// 6. Handle configuration setup - defaults will be populated after database initialization
|
||||||
cJSON* config_event = create_default_config_event(admin_privkey_bytes, relay_privkey, relay_pubkey, cli_options);
|
log_info("Configuration setup prepared - defaults will be populated after database initialization");
|
||||||
if (!config_event) {
|
|
||||||
log_error("Failed to create default configuration event");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. Process configuration through admin API instead of storing in events table
|
|
||||||
if (process_startup_config_event_with_fallback(config_event) == 0) {
|
// CLI overrides will be applied after database initialization in main.c
|
||||||
log_success("Initial configuration processed successfully through admin API");
|
// 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 {
|
} else {
|
||||||
log_warning("Failed to process initial configuration - will retry after database init");
|
printf("\n");
|
||||||
// Cache the event for later processing
|
printf("=================================================================\n");
|
||||||
if (g_pending_config_event) {
|
printf("RELAY STARTUP COMPLETE\n");
|
||||||
cJSON_Delete(g_pending_config_event);
|
printf("=================================================================\n");
|
||||||
}
|
printf("Using provided admin public key for authentication\n");
|
||||||
g_pending_config_event = cJSON_Duplicate(config_event, 1);
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8. Cache the current config
|
|
||||||
if (g_current_config) {
|
|
||||||
cJSON_Delete(g_current_config);
|
|
||||||
}
|
|
||||||
g_current_config = cJSON_Duplicate(config_event, 1);
|
|
||||||
|
|
||||||
// 9. Clean up
|
|
||||||
cJSON_Delete(config_event);
|
|
||||||
|
|
||||||
// 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");
|
log_success("First-time startup sequence completed");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ typedef struct {
|
|||||||
// Command line options structure for first-time startup
|
// Command line options structure for first-time startup
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int port_override; // -1 = not set, >0 = port value
|
int port_override; // -1 = not set, >0 = port value
|
||||||
char admin_privkey_override[65]; // Empty string = not set, 64-char hex = override
|
char admin_pubkey_override[65]; // Empty string = not set, 64-char hex = override
|
||||||
char relay_privkey_override[65]; // Empty string = not set, 64-char hex = override
|
char relay_privkey_override[65]; // Empty string = not set, 64-char hex = override
|
||||||
int strict_port; // 0 = allow port increment, 1 = fail if exact port unavailable
|
int strict_port; // 0 = allow port increment, 1 = fail if exact port unavailable
|
||||||
} cli_options_t;
|
} cli_options_t;
|
||||||
|
|||||||
128
src/main.c
128
src/main.c
@@ -348,18 +348,18 @@ int init_database(const char* database_path_override) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!has_auth_rules) {
|
if (!has_auth_rules) {
|
||||||
// Add auth_rules table
|
// Add auth_rules table matching sql_schema.h
|
||||||
const char* create_auth_rules_sql =
|
const char* create_auth_rules_sql =
|
||||||
"CREATE TABLE IF NOT EXISTS auth_rules ("
|
"CREATE TABLE IF NOT EXISTS auth_rules ("
|
||||||
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||||
" rule_type TEXT NOT NULL," // 'pubkey_whitelist', 'pubkey_blacklist', 'hash_blacklist'
|
" rule_type TEXT NOT NULL CHECK (rule_type IN ('whitelist', 'blacklist', 'rate_limit', 'auth_required')),"
|
||||||
" operation TEXT NOT NULL," // 'event', 'event_kind_1', etc.
|
" pattern_type TEXT NOT NULL CHECK (pattern_type IN ('pubkey', 'kind', 'ip', 'global')),"
|
||||||
" rule_target TEXT NOT NULL," // pubkey, hash, or other identifier
|
" pattern_value TEXT,"
|
||||||
" enabled INTEGER DEFAULT 1," // 0 = disabled, 1 = enabled
|
" action TEXT NOT NULL CHECK (action IN ('allow', 'deny', 'require_auth', 'rate_limit')),"
|
||||||
" priority INTEGER DEFAULT 1000," // Lower numbers = higher priority
|
" parameters TEXT,"
|
||||||
" description TEXT," // Optional description
|
" active INTEGER NOT NULL DEFAULT 1,"
|
||||||
" created_at INTEGER DEFAULT (strftime('%s', 'now')),"
|
" created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),"
|
||||||
" UNIQUE(rule_type, operation, rule_target)"
|
" updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))"
|
||||||
");";
|
");";
|
||||||
|
|
||||||
char* error_msg = NULL;
|
char* error_msg = NULL;
|
||||||
@@ -373,6 +373,24 @@ int init_database(const char* database_path_override) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
log_success("Created auth_rules table");
|
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 {
|
} else {
|
||||||
log_info("auth_rules table already exists, skipping creation");
|
log_info("auth_rules table already exists, skipping creation");
|
||||||
}
|
}
|
||||||
@@ -1204,9 +1222,9 @@ void print_usage(const char* program_name) {
|
|||||||
printf(" -h, --help Show this help message\n");
|
printf(" -h, --help Show this help message\n");
|
||||||
printf(" -v, --version Show version information\n");
|
printf(" -v, --version Show version information\n");
|
||||||
printf(" -p, --port PORT Override relay port (first-time startup only)\n");
|
printf(" -p, --port PORT Override relay port (first-time startup only)\n");
|
||||||
printf(" -a, --admin-privkey HEX Override admin private key (64-char hex)\n");
|
|
||||||
printf(" -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(" --strict-port Fail if exact port is unavailable (no port increment)\n");
|
||||||
|
printf(" -a, --admin-pubkey HEX Override admin public key (64-char hex)\n");
|
||||||
|
printf(" -r, --relay-privkey HEX Override relay private key (64-char hex)\n");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("Configuration:\n");
|
printf("Configuration:\n");
|
||||||
printf(" This relay uses event-based configuration stored in the database.\n");
|
printf(" This relay uses event-based configuration stored in the database.\n");
|
||||||
@@ -1221,12 +1239,12 @@ void print_usage(const char* program_name) {
|
|||||||
printf("\n");
|
printf("\n");
|
||||||
printf("Examples:\n");
|
printf("Examples:\n");
|
||||||
printf(" %s # Start relay (auto-configure on first run)\n", program_name);
|
printf(" %s # Start relay (auto-configure on first run)\n", program_name);
|
||||||
printf(" %s -p 8080 # First-time setup with port 8080\n", program_name);
|
printf(" %s -p 8080 # First-time setup with port 8080\n", program_name);
|
||||||
printf(" %s --port 9000 # First-time setup with port 9000\n", program_name);
|
printf(" %s --port 9000 # First-time setup with port 9000\n", program_name);
|
||||||
printf(" %s --strict-port # Fail if default port 8888 is unavailable\n", program_name);
|
printf(" %s --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 -p 8080 --strict-port # Fail if port 8080 is unavailable\n", program_name);
|
||||||
printf(" %s --help # Show this help\n", program_name);
|
printf(" %s --help # Show this help\n", program_name);
|
||||||
printf(" %s --version # Show version info\n", program_name);
|
printf(" %s --version # Show version info\n", program_name);
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1242,7 +1260,7 @@ int main(int argc, char* argv[]) {
|
|||||||
// Initialize CLI options structure
|
// Initialize CLI options structure
|
||||||
cli_options_t cli_options = {
|
cli_options_t cli_options = {
|
||||||
.port_override = -1, // -1 = not set
|
.port_override = -1, // -1 = not set
|
||||||
.admin_privkey_override = {0}, // Empty string = not set
|
.admin_pubkey_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)
|
.strict_port = 0 // 0 = allow port increment (default)
|
||||||
};
|
};
|
||||||
@@ -1279,17 +1297,17 @@ int main(int argc, char* argv[]) {
|
|||||||
char port_msg[128];
|
char port_msg[128];
|
||||||
snprintf(port_msg, sizeof(port_msg), "Port override specified: %d", cli_options.port_override);
|
snprintf(port_msg, sizeof(port_msg), "Port override specified: %d", cli_options.port_override);
|
||||||
log_info(port_msg);
|
log_info(port_msg);
|
||||||
} else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--admin-privkey") == 0) {
|
} else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--admin-pubkey") == 0) {
|
||||||
// Admin private key override option
|
// Admin public key override option
|
||||||
if (i + 1 >= argc) {
|
if (i + 1 >= argc) {
|
||||||
log_error("Admin privkey option requires a value. Use --help for usage information.");
|
log_error("Admin pubkey option requires a value. Use --help for usage information.");
|
||||||
print_usage(argv[0]);
|
print_usage(argv[0]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate private key format (must be 64 hex characters)
|
// Validate public key format (must be 64 hex characters)
|
||||||
if (strlen(argv[i + 1]) != 64) {
|
if (strlen(argv[i + 1]) != 64) {
|
||||||
log_error("Invalid admin private key length. Must be exactly 64 hex characters.");
|
log_error("Invalid admin public key length. Must be exactly 64 hex characters.");
|
||||||
print_usage(argv[0]);
|
print_usage(argv[0]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -1298,17 +1316,17 @@ int main(int argc, char* argv[]) {
|
|||||||
for (int j = 0; j < 64; j++) {
|
for (int j = 0; j < 64; j++) {
|
||||||
char c = argv[i + 1][j];
|
char c = argv[i + 1][j];
|
||||||
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
|
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
|
||||||
log_error("Invalid admin private key format. Must contain only hex characters (0-9, a-f, A-F).");
|
log_error("Invalid admin public key format. Must contain only hex characters (0-9, a-f, A-F).");
|
||||||
print_usage(argv[0]);
|
print_usage(argv[0]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
strncpy(cli_options.admin_privkey_override, argv[i + 1], sizeof(cli_options.admin_privkey_override) - 1);
|
strncpy(cli_options.admin_pubkey_override, argv[i + 1], sizeof(cli_options.admin_pubkey_override) - 1);
|
||||||
cli_options.admin_privkey_override[sizeof(cli_options.admin_privkey_override) - 1] = '\0';
|
cli_options.admin_pubkey_override[sizeof(cli_options.admin_pubkey_override) - 1] = '\0';
|
||||||
i++; // Skip the key argument
|
i++; // Skip the key argument
|
||||||
|
|
||||||
log_info("Admin private key override specified");
|
log_info("Admin public key override specified");
|
||||||
} else if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--relay-privkey") == 0) {
|
} else if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--relay-privkey") == 0) {
|
||||||
// Relay private key override option
|
// Relay private key override option
|
||||||
if (i + 1 >= argc) {
|
if (i + 1 >= argc) {
|
||||||
@@ -1407,18 +1425,45 @@ int main(int argc, char* argv[]) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Systematically add pubkeys to config table
|
// Handle configuration setup after database is initialized
|
||||||
if (add_pubkeys_to_config_table() != 0) {
|
// Always populate defaults directly in config table (abandoning legacy event signing)
|
||||||
log_warning("Failed to add pubkeys to config table systematically");
|
log_info("Populating config table with defaults after database initialization");
|
||||||
} else {
|
|
||||||
log_success("Pubkeys added to config table systematically");
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retry storing the configuration event now that database is initialized
|
// Apply CLI overrides now that database is available
|
||||||
if (retry_store_initial_config_event() != 0) {
|
if (cli_options.port_override > 0) {
|
||||||
log_warning("Failed to store initial configuration event after database init");
|
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
|
// Now store the pubkeys in config table since database is available
|
||||||
const char* admin_pubkey = get_admin_pubkey_cached();
|
const char* admin_pubkey = get_admin_pubkey_cached();
|
||||||
const char* relay_pubkey_from_cache = get_relay_pubkey_cached();
|
const char* relay_pubkey_from_cache = get_relay_pubkey_cached();
|
||||||
@@ -1520,6 +1565,21 @@ int main(int argc, char* argv[]) {
|
|||||||
log_warning("No configuration event found in existing database");
|
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 memory
|
||||||
free(relay_pubkey);
|
free(relay_pubkey);
|
||||||
for (int i = 0; existing_files[i]; i++) {
|
for (int i = 0; existing_files[i]; i++) {
|
||||||
|
|||||||
@@ -310,8 +310,51 @@ else
|
|||||||
print_failure "Relay failed to start for network test"
|
print_failure "Relay failed to start for network test"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# TEST 10: Multiple Startup Attempts (Port Conflict)
|
# TEST 10: Port Override with Admin/Relay Key Overrides
|
||||||
print_test_header "Test 10: Port Conflict Handling"
|
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)
|
relay_pid1=$(start_relay_test "port_conflict_1" 10)
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|||||||
@@ -166,6 +166,81 @@ add_to_blacklist() {
|
|||||||
sleep 3
|
sleep 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Send admin command to add user to whitelist
|
||||||
|
add_to_whitelist() {
|
||||||
|
local pubkey="$1"
|
||||||
|
log_info "Adding pubkey to whitelist: ${pubkey:0:16}..."
|
||||||
|
|
||||||
|
# Create the admin command
|
||||||
|
COMMAND="[\"whitelist\", \"pubkey\", \"$pubkey\"]"
|
||||||
|
|
||||||
|
# Encrypt the command using NIP-44
|
||||||
|
ENCRYPTED_COMMAND=$(nak encrypt "$COMMAND" \
|
||||||
|
--sec "$ADMIN_PRIVKEY" \
|
||||||
|
--recipient-pubkey "$RELAY_PUBKEY")
|
||||||
|
|
||||||
|
if [ -z "$ENCRYPTED_COMMAND" ]; then
|
||||||
|
log_error "Failed to encrypt admin command"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create admin event
|
||||||
|
ADMIN_EVENT=$(nak event \
|
||||||
|
--kind 23456 \
|
||||||
|
--content "$ENCRYPTED_COMMAND" \
|
||||||
|
--sec "$ADMIN_PRIVKEY" \
|
||||||
|
--tag "p=$RELAY_PUBKEY")
|
||||||
|
|
||||||
|
# Post admin event
|
||||||
|
ADMIN_RESULT=$(echo "$ADMIN_EVENT" | nak event "$RELAY_URL")
|
||||||
|
|
||||||
|
if echo "$ADMIN_RESULT" | grep -q "error\|failed\|denied"; then
|
||||||
|
log_error "Failed to send admin command: $ADMIN_RESULT"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Admin command sent successfully - user added to whitelist"
|
||||||
|
# Wait for the relay to process the admin command
|
||||||
|
sleep 3
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clear all auth rules
|
||||||
|
clear_auth_rules() {
|
||||||
|
log_info "Clearing all auth rules..."
|
||||||
|
|
||||||
|
# Create the admin command
|
||||||
|
COMMAND="[\"system_command\", \"clear_all_auth_rules\"]"
|
||||||
|
|
||||||
|
# Encrypt the command using NIP-44
|
||||||
|
ENCRYPTED_COMMAND=$(nak encrypt "$COMMAND" \
|
||||||
|
--sec "$ADMIN_PRIVKEY" \
|
||||||
|
--recipient-pubkey "$RELAY_PUBKEY")
|
||||||
|
|
||||||
|
if [ -z "$ENCRYPTED_COMMAND" ]; then
|
||||||
|
log_error "Failed to encrypt admin command"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create admin event
|
||||||
|
ADMIN_EVENT=$(nak event \
|
||||||
|
--kind 23456 \
|
||||||
|
--content "$ENCRYPTED_COMMAND" \
|
||||||
|
--sec "$ADMIN_PRIVKEY" \
|
||||||
|
--tag "p=$RELAY_PUBKEY")
|
||||||
|
|
||||||
|
# Post admin event
|
||||||
|
ADMIN_RESULT=$(echo "$ADMIN_EVENT" | nak event "$RELAY_URL")
|
||||||
|
|
||||||
|
if echo "$ADMIN_RESULT" | grep -q "error\|failed\|denied"; then
|
||||||
|
log_error "Failed to send admin command: $ADMIN_RESULT"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Admin command sent successfully - all auth rules cleared"
|
||||||
|
# Wait for the relay to process the admin command
|
||||||
|
sleep 3
|
||||||
|
}
|
||||||
|
|
||||||
# Test 2: Try to post after blacklisting
|
# Test 2: Try to post after blacklisting
|
||||||
test_blacklist_post() {
|
test_blacklist_post() {
|
||||||
log_info "=== TEST 2: Attempt to post event after blacklisting ==="
|
log_info "=== TEST 2: Attempt to post event after blacklisting ==="
|
||||||
@@ -199,6 +274,92 @@ test_blacklist_post() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Test 3: Test whitelist functionality
|
||||||
|
test_whitelist_functionality() {
|
||||||
|
log_info "=== TEST 3: Test whitelist functionality ==="
|
||||||
|
|
||||||
|
# Generate a second test keypair for whitelist testing
|
||||||
|
log_info "Generating second test keypair for whitelist testing..."
|
||||||
|
WHITELIST_PRIVKEY=$(nak key generate 2>/dev/null)
|
||||||
|
WHITELIST_PUBKEY=$(nak key public "$WHITELIST_PRIVKEY" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -z "$WHITELIST_PUBKEY" ]; then
|
||||||
|
log_error "Failed to generate whitelist test keypair"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Generated whitelist test keypair: ${WHITELIST_PUBKEY:0:16}..."
|
||||||
|
|
||||||
|
# Clear all auth rules first
|
||||||
|
if ! clear_auth_rules; then
|
||||||
|
log_error "Failed to clear auth rules for whitelist test"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add the whitelist user to whitelist
|
||||||
|
if ! add_to_whitelist "$WHITELIST_PUBKEY"; then
|
||||||
|
log_error "Failed to add whitelist user"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 3a: Original test user should be blocked (not whitelisted)
|
||||||
|
log_info "Testing that non-whitelisted user is blocked..."
|
||||||
|
local timestamp=$(date +%s)
|
||||||
|
local content="Non-whitelisted test event at timestamp $timestamp"
|
||||||
|
|
||||||
|
NON_WHITELIST_EVENT=$(nak event \
|
||||||
|
--kind 1 \
|
||||||
|
--content "$content" \
|
||||||
|
--sec "$TEST_PRIVKEY" \
|
||||||
|
--tag 't=whitelist-test')
|
||||||
|
|
||||||
|
POST_RESULT=$(echo "$NON_WHITELIST_EVENT" | nak event "$RELAY_URL" 2>&1)
|
||||||
|
|
||||||
|
if echo "$POST_RESULT" | grep -q "error\|failed\|denied\|blocked"; then
|
||||||
|
log_success "Non-whitelisted user correctly blocked"
|
||||||
|
else
|
||||||
|
log_error "Non-whitelisted user was not blocked - whitelist may not be working"
|
||||||
|
log_error "Post result: $POST_RESULT"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test 3b: Whitelisted user should be allowed
|
||||||
|
log_info "Testing that whitelisted user can post..."
|
||||||
|
content="Whitelisted test event at timestamp $timestamp"
|
||||||
|
|
||||||
|
WHITELIST_EVENT=$(nak event \
|
||||||
|
--kind 1 \
|
||||||
|
--content "$content" \
|
||||||
|
--sec "$WHITELIST_PRIVKEY" \
|
||||||
|
--tag 't=whitelist-test')
|
||||||
|
|
||||||
|
POST_RESULT=$(echo "$WHITELIST_EVENT" | nak event "$RELAY_URL" 2>&1)
|
||||||
|
|
||||||
|
if echo "$POST_RESULT" | grep -q "error\|failed\|denied\|blocked"; then
|
||||||
|
log_error "Whitelisted user was blocked - whitelist not working correctly"
|
||||||
|
log_error "Post result: $POST_RESULT"
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
log_success "Whitelisted user can post successfully"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify the whitelisted event can be retrieved
|
||||||
|
WHITELIST_EVENT_ID=$(echo "$WHITELIST_EVENT" | jq -r '.id')
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
RETRIEVE_RESULT=$(nak req \
|
||||||
|
--id "$WHITELIST_EVENT_ID" \
|
||||||
|
"$RELAY_URL")
|
||||||
|
|
||||||
|
if echo "$RETRIEVE_RESULT" | grep -q "$WHITELIST_EVENT_ID"; then
|
||||||
|
log_success "Whitelisted event successfully retrieved"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_error "Failed to retrieve whitelisted event"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Main test function
|
# Main test function
|
||||||
main() {
|
main() {
|
||||||
log_info "Starting C-Relay Whitelist/Blacklist Test"
|
log_info "Starting C-Relay Whitelist/Blacklist Test"
|
||||||
@@ -237,6 +398,14 @@ main() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Test 3: Test whitelist functionality
|
||||||
|
if test_whitelist_functionality; then
|
||||||
|
log_success "TEST 3 PASSED: Whitelist functionality works correctly"
|
||||||
|
else
|
||||||
|
log_error "TEST 3 FAILED: Whitelist functionality not working"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
log_success "All tests passed! Whitelist/blacklist functionality is working correctly."
|
log_success "All tests passed! Whitelist/blacklist functionality is working correctly."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user