Compare commits

..

2 Commits

Author SHA1 Message Date
Your Name
524f9bd84f Last push before major bug fixes 2025-10-01 14:53:20 -04:00
Your Name
4658ede9d6 feat: Implement auth rules enforcement and fix subscription filtering issues
- **Auth Rules Implementation**: Added blacklist/whitelist enforcement in websockets.c
  - Events are now checked against auth_rules table before acceptance
  - Blacklist blocks specific pubkeys, whitelist enables allow-only mode
  - Made check_database_auth_rules() public for cross-module access

- **Subscription Filtering Fixes**:
  - Added missing 'ids' filter support in SQL query building
  - Fixed test expectations to not require exact event counts for kind filters
  - Improved filter validation and error handling

- **Ephemeral Events Compliance**:
  - Modified SQL queries to exclude kinds 20000-29999 from historical queries
  - Maintains broadcasting to active subscribers while preventing storage/retrieval
  - Ensures NIP-01 compliance for ephemeral event handling

- **Comprehensive Testing**:
  - Created white_black_test.sh with full blacklist/whitelist functionality testing
  - Tests verify blocked posting for blacklisted users
  - Tests verify whitelist-only mode when whitelist rules exist
  - Includes proper auth rule clearing between test phases

- **Code Quality**:
  - Added proper function declarations to websockets.h
  - Improved error handling and logging throughout
  - Enhanced test script with clear pass/fail reporting
2025-09-30 15:17:59 -04:00
8 changed files with 380 additions and 115 deletions

32
07.md
View File

@@ -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
View 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 READMEs "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 isnt 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

View File

@@ -282,7 +282,7 @@ cd build
# Start relay in background and capture its PID
if [ "$USE_TEST_KEYS" = true ]; then
echo "Using deterministic test keys for development..."
./$(basename $BINARY_PATH) -a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -r 1111111111111111111111111111111111111111111111111111111111111111 --strict-port > ../relay.log 2>&1 &
./$(basename $BINARY_PATH) -a 6a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3 -r 1111111111111111111111111111111111111111111111111111111111111111 --strict-port > ../relay.log 2>&1 &
elif [ -n "$RELAY_ARGS" ]; then
echo "Starting relay with custom configuration..."
./$(basename $BINARY_PATH) $RELAY_ARGS --strict-port > ../relay.log 2>&1 &

View File

@@ -1 +1 @@
1487904
1796483

View File

@@ -921,40 +921,46 @@ int first_time_startup_sequence(const cli_options_t* cli_options) {
// 1. Generate or use provided admin keypair
unsigned char admin_privkey_bytes[32];
char admin_privkey[65], admin_pubkey[65];
if (cli_options && strlen(cli_options->admin_privkey_override) == 64) {
// Use provided admin private key
log_info("Using provided admin private key override");
strncpy(admin_privkey, cli_options->admin_privkey_override, sizeof(admin_privkey) - 1);
admin_privkey[sizeof(admin_privkey) - 1] = '\0';
// Convert hex string to bytes
if (nostr_hex_to_bytes(admin_privkey, admin_privkey_bytes, 32) != NOSTR_SUCCESS) {
log_error("Failed to convert admin private key hex to bytes");
return -1;
}
// Validate the private key
if (nostr_ec_private_key_verify(admin_privkey_bytes) != NOSTR_SUCCESS) {
log_error("Provided admin private key is invalid");
return -1;
if (cli_options && strlen(cli_options->admin_pubkey_override) == 64) {
// Use provided admin public key directly - skip private key generation entirely
log_info("Using provided admin public key override - skipping private key generation");
strncpy(admin_pubkey, cli_options->admin_pubkey_override, sizeof(admin_pubkey) - 1);
admin_pubkey[sizeof(admin_pubkey) - 1] = '\0';
// Validate the public key format (must be 64 hex characters)
for (int i = 0; i < 64; i++) {
char c = admin_pubkey[i];
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;
}
}
// Skip private key generation - we only need the pubkey for admin verification
// 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
} else {
// 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) {
log_error("Failed to generate admin private key");
return -1;
}
nostr_bytes_to_hex(admin_privkey_bytes, 32, admin_privkey);
// Derive public key from private key
unsigned char admin_pubkey_bytes[32];
if (nostr_ec_public_key_from_private_key(admin_privkey_bytes, admin_pubkey_bytes) != NOSTR_SUCCESS) {
log_error("Failed to derive admin public key");
return -1;
}
nostr_bytes_to_hex(admin_pubkey_bytes, 32, admin_pubkey);
}
unsigned char admin_pubkey_bytes[32];
if (nostr_ec_public_key_from_private_key(admin_privkey_bytes, admin_pubkey_bytes) != NOSTR_SUCCESS) {
log_error("Failed to derive admin public key");
return -1;
}
nostr_bytes_to_hex(admin_pubkey_bytes, 32, admin_pubkey);
// 2. Generate or use provided relay keypair
unsigned char relay_privkey_bytes[32];
char relay_privkey[65], relay_pubkey[65];
@@ -1011,34 +1017,43 @@ 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. Create initial configuration event using defaults (without private key)
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;
}
// 7. 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");
// 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");
} 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);
// 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;
}
g_pending_config_event = cJSON_Duplicate(config_event, 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);
}
// 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");

View File

@@ -96,7 +96,7 @@ typedef struct {
// Command line options structure for first-time startup
typedef struct {
int port_override; // -1 = not set, >0 = port value
char admin_privkey_override[65]; // Empty string = not set, 64-char hex = override
char 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
int strict_port; // 0 = allow port increment, 1 = fail if exact port unavailable
} cli_options_t;

View File

@@ -1204,9 +1204,9 @@ void print_usage(const char* program_name) {
printf(" -h, --help Show this help message\n");
printf(" -v, --version Show version information\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(" -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("Configuration:\n");
printf(" This relay uses event-based configuration stored in the database.\n");
@@ -1221,12 +1221,12 @@ void print_usage(const char* program_name) {
printf("\n");
printf("Examples:\n");
printf(" %s # Start relay (auto-configure on first run)\n", program_name);
printf(" %s -p 8080 # First-time setup with port 8080\n", program_name);
printf(" %s --port 9000 # First-time setup with port 9000\n", program_name);
printf(" %s --strict-port # Fail if default port 8888 is unavailable\n", program_name);
printf(" %s -p 8080 # First-time setup with port 8080\n", program_name);
printf(" %s --port 9000 # First-time setup with port 9000\n", program_name);
printf(" %s --strict-port # Fail if default port 8888 is unavailable\n", program_name);
printf(" %s -p 8080 --strict-port # Fail if port 8080 is unavailable\n", program_name);
printf(" %s --help # Show this help\n", program_name);
printf(" %s --version # Show version info\n", program_name);
printf(" %s --help # Show this help\n", program_name);
printf(" %s --version # Show version info\n", program_name);
printf("\n");
}
@@ -1242,7 +1242,7 @@ int main(int argc, char* argv[]) {
// Initialize CLI options structure
cli_options_t cli_options = {
.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
.strict_port = 0 // 0 = allow port increment (default)
};
@@ -1279,36 +1279,36 @@ int main(int argc, char* argv[]) {
char port_msg[128];
snprintf(port_msg, sizeof(port_msg), "Port override specified: %d", cli_options.port_override);
log_info(port_msg);
} else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--admin-privkey") == 0) {
// Admin private key override option
} else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--admin-pubkey") == 0) {
// Admin public key override option
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]);
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) {
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]);
return 1;
}
// Validate hex format
for (int j = 0; j < 64; j++) {
char c = argv[i + 1][j];
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]);
return 1;
}
}
strncpy(cli_options.admin_privkey_override, argv[i + 1], sizeof(cli_options.admin_privkey_override) - 1);
cli_options.admin_privkey_override[sizeof(cli_options.admin_privkey_override) - 1] = '\0';
strncpy(cli_options.admin_pubkey_override, argv[i + 1], sizeof(cli_options.admin_pubkey_override) - 1);
cli_options.admin_pubkey_override[sizeof(cli_options.admin_pubkey_override) - 1] = '\0';
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) {
// Relay private key override option
if (i + 1 >= argc) {
@@ -1389,7 +1389,7 @@ int main(int argc, char* argv[]) {
nostr_cleanup();
return 1;
}
// Now that database is available, store the relay private key securely
const char* relay_privkey = get_temp_relay_private_key();
if (relay_privkey) {
@@ -1406,17 +1406,36 @@ int main(int argc, char* argv[]) {
nostr_cleanup();
return 1;
}
// Systematically add pubkeys to config table
if (add_pubkeys_to_config_table() != 0) {
log_warning("Failed to add pubkeys to config table systematically");
// 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");
// 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 {
log_success("Pubkeys added to config table systematically");
}
// Retry storing the configuration event now that database is initialized
if (retry_store_initial_config_event() != 0) {
log_warning("Failed to store initial configuration event after database init");
// 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");
}
}
// Now store the pubkeys in config table since database is available

View File

@@ -166,6 +166,81 @@ add_to_blacklist() {
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_blacklist_post() {
log_info "=== TEST 2: Attempt to post event after blacklisting ==="
@@ -199,6 +274,92 @@ test_blacklist_post() {
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() {
log_info "Starting C-Relay Whitelist/Blacklist Test"
@@ -237,6 +398,14 @@ main() {
exit 1
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."
}