diff --git a/.roo/rules-ask/AGENTS.md b/.roo/rules-ask/AGENTS.md index 8b564c6..5a844bf 100644 --- a/.roo/rules-ask/AGENTS.md +++ b/.roo/rules-ask/AGENTS.md @@ -13,4 +13,5 @@ This file provides guidance to agents when working with code in this repository. - **Testing Setup**: Tests require `nak` tool for Nostr event generation - not standard HTTP testing - **Development Ports**: Local development uses port 9001, production typically uses nginx proxy on standard ports - **Setup Wizard**: Interactive setup creates cryptographically signed config files - not typical config generation -- **Extension Handling**: nginx config uses wildcards to serve files regardless of URL extension - Blossom protocol compliance \ No newline at end of file +- **Extension Handling**: nginx config uses wildcards to serve files regardless of URL extension - Blossom protocol compliance +- **Configuration Events**: Uses Nostr kind 33333 events with 1-year default expiration - XDG paths take priority over database \ No newline at end of file diff --git a/.roo/rules-code/AGENTS.md b/.roo/rules-code/AGENTS.md index 79b00e0..9efb0f6 100644 --- a/.roo/rules-code/AGENTS.md +++ b/.roo/rules-code/AGENTS.md @@ -15,4 +15,8 @@ This file provides guidance to agents when working with code in this repository. - **Configuration Loading**: File config takes priority over database - check XDG paths first, fallback to database - **Blob Metadata**: Database is single source of truth - use `get_blob_metadata()`, not filesystem checks - **nostr_core_lib Build**: Uses `build.sh` script, NOT `make` - run `./build.sh` to compile the library -- **Server Testing**: Use `./restart-all.sh` to properly restart and test ginxsom server, NOT direct binary execution \ No newline at end of file +- **Server Testing**: Use `./restart-all.sh` to properly restart and test ginxsom server, NOT direct binary execution +- **spawn-fcgi Parameters**: Must use `-M 666 -u "$USER" -g "$USER"` for socket permissions and ownership +- **Admin Event Structure**: Admin auth uses kind 24242 with "t" tag containing HTTP method, expiration typically 1 hour +- **Key File Permissions**: `.admin_keys` file must be chmod 600 or auth tests will fail +- **Development Environment**: Set `GINX_DEBUG=1` for pubkey extraction diagnostics during development \ No newline at end of file diff --git a/.roo/rules-debug/AGENTS.md b/.roo/rules-debug/AGENTS.md index 0533987..abf469b 100644 --- a/.roo/rules-debug/AGENTS.md +++ b/.roo/rules-debug/AGENTS.md @@ -13,4 +13,6 @@ This file provides guidance to agents when working with code in this repository. - **Config Loading**: File config errors are silent - check stderr for "CONFIG:" messages during startup - **Admin Key Mismatch**: Database admin_pubkey vs .admin_keys file often cause auth failures - **Nginx Port Conflicts**: Local nginx on 9001 conflicts with system nginx on 80 - check with `netstat -tlnp` -- **Hash Calculation**: File data buffer must be complete before `nostr_sha256()` call or hash is wrong \ No newline at end of file +- **Hash Calculation**: File data buffer must be complete before `nostr_sha256()` call or hash is wrong +- **Admin Key File**: `.admin_keys` must be chmod 600 and sourceable by bash - common test failure cause +- **Process Cleanup**: `restart-all.sh` performs force kill if graceful shutdown fails - check logs for hung processes \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 5fc74fd..659956c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,7 +18,7 @@ This file provides guidance to agents when working with code in this repository. ## Non-Standard Commands ```bash -# make clean && make && Restart nginx +# make clean && make && Restart nginx ./restart-all.sh # Start FastCGI daemon diff --git a/build/bud06.o b/build/bud06.o index ae40c44..c38df2b 100644 Binary files a/build/bud06.o and b/build/bud06.o differ diff --git a/build/ginxsom-fcgi b/build/ginxsom-fcgi index 668baa2..09bd5c7 100755 Binary files a/build/ginxsom-fcgi and b/build/ginxsom-fcgi differ diff --git a/build/request_validator.o b/build/request_validator.o index d93da8d..923ee97 100644 Binary files a/build/request_validator.o and b/build/request_validator.o differ diff --git a/db/ginxsom.db b/db/ginxsom.db index 11a1706..4738486 100644 Binary files a/db/ginxsom.db and b/db/ginxsom.db differ diff --git a/src/bud06.c b/src/bud06.c index 0b87d89..23dfa65 100644 --- a/src/bud06.c +++ b/src/bud06.c @@ -204,14 +204,7 @@ void handle_head_upload_request(void) { return; } - // Check if blob already exists (duplicate detection) - if (check_blob_exists(sha256)) { - send_upload_error_response(409, "blob_exists", "Blob with this hash already exists", XREASON_BLOB_EXISTS); - log_request("HEAD", "/upload", "none", 409); - return; - } - - // Check for optional authorization + // Check for authorization first (before duplicate detection) const char* auth_header = getenv("HTTP_AUTHORIZATION"); const char* auth_status = "none"; @@ -220,6 +213,18 @@ void handle_head_upload_request(void) { // This handler receives pre-validated requests, so if we reach here with auth_header, // the authentication was already successful auth_status = "authenticated"; + } else { + // Check if server requires authorization for uploads + // If auth is required but not provided, return 401 before checking for duplicates + // TODO: This should check server configuration for auth requirements + // For now, assume auth is optional unless configured otherwise + } + + // Check if blob already exists (duplicate detection - after auth validation) + if (check_blob_exists(sha256)) { + send_upload_error_response(409, "blob_exists", "Blob with this hash already exists", XREASON_BLOB_EXISTS); + log_request("HEAD", "/upload", auth_status, 409); + return; } // All validations passed - return success diff --git a/src/main.c b/src/main.c index 00b2a70..2da2980 100644 --- a/src/main.c +++ b/src/main.c @@ -28,16 +28,6 @@ // Database path #define DB_PATH "db/ginxsom.db" -// ===== COMMENTED OUT UNUSED CODE ===== -// Forward declarations for config system (all commented out) -/* -int initialize_server_config(void); -int apply_config_from_event(cJSON *event); -int get_config_file_path(char *path, size_t path_size); -int load_server_config(const char *config_path); -int run_interactive_setup(const char *config_path); -*/ -// ===== END COMMENTED OUT CODE ===== // Configuration system implementation #include @@ -536,15 +526,6 @@ const char *extract_sha256_from_uri(const char *uri) { ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// -// ===== COMMENTED OUT UNUSED CODE ===== -// Forward declarations for detailed validation functions (all commented out) -/* -int detailed_structure_validation(cJSON *event); -int detailed_signature_validation(cJSON *event); -void analyze_event_fields(cJSON *event); -void hex_dump(const char *label, const unsigned char *data, size_t len); -*/ -// ===== END COMMENTED OUT CODE ===== ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// @@ -1024,38 +1005,7 @@ void handle_upload_request(void) { fflush(stderr); - // Legacy authentication check - now handled by centralized validation system - /* - // Check if authentication rules are enabled using nostr_core_lib system - int auth_required = nostr_auth_rules_enabled(); - fprintf(stderr, "AUTH: auth_rules_enabled = %d, auth_header present: %s\r\n", - auth_required, auth_header ? "YES" : "NO"); - // If authentication is required but no auth header provided, fail immediately - if (auth_required && !auth_header) { - free(file_data); - send_error_response(401, "authorization_required", - "Authorization required for upload operations", - "This server requires authentication for all uploads"); - log_request("PUT", "/upload", "missing_auth", 401); - return; - } - - // If auth rules are completely disabled, skip all validation and allow upload - if (!auth_required) { - fprintf(stderr, "AUTH: Authentication rules disabled - skipping all " - "validation and allowing upload\n"); - // Skip validation and proceed to file processing - goto process_file_upload; - } - */ - - // Authentication is handled by centralized validation system - // TODO: Get uploader_pubkey from centralized validation result - // For now, keep existing uploader_pubkey extraction for compatibility - -// Legacy goto label - no longer needed with centralized validation -// process_file_upload: // Get dimensions from in-memory buffer before saving file int width = 0, height = 0; nip94_get_dimensions(file_data, content_length, content_type, &width, @@ -1299,37 +1249,7 @@ void handle_upload_request_with_validation(nostr_request_result_t* validation_re fflush(stderr); - // Legacy authentication check - now handled by centralized validation system - /* - // Check if authentication rules are enabled using nostr_core_lib system - int auth_required = nostr_auth_rules_enabled(); - fprintf(stderr, "AUTH: auth_rules_enabled = %d, auth_header present: %s\r\n", - auth_required, auth_header ? "YES" : "NO"); - // If authentication is required but no auth header provided, fail immediately - if (auth_required && !auth_header) { - if (should_free_file_data) free(file_data); - send_error_response(401, "authorization_required", - "Authorization required for upload operations", - "This server requires authentication for all uploads"); - log_request("PUT", "/upload", "missing_auth", 401); - return; - } - - // If auth rules are completely disabled, skip all validation and allow upload - if (!auth_required) { - fprintf(stderr, "AUTH: Authentication rules disabled - skipping all " - "validation and allowing upload\n"); - // Skip validation and proceed to file processing - goto process_file_upload; - } - */ - - // Authentication was handled by centralized validation system - // uploader_pubkey should be set from validation result - -// Legacy goto label - no longer needed with centralized validation -// process_file_upload: // Get dimensions from in-memory buffer before saving file int width = 0, height = 0; nip94_get_dimensions(file_data, file_size, content_type, &width, @@ -1523,23 +1443,6 @@ int main(void) { // Try file-based config first, then fall back to database config int config_loaded = 0; -// All file-based config and interactive setup are commented out -/* -char config_path[512]; - -if (get_config_file_path(config_path, sizeof(config_path))) { - fprintf(stderr, "STARTUP: Checking for config file at: %s\n", config_path); - if (load_server_config(config_path)) { - fprintf(stderr, - "STARTUP: File-based configuration loaded successfully\n"); - config_loaded = 1; - } else { - fprintf(stderr, "STARTUP: No valid file-based config found, trying " - "database config\n"); - } -} -*/ - // Fall back to database configuration if file config failed if (!config_loaded /* && !initialize_server_config() */) { fprintf( @@ -1547,13 +1450,7 @@ if (!config_loaded /* && !initialize_server_config() */) { "STARTUP: No configuration found - server starting in setup mode\n"); fprintf(stderr, "STARTUP: Run interactive setup with: ginxsom --setup\n"); // For interactive mode (when stdin is available), offer setup - /* - char config_path[512]; - if (isatty(STDIN_FILENO) && - get_config_file_path(config_path, sizeof(config_path))) { - return run_interactive_setup(config_path); - } - */ + } else if (!config_loaded) { fprintf(stderr, "STARTUP: Database configuration loaded successfully\n"); } diff --git a/src/request_validator.c b/src/request_validator.c index a539952..ddc99ca 100644 --- a/src/request_validator.c +++ b/src/request_validator.c @@ -283,7 +283,17 @@ int nostr_validate_unified_request(const nostr_unified_request_t *request, // PHASE 2: NOSTR EVENT VALIDATION (CPU Intensive ~2ms) ///////////////////////////////////////////////////////////////////// - // Check if authentication header is provided + // Check if this is a BUD-09 report request - allow anonymous reporting + if (request->operation && strcmp(request->operation, "report") == 0) { + // BUD-09 allows anonymous reporting - pass through to bud09.c for validation + result->valid = 1; + result->error_code = NOSTR_SUCCESS; + strcpy(result->reason, "BUD-09 report request - bypassing auth for anonymous reporting"); + validator_debug_log("VALIDATOR_DEBUG: BUD-09 report detected, bypassing authentication\n"); + return NOSTR_SUCCESS; + } + + // Check if authentication header is provided (required for non-report operations) if (!request->auth_header) { result->valid = 0; diff --git a/tests/auth_test_tmp/nip42_challenge b/tests/auth_test_tmp/nip42_challenge index 969dfc0..9e491be 100644 --- a/tests/auth_test_tmp/nip42_challenge +++ b/tests/auth_test_tmp/nip42_challenge @@ -1 +1 @@ -09127399ac6d531773cafe433bd6ffd0592b04480543b8225ba17d48fd61b5ac +e3ba927d32ca105a8a4cafa2e013b97945a165c38e9ce573446a2332dc312fdb diff --git a/tests/init_admin.sh b/tests/init_admin.sh deleted file mode 100755 index 9b0805d..0000000 --- a/tests/init_admin.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -# Admin Initialization Script for Ginxsom Testing -# Sets up the test admin key in the database - -set -e - -# Test admin public key (must match TEST_ADMIN_PUBKEY from admin_test.sh) -TEST_ADMIN_PUBKEY="2ef05348f28d24e0f0ed0751278442c27b62c823c37af8d8d89d8592c6ee84e7" - -echo "Initializing admin access for testing..." - -# Check if database exists -if [ ! -f "db/ginxsom.db" ]; then - echo "Error: Database db/ginxsom.db not found. Run ./db/init.sh first." - exit 1 -fi - -# Configure admin settings -sqlite3 db/ginxsom.db << EOF -INSERT OR REPLACE INTO config (key, value, description) VALUES - ('admin_pubkey', '$TEST_ADMIN_PUBKEY', 'Nostr public key authorized for admin operations (test key)'), - ('admin_enabled', 'true', 'Enable admin interface'); -EOF - -echo "Admin access configured successfully!" -echo "Test admin public key: $TEST_ADMIN_PUBKEY" -echo "Use private key from admin_test.sh to generate authentication tokens" - -# Verify configuration -echo "" -echo "Current admin configuration:" -sqlite3 db/ginxsom.db "SELECT key, value FROM config WHERE key IN ('admin_pubkey', 'admin_enabled');" \ No newline at end of file diff --git a/tests/requirements_test_bud06.sh b/tests/requirements_test_bud06.sh index eb9f08b..973d563 100755 --- a/tests/requirements_test_bud06.sh +++ b/tests/requirements_test_bud06.sh @@ -11,11 +11,16 @@ echo "=== BUD-06 Upload Requirements Test Suite ===" SERVER_URL="http://localhost:9001" UPLOAD_ENDPOINT="${SERVER_URL}/upload" -# Test file properties -TEST_SHA256="24308d48eb498b593e55a87b6300ccffdea8432babc0bb898b1eff21ebbb72de" +# Test file properties - generate unique hashes for each test run +TIMESTAMP=$(date +%s) +TEST_SHA256=$(echo "test_bud06_${TIMESTAMP}_$(uname -n)" | sha256sum | cut -d' ' -f1) TEST_CONTENT_TYPE="image/png" TEST_CONTENT_LENGTH="71418" +# Generate additional unique hashes for tests that need fresh hashes +TEST_SHA256_FRESH=$(echo "fresh_bud06_${TIMESTAMP}_$(uname -n)" | sha256sum | cut -d' ' -f1) +TEST_SHA256_AUTH=$(echo "auth_bud06_${TIMESTAMP}_$(uname -n)" | sha256sum | cut -d' ' -f1) + # Helper function to make HEAD request with custom headers make_head_request() { local sha256="$1" @@ -59,11 +64,11 @@ echo "" echo "=== Test 1: Valid Upload Requirements ===" echo "Testing HEAD /upload with valid headers..." -RESPONSE=$(make_head_request "$TEST_SHA256" "$TEST_CONTENT_TYPE" "$TEST_CONTENT_LENGTH") +RESPONSE=$(make_head_request "$TEST_SHA256_FRESH" "$TEST_CONTENT_TYPE" "$TEST_CONTENT_LENGTH") STATUS=$(get_status_code "$RESPONSE") echo "Request Headers:" -echo " X-SHA-256: $TEST_SHA256" +echo " X-SHA-256: $TEST_SHA256_FRESH" echo " X-Content-Type: $TEST_CONTENT_TYPE" echo " X-Content-Length: $TEST_CONTENT_LENGTH" echo "" @@ -185,12 +190,12 @@ echo "=== Test 6: Unsupported Media Type ===" echo "Testing HEAD /upload with potentially unsupported MIME type..." UNSUPPORTED_TYPE="application/x-malware" -RESPONSE=$(make_head_request "$TEST_SHA256" "$UNSUPPORTED_TYPE" "$TEST_CONTENT_LENGTH") +RESPONSE=$(make_head_request "$TEST_SHA256_FRESH" "$UNSUPPORTED_TYPE" "$TEST_CONTENT_LENGTH") STATUS=$(get_status_code "$RESPONSE") REASON=$(get_x_reason "$RESPONSE") echo "Request Headers:" -echo " X-SHA-256: $TEST_SHA256" +echo " X-SHA-256: $TEST_SHA256_FRESH" echo " X-Content-Type: $UNSUPPORTED_TYPE" echo " X-Content-Length: $TEST_CONTENT_LENGTH" echo "" @@ -267,13 +272,13 @@ echo "=== Test 9: Authorization Handling ===" echo "Testing HEAD /upload authorization requirements..." # Test without authorization first -RESPONSE=$(make_head_request "$TEST_SHA256" "$TEST_CONTENT_TYPE" "$TEST_CONTENT_LENGTH") +RESPONSE=$(make_head_request "$TEST_SHA256_AUTH" "$TEST_CONTENT_TYPE" "$TEST_CONTENT_LENGTH") STATUS=$(get_status_code "$RESPONSE") REASON=$(get_x_reason "$RESPONSE") echo "Request Headers (no authorization):" -echo " X-SHA-256: $TEST_SHA256" -echo " X-Content-Type: $TEST_CONTENT_TYPE" +echo " X-SHA-256: $TEST_SHA256_AUTH" +echo " X-Content-Type: $TEST_CONTENT_TYPE" echo " X-Content-Length: $TEST_CONTENT_LENGTH" echo " Authorization: (missing)" echo ""