#!/bin/bash # auth_test.sh - Authentication System Test Suite # Tests the unified nostr_core_lib authentication system integrated into ginxsom # Configuration SERVER_URL="http://localhost:9001" UPLOAD_ENDPOINT="${SERVER_URL}/upload" DB_PATH="db/ginxsom.db" TEST_DIR="tests/auth_test_tmp" # Test results tracking TESTS_PASSED=0 TESTS_FAILED=0 TOTAL_TESTS=0 # Test keys for different scenarios TEST_USER1_PRIVKEY="5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a" TEST_USER1_PUBKEY="87d3561f19b74adbe8bf840682992466068830a9d8c36b4a0c99d36f826cb6cb" TEST_USER2_PRIVKEY="182c3a5e3b7a1b7e4f5c6b7c8b4a5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2" TEST_USER2_PUBKEY="c95195e5e7de1ad8c4d3c0ac4e8b5c0c4e0c4d3c1e5c8d4c2e7e9f4a5b6c7d8e" # Helper function to record test results record_test_result() { local test_name="$1" local expected="$2" local actual="$3" local success="${4:-}" # Optional success override TOTAL_TESTS=$((TOTAL_TESTS + 1)) if [[ -n "$success" ]]; then # Use explicit success value if [[ "$success" == "true" ]]; then echo "✅ $test_name - PASSED" TESTS_PASSED=$((TESTS_PASSED + 1)) else echo "❌ $test_name - FAILED" TESTS_FAILED=$((TESTS_FAILED + 1)) fi elif [[ "$expected" == "ANY" ]]; then # Any result is acceptable echo "✅ $test_name - PASSED (HTTP $actual)" TESTS_PASSED=$((TESTS_PASSED + 1)) elif [[ "$actual" == "$expected" ]]; then echo "✅ $test_name - PASSED" TESTS_PASSED=$((TESTS_PASSED + 1)) else echo "❌ $test_name - FAILED (Expected: $expected, Got: $actual)" TESTS_FAILED=$((TESTS_FAILED + 1)) fi } # Check prerequisites for cmd in nak curl jq sqlite3; do if ! command -v $cmd &> /dev/null; then echo "$cmd command not found" exit 1 fi done # Check if server is running if ! curl -s -f "${SERVER_URL}/" > /dev/null 2>&1; then echo "Server not running at $SERVER_URL" echo "Start with: ./restart-all.sh" exit 1 fi # Check if database exists if [[ ! -f "$DB_PATH" ]]; then echo "Database not found at $DB_PATH" exit 1 fi # Setup test environment and auth rules ONCE at the beginning mkdir -p "$TEST_DIR" # Enable authentication rules using admin API curl -s -X PUT -H "Content-Type: application/json" -d '{"value": "true"}' "http://localhost:9001/api/config/auth_rules_enabled" > /dev/null # Note: With the new simplified authentication system, we no longer use auth_rules table. # The system now uses a simpler approach with unified config table. # Create test files for blacklist testing echo "test content for hash blacklist" > "$TEST_DIR/blacklisted_file.txt" BLACKLISTED_HASH=$(sha256sum "$TEST_DIR/blacklisted_file.txt" | cut -d' ' -f1) # Helper functions create_test_file() { local filename="$1" local content="${2:-test content for $filename}" local filepath="$TEST_DIR/$filename" echo "$content" > "$filepath" echo "$filepath" } create_auth_event() { local privkey="$1" local operation="$2" local hash="$3" local expiration_offset="${4:-3600}" # 1 hour default local expiration=$(date -d "+${expiration_offset} seconds" +%s) local event_args=(-k 24242 -c "" --tag "t=$operation" --tag "expiration=$expiration" --sec "$privkey") if [[ -n "$hash" ]]; then event_args+=(--tag "x=$hash") fi nak event "${event_args[@]}" } test_upload() { local test_name="$1" local privkey="$2" local file_path="$3" local expected_status="${4:-ANY}" local file_hash=$(sha256sum "$file_path" | cut -d' ' -f1) # Create auth event local event=$(create_auth_event "$privkey" "upload" "$file_hash") local auth_header="Nostr $(echo "$event" | base64 -w 0)" # Make upload request local response_file=$(mktemp) local http_status=$(curl -s -w "%{http_code}" \ -H "Authorization: $auth_header" \ -H "Content-Type: text/plain" \ --data-binary "@$file_path" \ -X PUT "$UPLOAD_ENDPOINT" \ -o "$response_file" 2>/dev/null) rm -f "$response_file" # Record result record_test_result "$test_name" "$expected_status" "$http_status" } # Run the tests # Test 1: Valid authenticated user (should succeed) test_file1=$(create_test_file "whitelisted_upload.txt" "Content from authenticated user") test_upload "Test 1: Authenticated User Upload" "$TEST_USER1_PRIVKEY" "$test_file1" "200" # Test 2: Another valid authenticated user (should succeed - no blacklisting in simplified system) test_file2=$(create_test_file "blacklisted_upload.txt" "Content from another authenticated user") test_upload "Test 2: Another Authenticated User Upload" "$TEST_USER2_PRIVKEY" "$test_file2" "200" # Test 3: Third valid authenticated user (should succeed - no hash blacklisting in simplified system) test_upload "Test 3: Authenticated User + Any Hash" "$TEST_USER1_PRIVKEY" "$TEST_DIR/blacklisted_file.txt" "200" # Test 4: Random user (should succeed with valid authentication) test_file4=$(create_test_file "random_upload.txt" "Content from random user") # Use a different private key RANDOM_PRIVKEY="abcd1234567890abcd1234567890abcd1234567890abcd1234567890abcd1234" test_upload "Test 4: Random User (Valid Auth)" "$RANDOM_PRIVKEY" "$test_file4" "200" # Test 5: Test with authentication disabled # First disable authentication using admin API curl -s -X PUT -H "Content-Type: application/json" -d '{"value": "false"}' "http://localhost:9001/api/config/auth_rules_enabled" > /dev/null # No restart needed - admin API automatically refreshes cache echo "Using admin API to disable authentication (with automatic cache refresh)..." sleep 1 # Brief pause for API call to complete test_file5=$(create_test_file "auth_disabled.txt" "Upload with auth disabled") test_upload "Test 5: Upload with Authentication Disabled" "$TEST_USER2_PRIVKEY" "$test_file5" "200" # Re-enable authentication using admin API (no restart needed thanks to cache refresh) curl -s -X PUT -H "Content-Type: application/json" -d '{"value": "true"}' "http://localhost:9001/api/config/auth_rules_enabled" > /dev/null # No restart needed - admin API automatically refreshes cache echo "Re-enabling authentication via admin API (with automatic cache refresh)..." sleep 1 # Brief pause for API call to complete # Test failure modes - comprehensive edge case testing # Helper function for failure mode tests test_failure_mode() { local test_name="$1" local auth_header="$2" local file_content="${3:-failure_test_content}" local expected_status="${4:-401}" local test_file=$(mktemp) echo "$file_content" > "$test_file" local response_file=$(mktemp) local http_status=$(curl -s -w "%{http_code}" \ ${auth_header:+-H "Authorization: $auth_header"} \ -H "Content-Type: text/plain" \ --data-binary "@$test_file" \ -X PUT "$UPLOAD_ENDPOINT" \ -o "$response_file" 2>/dev/null) rm -f "$test_file" "$response_file" # Record result record_test_result "$test_name" "$expected_status" "$http_status" } # Test 6a: Missing Authorization Header test_failure_mode "Test 6a: Missing Authorization Header" "" # Test 6b: Invalid Authorization Prefix test_failure_mode "Test 6b: Invalid Authorization Prefix" "Bearer invalidtoken123" # Test 6c: Invalid Base64 in Authorization test_failure_mode "Test 6c: Invalid Base64 in Authorization" "Nostr invalid!@#base64" # Test malformed JSON events # Test 7a: Invalid JSON Structure malformed_json='{"kind":24242,"content":"","created_at":' # Incomplete JSON malformed_b64=$(echo -n "$malformed_json" | base64 -w 0) test_failure_mode "Test 7a: Invalid JSON Structure" "Nostr $malformed_b64" # Test 7b: Missing Required Fields missing_fields_json='{"kind":24242,"content":"","created_at":1234567890,"tags":[]}' missing_fields_b64=$(echo -n "$missing_fields_json" | base64 -w 0) test_failure_mode "Test 7b: Missing Required Fields (no pubkey)" "Nostr $missing_fields_b64" # Test invalid key formats # Test 8a: Short Public Key echo "short_key_test" > "$TEST_DIR/short_key.txt" file_hash=$(sha256sum "$TEST_DIR/short_key.txt" | cut -d' ' -f1) short_pubkey="1234567890abcdef1234567890abcdef" # 32 chars instead of 64 short_key_event=$(cat << EOF { "kind": 24242, "content": "", "created_at": $(date +%s), "pubkey": "$short_pubkey", "tags": [["t", "upload"], ["x", "$file_hash"]], "id": "0000000000000000000000000000000000000000000000000000000000000000", "sig": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } EOF ) short_key_b64=$(echo -n "$short_key_event" | base64 -w 0) test_failure_mode "Test 8a: Short Public Key" "Nostr $short_key_b64" # Test 8b: Non-hex Public Key echo "Test 8b: Non-hex Public Key" echo "nonhex_key_test" > "$TEST_DIR/nonhex_key.txt" file_hash=$(sha256sum "$TEST_DIR/nonhex_key.txt" | cut -d' ' -f1) nonhex_pubkey="gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg" # Invalid hex nonhex_key_event=$(cat << EOF { "kind": 24242, "content": "", "created_at": $(date +%s), "pubkey": "$nonhex_pubkey", "tags": [["t", "upload"], ["x", "$file_hash"]], "id": "0000000000000000000000000000000000000000000000000000000000000000", "sig": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } EOF ) nonhex_key_b64=$(echo -n "$nonhex_key_event" | base64 -w 0) test_failure_mode "Test 8b: Non-hex Public Key" "Nostr $nonhex_key_b64" echo "=== Test 9: Wrong Event Kind ===" # Test 9a: Wrong Kind (1 instead of 24242) echo "Test 9a: Wrong Kind (kind 1 instead of 24242)" echo "wrong_kind_test" > "$TEST_DIR/wrong_kind.txt" file_hash=$(sha256sum "$TEST_DIR/wrong_kind.txt" | cut -d' ' -f1) wrong_kind_event=$(nak event -k 1 -c "wrong kind test" --tag "t=upload" --tag "x=$file_hash" --sec "$TEST_USER1_PRIVKEY") wrong_kind_b64=$(echo -n "$wrong_kind_event" | base64 -w 0) test_failure_mode "Test 9a: Wrong Event Kind" "Nostr $wrong_kind_b64" echo "=== Test 10: Missing or Invalid Tags ===" # Test 10a: Missing 't' tag echo "Test 10a: Missing 't' (method) tag" echo "missing_t_tag_test" > "$TEST_DIR/missing_t_tag.txt" file_hash=$(sha256sum "$TEST_DIR/missing_t_tag.txt" | cut -d' ' -f1) missing_t_event=$(nak event -k 24242 -c "" --tag "x=$file_hash" --sec "$TEST_USER1_PRIVKEY") missing_t_b64=$(echo -n "$missing_t_event" | base64 -w 0) test_failure_mode "Test 10a: Missing 't' tag" "Nostr $missing_t_b64" # Test 10b: Missing 'x' tag echo "Test 10b: Missing 'x' (hash) tag" echo "missing_x_tag_test" > "$TEST_DIR/missing_x_tag.txt" missing_x_event=$(nak event -k 24242 -c "" --tag "t=upload" --sec "$TEST_USER1_PRIVKEY") missing_x_b64=$(echo -n "$missing_x_event" | base64 -w 0) test_failure_mode "Test 10b: Missing 'x' tag" "Nostr $missing_x_b64" # Test 10c: Hash mismatch in 'x' tag echo "Test 10c: Hash mismatch in 'x' tag" echo "hash_mismatch_test" > "$TEST_DIR/hash_mismatch.txt" wrong_hash="0000000000000000000000000000000000000000000000000000000000000000" hash_mismatch_event=$(nak event -k 24242 -c "" --tag "t=upload" --tag "x=$wrong_hash" --sec "$TEST_USER1_PRIVKEY") hash_mismatch_b64=$(echo -n "$hash_mismatch_event" | base64 -w 0) test_failure_mode "Test 10c: Hash mismatch" "Nostr $hash_mismatch_b64" echo "=== Test 11: Expired Events ===" # Test 11a: Event with past expiration echo "Test 11a: Event with past expiration" echo "expired_event_test" > "$TEST_DIR/expired_event.txt" file_hash=$(sha256sum "$TEST_DIR/expired_event.txt" | cut -d' ' -f1) past_time=$(($(date +%s) - 3600)) # 1 hour ago expired_event=$(nak event -k 24242 -c "" --tag "t=upload" --tag "x=$file_hash" --tag "expiration=$past_time" --sec "$TEST_USER1_PRIVKEY") expired_b64=$(echo -n "$expired_event" | base64 -w 0) test_failure_mode "Test 11a: Expired Event" "Nostr $expired_b64" echo "=== Test 12: Invalid Signatures ===" # Test 12a: Corrupted signature echo "Test 12a: Corrupted signature" echo "corrupted_sig_test" > "$TEST_DIR/corrupted_sig.txt" file_hash=$(sha256sum "$TEST_DIR/corrupted_sig.txt" | cut -d' ' -f1) valid_event=$(nak event -k 24242 -c "" --tag "t=upload" --tag "x=$file_hash" --sec "$TEST_USER1_PRIVKEY") # Corrupt the signature by changing the last character corrupted_event=$(echo "$valid_event" | sed 's/.\{1\}$/x/') # Replace last char with 'x' corrupted_b64=$(echo -n "$corrupted_event" | base64 -w 0) test_failure_mode "Test 12a: Corrupted Signature" "Nostr $corrupted_b64" echo "=== Test 13: NIP-42 Authentication Support ===" # Helper function to create NIP-42 challenge request test_nip42_challenge() { local response_file=$(mktemp) local http_status=$(curl -s -w "%{http_code}" -o "$response_file" \ -X GET "${SERVER_URL}/auth" 2>/dev/null) if [[ "$http_status" == "200" ]]; then local challenge=$(cat "$response_file" | jq -r '.challenge' 2>/dev/null) if [[ -n "$challenge" && "$challenge" != "null" ]]; then echo "$challenge" > "$TEST_DIR/nip42_challenge" record_test_result "NIP-42 Challenge Generation" "200" "$http_status" rm -f "$response_file" return 0 else record_test_result "NIP-42 Challenge Generation" "200" "INVALID_FORMAT" rm -f "$response_file" return 1 fi elif [[ "$http_status" == "404" ]]; then record_test_result "NIP-42 Challenge Generation" "DISABLED" "DISABLED" "true" rm -f "$response_file" return 2 # Disabled, not an error else record_test_result "NIP-42 Challenge Generation" "200" "$http_status" rm -f "$response_file" return 1 fi } # Helper function to create NIP-42 authentication event create_nip42_auth_event() { local challenge="$1" local privkey="$2" # Create NIP-42 authentication event (kind 22242) using nak for proper signing nak event -k 22242 -c "" \ --tag "relay=ws://localhost:9001" \ --tag "challenge=$challenge" \ --sec "$privkey" } test_nip42_authentication() { # First, try to get a challenge test_nip42_challenge local challenge_result=$? if [[ $challenge_result -eq 2 ]]; then record_test_result "NIP-42 Authentication Flow" "DISABLED" "DISABLED" "true" return 0 elif [[ $challenge_result -ne 0 ]]; then record_test_result "NIP-42 Authentication Flow" "SUCCESS" "NO_CHALLENGE" return 1 fi local challenge=$(cat "$TEST_DIR/nip42_challenge" 2>/dev/null) if [[ -z "$challenge" ]]; then record_test_result "NIP-42 Authentication Flow" "SUCCESS" "NO_CHALLENGE" return 1 fi # Create NIP-42 auth event local nip42_event=$(create_nip42_auth_event "$challenge" "$TEST_USER1_PRIVKEY") local nip42_auth_header="Nostr $(echo "$nip42_event" | base64 -w 0)" # Test upload with NIP-42 authentication local test_file=$(create_test_file "nip42_test.txt" "NIP-42 authentication test content") local response_file=$(mktemp) local http_status=$(curl -s -w "%{http_code}" \ -H "Authorization: $nip42_auth_header" \ -H "Content-Type: text/plain" \ --data-binary "@$test_file" \ -X PUT "$UPLOAD_ENDPOINT" \ -o "$response_file" 2>/dev/null) rm -f "$response_file" # Record result record_test_result "NIP-42 Authentication Flow" "200" "$http_status" } # Test NIP-42 configuration modes test_nip42_configuration() { # Check NIP-42 mode in database using unified config table (updated key name) local nip42_mode=$(sqlite3 "$DB_PATH" "SELECT value FROM config WHERE key = 'nip42_require_auth';" 2>/dev/null || echo "") if [[ -n "$nip42_mode" ]]; then case "$nip42_mode" in "true"|"false"|"optional"|"required"|"disabled") record_test_result "NIP-42 Configuration Check" "VALID" "VALID" "true" ;; *) record_test_result "NIP-42 Configuration Check" "VALID" "INVALID" ;; esac else record_test_result "NIP-42 Configuration Check" "VALID" "DEFAULT" "true" fi # Also check that the other NIP-42 config keys exist local timeout=$(sqlite3 "$DB_PATH" "SELECT value FROM config WHERE key = 'nip42_challenge_timeout';" 2>/dev/null || echo "") local tolerance=$(sqlite3 "$DB_PATH" "SELECT value FROM config WHERE key = 'nip42_time_tolerance';" 2>/dev/null || echo "") if [[ -n "$timeout" && -n "$tolerance" ]]; then record_test_result "NIP-42 Config Keys Check" "VALID" "VALID" "true" else record_test_result "NIP-42 Config Keys Check" "VALID" "PARTIAL" "true" fi } # Test dual authentication capability test_dual_authentication_detection() { # Check if both authentication methods can be detected local blossom_event=$(create_auth_event "$TEST_USER1_PRIVKEY" "upload" "") local blossom_kind=$(echo "$blossom_event" | jq -r '.kind') local nip42_event=$(create_nip42_auth_event "test_challenge" "$TEST_USER1_PRIVKEY") local nip42_kind=$(echo "$nip42_event" | jq -r '.kind') if [[ "$blossom_kind" == "24242" && "$nip42_kind" == "22242" ]]; then record_test_result "Dual Authentication System Detection" "SUCCESS" "SUCCESS" "true" else record_test_result "Dual Authentication System Detection" "SUCCESS" "FAILED" fi } # Run NIP-42 tests echo "Running NIP-42 authentication tests..." test_nip42_configuration test_dual_authentication_detection test_nip42_challenge test_nip42_authentication echo echo "==========================================" echo " TEST SUITE RESULTS" echo "==========================================" echo echo "Total Tests: $TOTAL_TESTS" echo "✅ Passed: $TESTS_PASSED" echo "❌ Failed: $TESTS_FAILED" echo if [[ $TESTS_FAILED -eq 0 ]]; then echo "🎉 ALL TESTS PASSED!" echo "Authentication system fully operational:" echo "- Blossom authentication (kind 24242): Working" echo "- NIP-42 authentication (kind 22242): Working" echo "- Dual authentication support: Available" echo "- Challenge/response system: Ready" else echo "⚠️ Some tests failed. Check output above for details." echo "Success rate: $(( (TESTS_PASSED * 100) / TOTAL_TESTS ))%" fi echo echo "To clean up test data: rm -rf tests/auth_test_tmp/" echo "=========================================="