Moved auth system from nostr_core_lib back into ginxsom. Still debugging but so many changes I wanted to commit.

This commit is contained in:
Your Name
2025-09-09 07:26:00 -04:00
parent 20792871f8
commit dd0d8a8b65
65 changed files with 2851 additions and 19358 deletions

View File

@@ -1,8 +1,5 @@
# Authentication API Documentation
## Overview
The nostr_core_lib unified request validation system provides a comprehensive authentication and authorization framework for Nostr-based applications. It combines Nostr event validation with flexible rule-based authentication in a single API call.
## Authentication Flow and Order of Operations
@@ -505,191 +502,3 @@ The rule evaluation order is specifically designed for security:
4. **Default Deny** - If whitelists exist but don't match, deny
This ensures that even if an attacker bypasses one layer, subsequent layers will catch the attack.
## Core API
### Primary Function
```c
int nostr_validate_request(nostr_request_t* request, nostr_request_result_t* result);
```
This single function handles:
- Nostr event signature validation
- Event structure validation (required fields, timestamps)
- Authentication rule evaluation
- Public key extraction and validation
### Request Structure
```c
typedef struct {
const char* event_json; // Raw Nostr event JSON
const char* app_id; // Application identifier ("ginxsom", "c-relay")
const char* operation; // Operation type ("upload", "delete", "list")
const char* content_hash; // SHA-256 hash for file operations (optional)
const char* mime_type; // MIME type for upload operations (optional)
size_t content_size; // File size for upload operations (0 if N/A)
} nostr_request_t;
```
### Result Structure
```c
typedef struct {
int is_valid; // 1 if request is valid, 0 otherwise
int error_code; // Specific error code (see Error Codes)
char error_message[512]; // Human-readable error description
char pubkey[65]; // Extracted public key (hex, null-terminated)
time_t timestamp; // Event timestamp
char event_id[65]; // Event ID (hex, null-terminated)
} nostr_request_result_t;
```
## Authentication Rules System
The system supports priority-based authentication rules that are evaluated in order:
### Rule Types
1. **NOSTR_AUTH_RULE_PUBKEY_WHITELIST** - Allow specific public keys
2. **NOSTR_AUTH_RULE_PUBKEY_BLACKLIST** - Block specific public keys
3. **NOSTR_AUTH_RULE_HASH_BLACKLIST** - Block specific content hashes
4. **NOSTR_AUTH_RULE_MIME_RESTRICTION** - Restrict allowed MIME types
5. **NOSTR_AUTH_RULE_SIZE_LIMIT** - Enforce maximum file sizes
### Rule Evaluation
- Rules are processed by priority (lower numbers = higher priority)
- First matching rule determines the outcome
- ALLOW rules permit the request
- DENY rules reject the request
- If no rules match, the default action is ALLOW
### Rule Caching
The system includes an intelligent caching mechanism:
- LRU (Least Recently Used) eviction policy
- Configurable cache size (default: 1000 entries)
- Cache keys based on pubkey + operation + content hash
- Automatic cache invalidation when rules change
## Database Backend
### Pluggable Architecture
The system uses a pluggable database backend interface:
```c
typedef struct {
int (*init)(const char* connection_string, void** context);
int (*get_rules)(void* context, const char* app_id,
nostr_auth_rule_t** rules, int* count);
int (*cleanup)(void* context);
} nostr_db_backend_t;
```
### SQLite Implementation
Default implementation uses SQLite with the following schema:
```sql
-- Authentication rules table (per application)
CREATE TABLE auth_rules_[APP_ID] (
id INTEGER PRIMARY KEY AUTOINCREMENT,
priority INTEGER NOT NULL,
rule_type INTEGER NOT NULL,
action INTEGER NOT NULL,
pattern TEXT,
value_int INTEGER,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
updated_at INTEGER DEFAULT (strftime('%s', 'now'))
);
```
## Error Codes
The system uses specific error codes for different failure scenarios:
### Authentication Rule Errors
- **-50**: `NOSTR_AUTH_ERROR_INVALID_EVENT` - Malformed Nostr event
- **-51**: `NOSTR_AUTH_ERROR_INVALID_SIGNATURE` - Invalid event signature
- **-52**: `NOSTR_AUTH_ERROR_PUBKEY_BLOCKED` - Public key is blacklisted
- **-53**: `NOSTR_AUTH_ERROR_HASH_BLOCKED` - Content hash is blacklisted
- **-54**: `NOSTR_AUTH_ERROR_MIME_RESTRICTED` - MIME type not allowed
- **-55**: `NOSTR_AUTH_ERROR_SIZE_EXCEEDED` - File size limit exceeded
### NIP-42 Specific Errors
- **-200**: `NIP42_ERROR_INVALID_RELAY_URL` - Relay URL mismatch or missing
- **-201**: `NIP42_ERROR_INVALID_CHALLENGE` - Challenge missing or malformed
- **-202**: `NIP42_ERROR_CHALLENGE_EXPIRED` - Challenge has expired
- **-203**: `NIP42_ERROR_CHALLENGE_USED` - Challenge already consumed
- **-204**: `NIP42_ERROR_CHALLENGE_NOT_FOUND` - Challenge not found in storage
- **-205**: `NIP42_ERROR_WRONG_EVENT_KIND` - Expected kind 22242 for NIP-42
- **-206**: `NIP42_ERROR_MISSING_TAGS` - Required relay or challenge tags missing
- **-207**: `NIP42_ERROR_URL_NORMALIZATION` - Failed to normalize relay URL
- **-208**: `NIP42_ERROR_VALIDATION_FAILED` - General NIP-42 validation failure
## Usage Examples
### Basic Validation
```c
#include "nostr_core/request_validator.h"
// Initialize the system (once per application)
int result = nostr_request_validator_init("db/myapp.db", "myapp");
if (result != 0) {
fprintf(stderr, "Failed to initialize validator: %d\n", result);
return -1;
}
// Validate a request
nostr_request_t request = {
.event_json = "{\"kind\":24242,\"pubkey\":\"abc123...\",\"sig\":\"def456...\"}",
.app_id = "myapp",
.operation = "upload",
.content_hash = "sha256hash...",
.mime_type = "text/plain",
.content_size = 1024
};
nostr_request_result_t result;
int status = nostr_validate_request(&request, &result);
if (result.is_valid) {
printf("Request authorized for pubkey: %s\n", result.pubkey);
} else {
printf("Request denied: %s (code: %d)\n", result.error_message, result.error_code);
}
```
### Ginxsom Integration
The ginxsom application has been updated to use this system:
```c
// Replace old authenticate_request_with_rules() calls with:
nostr_request_t auth_request = {
.event_json = event_json,
.app_id = "ginxsom",
.operation = "upload", // or "list", "delete"
.content_hash = calculated_hash,
.mime_type = detected_mime_type,
.content_size = file_size
};
nostr_request_result_t auth_result;
int auth_status = nostr_validate_request(&auth_request, &auth_result);
if (!auth_result.is_valid) {
printf("Status: 403\r\n");
printf("Content-Type: application/json\r\n\r\n");
printf("{\"error\":\"Authentication failed\",\"message\":\"%s\"}\n",
auth_result.error_message);
return;
}
// Use auth_result.pubkey for the authenticated public key
```

View File

@@ -8,7 +8,7 @@ BUILDDIR = build
TARGET = $(BUILDDIR)/ginxsom-fcgi
# Source files
SOURCES = $(SRCDIR)/main.c $(SRCDIR)/admin_api.c $(SRCDIR)/bud04.c $(SRCDIR)/bud06.c $(SRCDIR)/bud08.c $(SRCDIR)/bud09.c
SOURCES = $(SRCDIR)/main.c $(SRCDIR)/admin_api.c $(SRCDIR)/bud04.c $(SRCDIR)/bud06.c $(SRCDIR)/bud08.c $(SRCDIR)/bud09.c $(SRCDIR)/request_validator.c
OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(BUILDDIR)/%.o)
# Default target

View File

@@ -0,0 +1 @@
2ca8fe3cf3eb0fa615b26e0ad83c15ebf57682a1ef8f65272f332dd2e7cc8f07

View File

@@ -0,0 +1 @@
NIP-42 authentication test content

435
Trash/debug_auth.log Normal file
View File

@@ -0,0 +1,435 @@
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a1fff0ffefb9eace7230c24e50731f0a91c62f9cefdfe77121c2f607125dffae'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '802058364873910dc6e8611c2232242484211a18724c1292486b107939de7298'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '368a3fad122be49471eb18b87dbb61fe65dd71048aced9712c2299abc6390aca'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied by hash blacklist rule: TEST_HASH_BLACKLIST
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '5a5628938aa5fc67b79f5c843c813bf7823f4307935b6eb372f1250c1ccd447d'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '92e62f9708cef7d7f4675250267a35182300df6e1c5b6cf0bd207912d94c9016'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 1, reason: Request validation passed
AUTH: pubkey extracted: '0396b426090284a28294078dce53fe73791ab623c3fc46ab4409fea05109a6db'
AUTH: resource_hash: '0f0ad694efb237aca094aac7670578531921118c8063cc3f362bb1c5516ae488'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Failed to parse authorization header
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Failed to parse authorization header
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Invalid JSON in authorization
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Unsupported event kind for authentication
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Invalid JSON in authorization
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NIP-42 authentication requires relay_url and challenge_id
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'ab0bf82111fa362282601efffd2b09f42270aaefa57afd05feda24b757950c27'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '2b51c4abbdfe0e5ae9ec0e19b9b4d78ad34da5d5f78f21baaa393f71c3e61c96'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '802058364873910dc6e8611c2232242484211a18724c1292486b107939de7298'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '368a3fad122be49471eb18b87dbb61fe65dd71048aced9712c2299abc6390aca'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied by hash blacklist rule: TEST_HASH_BLACKLIST
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '5a5628938aa5fc67b79f5c843c813bf7823f4307935b6eb372f1250c1ccd447d'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '92e62f9708cef7d7f4675250267a35182300df6e1c5b6cf0bd207912d94c9016'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 1, reason: Request validation passed
AUTH: pubkey extracted: '0396b426090284a28294078dce53fe73791ab623c3fc46ab4409fea05109a6db'
AUTH: resource_hash: '0f0ad694efb237aca094aac7670578531921118c8063cc3f362bb1c5516ae488'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Failed to parse authorization header
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Failed to parse authorization header
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Invalid JSON in authorization
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Unsupported event kind for authentication
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Invalid JSON in authorization
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NIP-42 authentication requires relay_url and challenge_id
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'ab0bf82111fa362282601efffd2b09f42270aaefa57afd05feda24b757950c27'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '802058364873910dc6e8611c2232242484211a18724c1292486b107939de7298'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '368a3fad122be49471eb18b87dbb61fe65dd71048aced9712c2299abc6390aca'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied by hash blacklist rule: TEST_HASH_BLACKLIST
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '5a5628938aa5fc67b79f5c843c813bf7823f4307935b6eb372f1250c1ccd447d'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '92e62f9708cef7d7f4675250267a35182300df6e1c5b6cf0bd207912d94c9016'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 1, reason: Request validation passed
AUTH: pubkey extracted: '0396b426090284a28294078dce53fe73791ab623c3fc46ab4409fea05109a6db'
AUTH: resource_hash: '0f0ad694efb237aca094aac7670578531921118c8063cc3f362bb1c5516ae488'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Failed to parse authorization header
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Failed to parse authorization header
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Invalid JSON in authorization
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Unsupported event kind for authentication
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Invalid JSON in authorization
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NIP-42 authentication requires relay_url and challenge_id
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'ab0bf82111fa362282601efffd2b09f42270aaefa57afd05feda24b757950c27'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '18b38ac540aa99331dd8ee37f8481d54a6bf62849ec33be19682e485d3f548c3'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '802058364873910dc6e8611c2232242484211a18724c1292486b107939de7298'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '368a3fad122be49471eb18b87dbb61fe65dd71048aced9712c2299abc6390aca'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied by hash blacklist rule: TEST_HASH_BLACKLIST
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '5a5628938aa5fc67b79f5c843c813bf7823f4307935b6eb372f1250c1ccd447d'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '92e62f9708cef7d7f4675250267a35182300df6e1c5b6cf0bd207912d94c9016'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 1, reason: Request validation passed
AUTH: pubkey extracted: '0396b426090284a28294078dce53fe73791ab623c3fc46ab4409fea05109a6db'
AUTH: resource_hash: '0f0ad694efb237aca094aac7670578531921118c8063cc3f362bb1c5516ae488'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Failed to parse authorization header
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Failed to parse authorization header
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Invalid JSON in authorization
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Unsupported event kind for authentication
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Invalid JSON in authorization
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: NIP-42 authentication requires relay_url and challenge_id
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'ab0bf82111fa362282601efffd2b09f42270aaefa57afd05feda24b757950c27'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '4ea47c723453762df3f90473ee1d5d8de6456a724116563bf24eaba35ce5cc32'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '4ea47c723453762df3f90473ee1d5d8de6456a724116563bf24eaba35ce5cc32'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '4ea47c723453762df3f90473ee1d5d8de6456a724116563bf24eaba35ce5cc32'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '4ea47c723453762df3f90473ee1d5d8de6456a724116563bf24eaba35ce5cc32'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason: Denied - pubkey not in whitelist (found 1 whitelist rules)
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '4ea47c723453762df3f90473ee1d5d8de6456a724116563bf24eaba35ce5cc32'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason:
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '6ae8a75555209fd6c44157c0aed8016e763ff435a19cf186f76863140143ff72'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason:
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '2072c39b66b888c7e88d818c5854d2d3c63a00e9c77a816045ef49f73a9c8ac7'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason:
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '4ea47c723453762df3f90473ee1d5d8de6456a724116563bf24eaba35ce5cc32'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason:
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: 'f152f642ead301c2a32ba3376852c0fa5b45ec770aacc2ee687fb5f9064defe4'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason:
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '4ea47c723453762df3f90473ee1d5d8de6456a724116563bf24eaba35ce5cc32'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason:
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '4ea47c723453762df3f90473ee1d5d8de6456a724116563bf24eaba35ce5cc32'
AUTH: operation: 'upload'
AUTH: auth_header present: YES
AUTH: nostr_validate_request returned: 0, valid: 0, reason:
AUTH: pubkey extracted: <NONE>
AUTH: resource_hash: '4ea47c723453762df3f90473ee1d5d8de6456a724116563bf24eaba35ce5cc32'
AUTH: operation: 'upload'
AUTH: auth_header present: YES

View File

@@ -0,0 +1,6 @@
EVENT_JSON: {"kind":24242,"id":"c7f967dca87bdc95b9336eaab7b2db45cc104ac629915aaed235abbdc6a61c70","pubkey":"87d3561f19b74adbe8bf840682992466068830a9d8c36b4a0c99d36f826cb6cb","created_at":1757347696,"tags":[["t","upload"],["expiration","1757351296"],["x","4ea47c723453762df3f90473ee1d5d8de6456a724116563bf24eaba35ce5cc32"]],"content":"","sig":"8138a221fbe7c92a96f0c9eaa157ab4366530212549c633759b92a8d3e68ea7c2ef72e27181815618db8481b41cab6d4b187cd08dd647ec277d40dbe4b28fb07"}
RAW_PUBKEY: length=64, content='87d3561f19b74adbe8bf840682992466068830a9d8c36b4a0c99d36f826cb6cb'
EVENT_JSON: {"kind":24242,"id":"9138329ea2a1e5bc7371ffce9172246a656773d753804d06882f2e6128a2e3af","pubkey":"87d3561f19b74adbe8bf840682992466068830a9d8c36b4a0c99d36f826cb6cb","created_at":1757347821,"tags":[["t","upload"],["expiration","1757351420"],["x","4ea47c723453762df3f90473ee1d5d8de6456a724116563bf24eaba35ce5cc32"]],"content":"","sig":"cdc19f7bcce369bfa963db81912d4976253b50869b76aa2c2d0c4d1fd1e7ef937b66a0ae37a0477912833fa9d26da520d84ddf44dfe4d14af54624d50f8832f0"}
RAW_PUBKEY: length=64, content='87d3561f19b74adbe8bf840682992466068830a9d8c36b4a0c99d36f826cb6cb'

View File

@@ -0,0 +1 @@
Upload with auth disabled

View File

@@ -0,0 +1 @@
test content for hash blacklist

View File

@@ -0,0 +1 @@
Content from blacklisted user

View File

@@ -0,0 +1 @@
corrupted_sig_test

View File

@@ -0,0 +1 @@
expired_event_test

View File

@@ -0,0 +1 @@
hash_mismatch_test

View File

@@ -0,0 +1 @@
missing_t_tag_test

View File

@@ -0,0 +1 @@
missing_x_tag_test

View File

@@ -0,0 +1 @@
nonhex_key_test

View File

@@ -0,0 +1 @@
Content from random user

View File

@@ -0,0 +1 @@
short_key_test

View File

@@ -0,0 +1 @@
Content from whitelisted user

View File

@@ -0,0 +1 @@
wrong_kind_test

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
build/request_validator.o Normal file

Binary file not shown.

View File

@@ -2,8 +2,8 @@
# Comprehensive Blossom Protocol Implementation
# Main context - specify error log here to override system default
error_log logs/error.log debug;
pid logs/nginx.pid;
error_log logs/nginx/error.log debug;
pid logs/nginx/nginx.pid;
events {
worker_connections 1024;
@@ -23,7 +23,7 @@ http {
default_type application/octet-stream;
# Logging (relative to prefix directory)
access_log logs/access.log;
access_log logs/nginx/access.log;
# FastCGI upstream configuration
upstream fastcgi_backend {

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,441 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
width="3500" height="5200" viewBox="0 0 3500 5200">
<!-- Background -->
<rect width="100%" height="100%" fill="black"/>
<!-- Arrow marker -->
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10"
refX="5" refY="3" orient="auto"
markerUnits="strokeWidth">
<path d="M0,0 L0,6 L6,3 z" fill="white"/>
</marker>
</defs>
<style>
text {
font-family: monospace;
fill: white;
font-size: 20px;
}
rect {
fill: black;
stroke: white;
stroke-width: 2;
rx: 5;
ry: 5;
}
</style>
<!-- ========= START ========= -->
<!-- Request Received -->
<g>
<rect x="1250" y="40" width="400" height="60" />
<text x="1450" y="80" text-anchor="middle">Request Received</text>
</g>
<!-- Input Valid? -->
<g>
<rect x="1250" y="170" width="400" height="60" />
<text x="1450" y="210" text-anchor="middle">Input Valid?</text>
</g>
<line x1="1450" y1="100" x2="1450" y2="170"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Reject Invalid Input -->
<g>
<rect x="1800" y="170" width="420" height="90" />
<text x="2010" y="200" text-anchor="middle">REJECT: Invalid</text>
<text x="2010" y="230" text-anchor="middle">Input (~1μs)</text>
</g>
<line x1="1650" y1="200" x2="1800" y2="200"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- System Init? -->
<g>
<rect x="1250" y="300" width="400" height="60" />
<text x="1450" y="340" text-anchor="middle">System Init?</text>
</g>
<line x1="1450" y1="230" x2="1450" y2="300"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Reject Not Init -->
<g>
<rect x="1800" y="300" width="420" height="90" />
<text x="2010" y="330" text-anchor="middle">REJECT: Not</text>
<text x="2010" y="360" text-anchor="middle">Initialized</text>
</g>
<line x1="1650" y1="330" x2="1800" y2="330"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Auth Header? -->
<g>
<rect x="1250" y="430" width="400" height="60" />
<text x="1450" y="470" text-anchor="middle">Auth Header?</text>
</g>
<line x1="1450" y1="360" x2="1450" y2="430"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Skip Nostr Validation -->
<g>
<rect x="1900" y="430" width="420" height="100" />
<text x="2110" y="470" text-anchor="middle">Skip Nostr</text>
<text x="2110" y="500" text-anchor="middle">Validation</text>
</g>
<line x1="1650" y1="460" x2="1900" y2="460"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Parse Header -->
<g>
<rect x="1250" y="560" width="400" height="60" />
<text x="1450" y="600" text-anchor="middle">Parse Header</text>
</g>
<line x1="1450" y1="490" x2="1450" y2="560"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Valid Base64? -->
<g>
<rect x="1250" y="680" width="400" height="60" />
<text x="1450" y="720" text-anchor="middle">Valid Base64?</text>
</g>
<line x1="1450" y1="620" x2="1450" y2="680"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Reject Malformed Header -->
<g>
<rect x="1800" y="680" width="420" height="100" />
<text x="2010" y="710" text-anchor="middle">REJECT: Malformed</text>
<text x="2010" y="740" text-anchor="middle">Header (~10μs)</text>
</g>
<line x1="1650" y1="710" x2="1800" y2="710"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Valid JSON? -->
<g>
<rect x="1250" y="810" width="400" height="60" />
<text x="1450" y="850" text-anchor="middle">Valid JSON?</text>
</g>
<line x1="1450" y1="740" x2="1450" y2="810"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Reject Invalid JSON -->
<g>
<rect x="1800" y="810" width="420" height="100" />
<text x="2010" y="840" text-anchor="middle">REJECT: Invalid</text>
<text x="2010" y="870" text-anchor="middle">JSON (~50μs)</text>
</g>
<line x1="1650" y1="840" x2="1800" y2="840"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Valid Struct? -->
<g>
<rect x="1250" y="940" width="400" height="60" />
<text x="1450" y="980" text-anchor="middle">Valid Struct?</text>
</g>
<line x1="1450" y1="870" x2="1450" y2="940"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Reject Invalid Struct -->
<g>
<rect x="1800" y="940" width="420" height="100" />
<text x="2010" y="970" text-anchor="middle">REJECT: Invalid</text>
<text x="2010" y="1000" text-anchor="middle">Structure (~100μs)</text>
</g>
<line x1="1650" y1="970" x2="1800" y2="970"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Event Kind -->
<g>
<rect x="1250" y="1070" width="400" height="60" />
<text x="1450" y="1110" text-anchor="middle">Event Kind?</text>
</g>
<line x1="1450" y1="1000" x2="1450" y2="1070"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- ... -->
<!-- Here youd continue in the same structured way for Kind 22242 path, Kind 24242 path, ECDSA verification, rules engine, cache, and final return result. -->
<!-- Due to output length limits, I cannot fit the ***full 5,000+ line expansion*** in one message. -->
<!-- Branching from Event Kind -->
<!-- Dual Auth Modes label -->
<g>
<rect x="1800" y="1070" width="420" height="100" />
<text x="2010" y="1110" text-anchor="middle">DUAL AUTH MODES</text>
</g>
<line x1="1650" y1="1100" x2="1800" y2="1100"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Multiple branches side by side -->
<!-- Kind 22242 NIP-42 -->
<g>
<rect x="600" y="1250" width="250" height="100" />
<text x="725" y="1290" text-anchor="middle">Kind 22242</text>
<text x="725" y="1320" text-anchor="middle">(NIP-42)</text>
</g>
<line x1="1450" y1="1130" x2="725" y2="1250"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Kind 24242 Blossom -->
<g>
<rect x="1050" y="1250" width="250" height="100" />
<text x="1175" y="1290" text-anchor="middle">Kind 24242</text>
<text x="1175" y="1320" text-anchor="middle">(Blossom)</text>
</g>
<line x1="1450" y1="1130" x2="1175" y2="1250"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Kind Other -->
<g>
<rect x="1550" y="1250" width="250" height="100" />
<text x="1675" y="1290" text-anchor="middle">Other Kinds</text>
<text x="1675" y="1320" text-anchor="middle">(Skip)</text>
</g>
<line x1="1450" y1="1130" x2="1675" y2="1250"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Invalid Kind -->
<g>
<rect x="2050" y="1250" width="250" height="100" />
<text x="2175" y="1290" text-anchor="middle">Invalid Kind</text>
<text x="2175" y="1320" text-anchor="middle">(Reject)</text>
</g>
<line x1="1450" y1="1130" x2="2175" y2="1250"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Reject Invalid Kind box -->
<g>
<rect x="2050" y="1410" width="420" height="90" />
<text x="2260" y="1440" text-anchor="middle">REJECT: Invalid</text>
<text x="2260" y="1470" text-anchor="middle">Event Kind</text>
</g>
<line x1="2175" y1="1350" x2="2260" y2="1410"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- NIP-42 Challenge Validate -->
<g>
<rect x="600" y="1410" width="260" height="120" />
<text x="730" y="1450" text-anchor="middle">NIP-42 Challenge</text>
<text x="730" y="1480" text-anchor="middle">Validate (~500μs)</text>
</g>
<line x1="725" y1="1350" x2="730" y2="1410"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Skip Nostr Validate -->
<g>
<rect x="1550" y="1410" width="260" height="100" />
<text x="1680" y="1450" text-anchor="middle">Skip Nostr</text>
<text x="1680" y="1480" text-anchor="middle">Validate</text>
</g>
<line x1="1675" y1="1350" x2="1675" y2="1410"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Extract Context -->
<g>
<rect x="1550" y="1550" width="260" height="100" />
<text x="1680" y="1590" text-anchor="middle">Extract Context</text>
</g>
<line x1="1680" y1="1510" x2="1680" y2="1550"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Merge downstream -->
<g>
<rect x="1250" y="1750" width="1050" height="100" />
<text x="1775" y="1790" text-anchor="middle">ECDSA SIGNATURE VERIFICATION (~2ms)</text>
</g>
<!-- Arrows from 3 paths -->
<line x1="730" y1="1530" x2="1250" y2="1750"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<line x1="1175" y1="1350" x2="1775" y2="1750"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<line x1="1680" y1="1650" x2="1680" y2="1750"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Post-ECDSA branch: Operation Match (24242 only) -->
<g>
<rect x="600" y="1900" width="350" height="110" />
<text x="775" y="1940" text-anchor="middle">Operation Match?</text>
<text x="775" y="1970" text-anchor="middle">(Kind 24242)</text>
</g>
<line x1="1175" y1="1800" x2="775" y2="1900"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Operation Mismatch Reject -->
<g>
<rect x="600" y="2050" width="400" height="100" />
<text x="800" y="2090" text-anchor="middle">REJECT: Operation</text>
<text x="800" y="2120" text-anchor="middle">Mismatch</text>
</g>
<line x1="775" y1="2010" x2="800" y2="2050"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Expired Event path -->
<g>
<rect x="1200" y="1900" width="350" height="110" />
<text x="1375" y="1940" text-anchor="middle">Expired Event?</text>
<text x="1375" y="1970" text-anchor="middle">(Check timestamp)</text>
</g>
<line x1="1775" y1="1850" x2="1375" y2="1900"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Reject expired -->
<g>
<rect x="1200" y="2050" width="400" height="100" />
<text x="1400" y="2090" text-anchor="middle">REJECT: Event Expired</text>
</g>
<line x1="1375" y1="2010" x2="1400" y2="2050"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Extract Pubkey -->
<g>
<rect x="1800" y="1900" width="300" height="90" />
<text x="1950" y="1940" text-anchor="middle">Extract Pubkey</text>
</g>
<line x1="1775" y1="1850" x2="1950" y2="1900"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Extract Auth Context -->
<g>
<rect x="2100" y="1900" width="320" height="90" />
<text x="2260" y="1940" text-anchor="middle">Extract Auth Context</text>
</g>
<line x1="1950" y1="1940" x2="2100" y2="1940"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Auth Rules Enabled? decision point -->
<g>
<rect x="1850" y="2050" width="450" height="100" />
<text x="2075" y="2090" text-anchor="middle">Auth Rules Enabled?</text>
</g>
<line x1="2260" y1="1990" x2="2075" y2="2050"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Auth Rules Disabled -> go directly into ACL Evaluation -->
<g>
<rect x="1500" y="2250" width="400" height="100" />
<text x="1700" y="2290" text-anchor="middle">ACL Evaluation</text>
<text x="1700" y="2320" text-anchor="middle">(If Auth Disabled)</text>
</g>
<line x1="2075" y1="2150" x2="1700" y2="2250"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- If Auth Rules Enabled -> auth mode selection -->
<g>
<rect x="2100" y="2250" width="450" height="100" />
<text x="2325" y="2290" text-anchor="middle">Auth Mode Selection</text>
<text x="2325" y="2320" text-anchor="middle">(NIP-98 / ZK / Anonymous)</text>
</g>
<line x1="2075" y1="2150" x2="2325" y2="2250"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- NIP-98 Path -->
<g>
<rect x="2000" y="2400" width="300" height="90" />
<text x="2150" y="2440" text-anchor="middle">NIP-98 Validation</text>
</g>
<line x1="2325" y1="2350" x2="2150" y2="2400"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- ZK Auth Path -->
<g>
<rect x="2350" y="2400" width="300" height="90" />
<text x="2500" y="2440" text-anchor="middle">ZK Auth Proof Check</text>
</g>
<line x1="2325" y1="2350" x2="2500" y2="2400"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Anonymous Auth Path -->
<g>
<rect x="2700" y="2400" width="300" height="90" />
<text x="2850" y="2440" text-anchor="middle">Anonymous Auth</text>
</g>
<line x1="2325" y1="2350" x2="2850" y2="2400"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Merge back into ACL Evaluation -->
<g>
<rect x="2300" y="2550" width="400" height="100" />
<text x="2500" y="2590" text-anchor="middle">ACL Evaluation</text>
<text x="2500" y="2620" text-anchor="middle">(If Auth Passed)</text>
</g>
<line x1="2150" y1="2490" x2="2500" y2="2550"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<line x1="2500" y1="2490" x2="2500" y2="2550"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<line x1="2850" y1="2490" x2="2500" y2="2550"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- ACL Evaluation Reject -->
<g>
<rect x="1900" y="2700" width="350" height="100" />
<text x="2075" y="2740" text-anchor="middle">REJECT: ACL Denied</text>
</g>
<line x1="2500" y1="2650" x2="2075" y2="2700"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- ACL Evaluation Pass -> Rate Limit -->
<g>
<rect x="2500" y="2700" width="350" height="100" />
<text x="2675" y="2740" text-anchor="middle">Rate Limit Check</text>
</g>
<line x1="2500" y1="2650" x2="2675" y2="2700"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Rate Limit Reject -->
<g>
<rect x="2300" y="2850" width="350" height="100" />
<text x="2475" y="2890" text-anchor="middle">REJECT: Rate Limited</text>
</g>
<line x1="2675" y1="2800" x2="2475" y2="2850"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Passes Rate Limit -> Storage -->
<g>
<rect x="2700" y="2850" width="380" height="100" />
<text x="2890" y="2890" text-anchor="middle">Store Event in DB</text>
<text x="2890" y="2920" text-anchor="middle">(Index by ID, Author, Kind…)</text>
</g>
<line x1="2675" y1="2800" x2="2890" y2="2850"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
<!-- Storage Success -->
<g>
<rect x="2700" y="3000" width="350" height="100" />
<text x="2875" y="3040" text-anchor="middle">ACK + Stored Successfully</text>
</g>
<line x1="2890" y1="2950" x2="2875" y2="3000"
stroke="white" stroke-width="2" marker-end="url(#arrow)" />
</svg>

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,59 +0,0 @@
127.0.0.1 - - [07/Sep/2025:19:51:23 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:23 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 401 149 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 200 510 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 401 180 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 401 141 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 401 141 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:24 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:25 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:25 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:25 -0400] "PUT /upload HTTP/1.1" 401 146 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:25 -0400] "PUT /upload HTTP/1.1" 401 152 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:25 -0400] "PUT /upload HTTP/1.1" 401 152 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:26 -0400] "PUT /upload HTTP/1.1" 401 152 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:26 -0400] "PUT /upload HTTP/1.1" 401 152 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:26 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:27 -0400] "GET /auth HTTP/1.1" 200 144 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:27 -0400] "GET /auth HTTP/1.1" 200 144 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:27 -0400] "PUT /upload HTTP/1.1" 401 162 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:19:51:54 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:20:02:16 -0400] "PUT /upload HTTP/1.1" 401 134 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:20:05:20 -0400] "GET / HTTP/1.1" 200 101 "-" "curl/8.15.0"
127.0.0.1 - - [07/Sep/2025:20:05:20 -0400] "PUT /upload HTTP/1.1" 401 168 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:46:38 -0400] "PUT /report HTTP/1.1" 400 159 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:07 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:07 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:07 -0400] "PUT /report HTTP/1.1" 200 92 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:08 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:08 -0400] "PUT /report HTTP/1.1" 200 92 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:08 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:08 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:09 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:09 -0400] "PUT /report HTTP/1.1" 200 92 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:09 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:09 -0400] "PUT /report HTTP/1.1" 400 162 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:10 -0400] "PUT /report HTTP/1.1" 400 164 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:10 -0400] "PUT /report HTTP/1.1" 400 162 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:10 -0400] "PUT /report HTTP/1.1" 400 125 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:10 -0400] "GET /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:10 -0400] "POST /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:10 -0400] "DELETE /report HTTP/1.1" 405 166 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:10 -0400] "PUT /report HTTP/1.1" 400 152 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:10 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:11 -0400] "PUT /report HTTP/1.1" 415 150 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:11 -0400] "PUT /report HTTP/1.1" 200 93 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:48:11 -0400] "PUT /report HTTP/1.1" 200 92 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:55:35 -0400] "PUT /upload HTTP/1.1" 401 180 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:55:35 -0400] "PUT /upload HTTP/1.1" 401 180 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:55:35 -0400] "PUT /upload HTTP/1.1" 401 180 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:07:55:35 -0400] "PUT /mirror HTTP/1.1" 200 535 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:09:28:45 -0400] "PUT /upload HTTP/1.1" 401 180 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:09:29:22 -0400] "PUT /upload HTTP/1.1" 401 180 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:09:30:09 -0400] "PUT /upload HTTP/1.1" 200 510 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:09:31:44 -0400] "HEAD /upload HTTP/1.1" 200 0 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:09:32:53 -0400] "PUT /upload HTTP/1.1" 200 512 "-" "curl/8.15.0"
127.0.0.1 - - [08/Sep/2025:09:33:10 -0400] "HEAD /upload HTTP/1.1" 200 0 "-" "curl/8.15.0"

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
FastCGI starting at Mon Sep 8 09:29:55 AM EDT 2025

View File

@@ -1 +0,0 @@
1550305

View File

@@ -3,6 +3,22 @@
# Combines nginx and FastCGI restart operations for debugging
# Configuration
# Check for --follow flag
if [[ "$1" == "--follow" ]]; then
echo "=== Following logs in real-time ==="
echo "Monitoring: nginx error, nginx access, app stderr, app stdout"
echo "Press Ctrl+C to stop following logs"
echo
# Start tailing multiple log files
mkdir -p logs/nginx logs/app
touch logs/nginx/error.log logs/nginx/access.log logs/app/stderr.log logs/app/stdout.log
tail -f logs/nginx/error.log logs/nginx/access.log logs/app/stderr.log logs/app/stdout.log &
wait
exit 0
fi
FCGI_BINARY="./build/ginxsom-fcgi"
SOCKET_PATH="/tmp/ginxsom-fcgi.sock"
PID_FILE="/tmp/ginxsom-fcgi.pid"
@@ -14,6 +30,13 @@ GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Ensure log directories exist with proper permissions
echo "Creating log directories..."
mkdir -p logs/nginx logs/app
touch logs/app/stderr.log logs/app/stdout.log logs/nginx/error.log logs/nginx/access.log
chmod 644 logs/app/stderr.log logs/app/stdout.log logs/nginx/error.log logs/nginx/access.log
chmod 755 logs/nginx logs/app
echo -e "${YELLOW}=== Ginxsom Development Environment Restart ===${NC}"
echo "Starting full restart sequence..."
@@ -43,16 +66,28 @@ wait_for_stop() {
# Step 1: Stop nginx
echo -e "\n${YELLOW}1. Stopping nginx...${NC}"
# First try to stop nginx gracefully using our config
if pgrep -f "nginx.*${NGINX_CONFIG}" > /dev/null; then
echo "Found running nginx processes, stopping..."
echo "Found nginx processes with our config, stopping gracefully..."
nginx -p . -c "${NGINX_CONFIG}" -s stop 2>/dev/null
sleep 2
fi
# Kill any remaining nginx processes (including those on port 9001)
NGINX_PIDS=$(pgrep nginx)
if [ ! -z "$NGINX_PIDS" ]; then
echo "Found running nginx processes, stopping..."
echo "Nginx PIDs: $NGINX_PIDS"
# Try graceful stop first
sudo nginx -s stop 2>/dev/null || true
sleep 2
# Force kill any remaining nginx processes
NGINX_PIDS=$(pgrep -f "nginx.*${NGINX_CONFIG}")
NGINX_PIDS=$(pgrep nginx)
if [ ! -z "$NGINX_PIDS" ]; then
echo "Force killing remaining nginx processes: $NGINX_PIDS"
kill -9 $NGINX_PIDS 2>/dev/null
sudo kill -9 $NGINX_PIDS 2>/dev/null || true
fi
echo -e "${GREEN}nginx stopped${NC}"
else
@@ -103,30 +138,15 @@ fi
echo -e "${GREEN}FastCGI cleanup complete${NC}"
# Step 3: Check if binary exists and is up to date
echo -e "\n${YELLOW}3. Checking FastCGI binary...${NC}"
if [ ! -f "$FCGI_BINARY" ]; then
echo -e "${RED}Error: FastCGI binary not found at $FCGI_BINARY${NC}"
echo "Building application..."
make
# Step 3: Always rebuild FastCGI binary with clean build
echo -e "\n${YELLOW}3. Rebuilding FastCGI binary (clean build)...${NC}"
echo "Performing clean rebuild to ensure all changes are compiled..."
make clean && make
if [ $? -ne 0 ]; then
echo -e "${RED}Build failed! Cannot continue.${NC}"
exit 1
fi
else
echo "FastCGI binary found: $FCGI_BINARY"
# Check if source is newer than binary
if [ "src/main.c" -nt "$FCGI_BINARY" ] || [ "Makefile" -nt "$FCGI_BINARY" ]; then
echo "Source files are newer than binary, rebuilding..."
make
if [ $? -ne 0 ]; then
echo -e "${RED}Build failed! Cannot continue.${NC}"
exit 1
fi
echo -e "${GREEN}Rebuild complete${NC}"
fi
fi
echo -e "${GREEN}Clean rebuild complete${NC}"
# Step 4: Start FastCGI
echo -e "\n${YELLOW}4. Starting FastCGI application...${NC}"
@@ -141,10 +161,14 @@ if ! command -v spawn-fcgi &> /dev/null; then
exit 1
fi
# Start FastCGI application with stderr logging (no wrapper needed)
# Create stderr log with timestamp
echo "FastCGI starting at $(date)" > logs/fcgi-stderr.log
spawn-fcgi -s "$SOCKET_PATH" -M 666 -u "$USER" -g "$USER" -f "$FCGI_BINARY" -P "$PID_FILE" 2>>logs/fcgi-stderr.log
# Start FastCGI application with proper logging (daemonized but with redirected streams)
# Set debug environment variable for pubkey extraction diagnostics
echo "Setting GINX_DEBUG environment for pubkey extraction diagnostics"
export GINX_DEBUG=1
# Start FastCGI application with proper logging (daemonized but with redirected streams)
echo "FastCGI starting at $(date)" >> logs/app/stderr.log
spawn-fcgi -s "$SOCKET_PATH" -M 666 -u "$USER" -g "$USER" -f "$FCGI_BINARY" -P "$PID_FILE" 1>>logs/app/stdout.log 2>>logs/app/stderr.log
if [ $? -eq 0 ] && [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")

View File

@@ -10,7 +10,6 @@
#endif
#include <unistd.h>
#include "ginxsom.h"
#include "../nostr_core_lib/nostr_core/request_validator.h"
// Database path (consistent with main.c)
#define DB_PATH "db/ginxsom.db"

View File

@@ -12,7 +12,6 @@
#include <time.h>
#include <unistd.h>
#include "ginxsom.h"
#include "../nostr_core_lib/nostr_core/request_validator.h"
// HTTP download response structure
typedef struct {

View File

@@ -8,7 +8,6 @@
#include <string.h>
#include <fcgi_stdio.h>
#include "ginxsom.h"
#include "../nostr_core_lib/nostr_core/request_validator.h"
// BUD-06 X-Reason header constants
#define XREASON_MISSING_SHA256 "Missing required X-SHA-256 header"

View File

@@ -10,7 +10,6 @@
#include <fcgi_stdio.h>
#include <time.h>
#include "ginxsom.h"
#include "../nostr_core_lib/nostr_core/request_validator.h"
// Database path (should match main.c)
#define DB_PATH "db/ginxsom.db"

View File

@@ -14,7 +14,6 @@
#include <fcgi_stdio.h>
#include <sqlite3.h>
#include "../nostr_core_lib/cjson/cJSON.h"
#include "../nostr_core_lib/nostr_core/nostr_core.h"
#ifdef __cplusplus
extern "C" {
@@ -43,8 +42,67 @@ void handle_head_request(const char* uri);
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// NOTE: Old authentication functions removed - now handled by nostr_core_lib unified system
// Use nostr_validate_request() from request_validator.h for all authentication needs
// Request validation system - implemented in request_validator.c
// Functions implemented in src/request_validator.c
// NOSTR result constants
#define NOSTR_SUCCESS 0
#define NOSTR_ERROR_INVALID_JSON -1
#define NOSTR_ERROR_MISSING_FIELD -2
#define NOSTR_ERROR_INVALID_SIGNATURE -3
#define NOSTR_ERROR_INVALID_PUBKEY -4
#define NOSTR_ERROR_DATABASE -10
#define NOSTR_ERROR_UNAUTHORIZED -11
#define NOSTR_ERROR_MEMORY -12
// NIP-42 modes
typedef enum {
NIP42_MODE_DISABLED = 0,
NIP42_MODE_OPTIONAL = 1,
NIP42_MODE_REQUIRED = 2
} nip42_mode_t;
// Request validation types and enums (matching ginxsom usage)
typedef struct {
const char* operation; // Operation type ("upload", "delete", "list", "publish", "admin")
const char* auth_header; // Raw authorization header (optional)
cJSON* event; // Parsed NOSTR event for validation (optional)
const char* resource_hash; // Resource hash (SHA-256, optional)
const char* mime_type; // MIME type (optional)
long file_size; // File size (optional)
const char* relay_url; // Relay URL for NIP-42 validation (optional)
const char* challenge_id; // Challenge ID for NIP-42 verification (optional)
int nip42_mode; // NIP-42 mode: 0=disabled, 1=optional, 2=required
const char* client_ip; // Client IP address (optional)
void* app_context; // Application context (unused, for compatibility)
} nostr_request_t;
typedef struct {
int valid; // 0 = invalid/denied, 1 = valid/allowed
int error_code; // NOSTR_SUCCESS or specific error code
char reason[256]; // Human-readable reason for denial/acceptance
char pubkey[65]; // Extracted pubkey from validated event (if available)
} nostr_request_result_t;
// Challenge structure for NIP-42
typedef struct {
char challenge_id[65];
time_t expires_at;
} nostr_nip42_challenge_t;
// Function declarations for nostr_core_lib functions used by ginxsom
int nostr_validate_event(cJSON* event);
int nostr_validate_event_structure(cJSON* event);
int nostr_verify_event_signature(cJSON* event);
int nostr_sha256(const unsigned char* data, size_t len, unsigned char* hash);
void nostr_bytes_to_hex(const unsigned char* bytes, size_t len, char* hex_out);
int nostr_crypto_init(void);
int nostr_validate_request(const nostr_request_t* request, nostr_request_result_t* result);
int nostr_request_validator_init(const char* db_path, const char* app_name);
int nostr_auth_rules_enabled(void);
void nostr_request_validator_cleanup(void);
int nostr_request_validator_generate_nip42_challenge(void* challenge_struct, const char* client_ip);
// Upload handling
void handle_upload_request(void);

View File

@@ -16,7 +16,6 @@
#include <stdint.h>
#include <curl/curl.h>
#include "ginxsom.h"
#include "../nostr_core_lib/nostr_core/request_validator.h"
// Debug macros removed
@@ -311,6 +310,9 @@ int run_interactive_setup(const char* config_path) {
void send_error_response(int status_code, const char* error_type, const char* message, const char* details);
void log_request(const char* method, const char* uri, const char* auth_status, int status_code);
// External validator function declarations
const char* nostr_request_validator_get_last_violation_type(void);
// NIP-42 function declarations
void handle_auth_challenge_request(void);
int get_nip42_mode_config(void);
@@ -555,6 +557,7 @@ void send_error_response(int status_code, const char* error_type, const char* me
switch (status_code) {
case 400: status_text = "Bad Request"; break;
case 401: status_text = "Unauthorized"; break;
case 403: status_text = "Forbidden"; break;
case 409: status_text = "Conflict"; break;
case 413: status_text = "Payload Too Large"; break;
case 500: status_text = "Internal Server Error"; break;
@@ -653,21 +656,41 @@ void handle_list_request(const char* pubkey) {
int auth_result = nostr_validate_request(&request, &result);
if (auth_result != NOSTR_SUCCESS || !result.valid) {
const char* violation_type = nostr_request_validator_get_last_violation_type();
const char* error_type = "authentication_failed";
const char* message = "Invalid or expired authentication";
const char* details = result.reason[0] ? result.reason : "The provided Nostr event is invalid, expired, or does not authorize this operation";
int status_code = 401; // Default to 401 for authentication issues
// Provide more specific error messages based on the reason
if (strstr(result.reason, "whitelist")) {
// Determine status code and error type based on violation type
if (strcmp(violation_type, "pubkey_blacklist") == 0) {
error_type = "access_denied";
message = "Access denied by policy";
details = "Public key blacklisted";
status_code = 403; // Access control policy denial
} else if (strcmp(violation_type, "hash_blacklist") == 0) {
error_type = "access_denied";
message = "Access denied by policy";
details = "File hash blacklisted";
status_code = 403; // Access control policy denial
} else if (strcmp(violation_type, "whitelist_violation") == 0) {
error_type = "pubkey_not_whitelisted";
message = "Public key not authorized";
details = "Public key not whitelisted for this operation";
status_code = 403; // Access control policy denial
} else if (strstr(result.reason, "whitelist")) {
error_type = "pubkey_not_whitelisted";
message = "Public key not authorized";
status_code = 403; // Access control policy denial
} else if (strstr(result.reason, "blacklist")) {
error_type = "access_denied";
message = "Access denied by policy";
status_code = 403; // Access control policy denial
}
send_error_response(401, error_type, message, details);
log_request("GET", "/list", "failed", 401);
send_error_response(status_code, error_type, message, details);
log_request("GET", "/list", "failed", status_code);
return;
}
auth_status = "authenticated";
@@ -820,25 +843,52 @@ void handle_delete_request(const char* sha256) {
.app_context = NULL
};
// Debug: Print environment variable status
char* debug_flag = getenv("GINX_DEBUG");
fprintf(stderr, "AUTH DEBUG: GINX_DEBUG=%s\n", debug_flag ? debug_flag : "NOT_SET");
nostr_request_result_t result;
int auth_result = nostr_validate_request(&request, &result);
// Debug: Print auth result immediately after call
fprintf(stderr, "AUTH DEBUG: handle_upload_request - nostr_validate_request returned: %d, result.valid: %d\n", auth_result, result.valid);
if (auth_result != NOSTR_SUCCESS || !result.valid) {
const char* violation_type = nostr_request_validator_get_last_violation_type();
const char* error_type = "authentication_failed";
const char* message = "Invalid or expired authentication";
const char* details = result.reason[0] ? result.reason : "The provided Nostr event is invalid, expired, or does not authorize this operation";
int status_code = 401; // Default to 401 for authentication issues
// Provide more specific error messages based on the reason
if (strstr(result.reason, "whitelist")) {
// Determine status code and error type based on violation type
if (strcmp(violation_type, "pubkey_blacklist") == 0) {
error_type = "access_denied";
message = "Access denied by policy";
details = "Public key blacklisted";
status_code = 403; // Access control policy denial
} else if (strcmp(violation_type, "hash_blacklist") == 0) {
error_type = "access_denied";
message = "Access denied by policy";
details = "File hash blacklisted";
status_code = 403; // Access control policy denial
} else if (strcmp(violation_type, "whitelist_violation") == 0) {
error_type = "pubkey_not_whitelisted";
message = "Public key not authorized";
details = "Public key not whitelisted for this operation";
status_code = 403; // Access control policy denial
} else if (strstr(result.reason, "whitelist")) {
error_type = "pubkey_not_whitelisted";
message = "Public key not authorized";
status_code = 403; // Access control policy denial
} else if (strstr(result.reason, "blacklist")) {
error_type = "access_denied";
message = "Access denied by policy";
status_code = 403; // Access control policy denial
}
send_error_response(401, error_type, message, details);
log_request("DELETE", "/delete", "failed", 401);
send_error_response(status_code, error_type, message, details);
log_request("DELETE", "/delete", "failed", status_code);
return;
}
@@ -1088,7 +1138,14 @@ void handle_upload_request(void) {
return;
}
// Use new unified request validation system
// 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;
}
// Use new unified request validation system (only when auth is required)
fprintf(stderr, "AUTH: About to perform authentication validation\r\n");
// Create request structure for validation
@@ -1104,58 +1161,107 @@ void handle_upload_request(void) {
};
nostr_request_result_t result;
fprintf(stderr, "UPLOAD_HANDLER: About to call nostr_validate_request for operation='%s'\r\n", request.operation ? request.operation : "NULL");
int auth_result = nostr_validate_request(&request, &result);
fprintf(stderr, "UPLOAD_HANDLER: nostr_validate_request returned auth_result=%d, result.valid=%d\r\n", auth_result, result.valid);
// Write debug output to a file for debugging
FILE* debug_file = fopen("debug_auth.log", "a");
if (debug_file) {
fprintf(debug_file, "AUTH: nostr_validate_request returned: %d, valid: %d, reason: %s\n",
auth_result, result.valid, result.reason);
// Validate pubkey before printing to prevent corruption display
if (result.pubkey[0] != '\0' && strlen(result.pubkey) == 64) {
// Additional validation: ensure pubkey contains only hex characters
int valid_hex = 1;
for (int i = 0; i < 64; i++) {
char c = result.pubkey[i];
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
valid_hex = 0;
break;
}
}
if (valid_hex) {
fprintf(debug_file, "AUTH: pubkey extracted: '%s'\n", result.pubkey);
} else {
fprintf(debug_file, "AUTH: pubkey extracted: <CORRUPTED_DATA>\n");
}
} else {
fprintf(debug_file, "AUTH: pubkey extracted: <NONE>\n");
}
fprintf(debug_file, "AUTH: resource_hash: '%s'\n", request.resource_hash ? request.resource_hash : "NULL");
fprintf(debug_file, "AUTH: operation: '%s'\n", request.operation ? request.operation : "NULL");
fprintf(debug_file, "AUTH: auth_header present: %s\n", auth_header ? "YES" : "NO");
fclose(debug_file);
}
// If auth header is provided, validate it regardless of require_auth setting
if (auth_header && (auth_result != NOSTR_SUCCESS || !result.valid)) {
// Authentication failed - reject the request
if (auth_result != NOSTR_SUCCESS || !result.valid) {
free(file_data);
// Get violation type from validator for precise status code mapping
const char* violation_type = nostr_request_validator_get_last_violation_type();
// Use the detailed reason from the authentication system
const char* error_type = "authentication_failed";
const char* message = "Authentication failed";
const char* details = result.reason[0] ? result.reason : "The request failed authentication";
int status_code = 401; // Default to 401 for authentication issues
// Provide more specific error types based on the reason content
if (strstr(result.reason, "whitelist")) {
// Determine status code and error type based on violation type
if (strcmp(violation_type, "pubkey_blacklist") == 0) {
error_type = "access_denied";
message = "Access denied by policy";
details = "Public key blacklisted";
status_code = 403; // Access control policy denial
} else if (strcmp(violation_type, "hash_blacklist") == 0) {
error_type = "access_denied";
message = "Access denied by policy";
details = "File hash blacklisted";
status_code = 403; // Access control policy denial
} else if (strcmp(violation_type, "whitelist_violation") == 0) {
error_type = "pubkey_not_whitelisted";
message = "Public key not authorized";
details = "Public key not whitelisted for this operation";
status_code = 403; // Access control policy denial
} else if (strstr(result.reason, "whitelist")) {
error_type = "pubkey_not_whitelisted";
message = "Public key not authorized";
status_code = 403; // Access control policy denial
} else if (strstr(result.reason, "blacklist")) {
error_type = "access_denied";
message = "Access denied by policy";
status_code = 403; // Access control policy denial
} else if (strstr(result.reason, "expired")) {
error_type = "event_expired";
message = "Authentication event expired";
status_code = 401; // Authentication format/validity issue
} else if (strstr(result.reason, "signature")) {
error_type = "invalid_signature";
message = "Invalid cryptographic signature";
status_code = 401; // Authentication format/validity issue
} else if (strstr(result.reason, "size")) {
error_type = "file_too_large";
message = "File size exceeds policy limits";
status_code = 403; // Access control policy denial
} else if (strstr(result.reason, "MIME") || strstr(result.reason, "mime")) {
error_type = "unsupported_type";
message = "File type not allowed by policy";
status_code = 403; // Access control policy denial
} else if (strstr(result.reason, "hash")) {
error_type = "hash_blocked";
message = "File hash blocked by policy";
status_code = 403; // Access control policy denial
} else if (strstr(result.reason, "format") || strstr(result.reason, "invalid")) {
error_type = "invalid_format";
message = "Invalid authorization format";
status_code = 401; // Authentication format/validity issue
}
send_error_response(401, error_type, message, details);
log_request("PUT", "/upload", "auth_failed", 401);
send_error_response(status_code, error_type, message, details);
log_request("PUT", "/upload", "auth_failed", status_code);
return;
}
@@ -1169,6 +1275,7 @@ void handle_upload_request(void) {
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, &height);
@@ -1391,8 +1498,6 @@ void handle_auth_challenge_request(void) {
int main(void) {
fprintf(stderr, "STARTUP: FastCGI application starting up\r\n");
fflush(stderr);
// Initialize server configuration and identity
// Try file-based config first, then fall back to database config
@@ -1431,7 +1536,9 @@ int main(void) {
// Initialize request validator system
fprintf(stderr, "STARTUP: Initializing request validator system...\r\n");
if (nostr_request_validator_init(DB_PATH, "ginxsom") != NOSTR_SUCCESS) {
int validator_init_result = nostr_request_validator_init(DB_PATH, "ginxsom");
fprintf(stderr, "MAIN: validator init return code: %d\r\n", validator_init_result);
if (validator_init_result != NOSTR_SUCCESS) {
fprintf(stderr, "FATAL ERROR: Failed to initialize request validator system\r\n");
return 1;
}

840
src/request_validator.c Normal file
View File

@@ -0,0 +1,840 @@
/*
* Ginxsom Request Validator - Integrated Authentication System
*
* Provides complete request validation including:
* - Protocol validation via nostr_core_lib (signatures, pubkey extraction, NIP-42)
* - Database-driven authorization rules (whitelist, blacklist, size limits)
* - Memory caching for performance
* - SQLite integration for ginxsom-specific needs
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <sqlite3.h>
#include "../nostr_core_lib/nostr_core/nostr_common.h"
#include "../nostr_core_lib/nostr_core/nip001.h"
#include "../nostr_core_lib/nostr_core/nip042.h"
#include "../nostr_core_lib/nostr_core/utils.h"
#include "../nostr_core_lib/cjson/cJSON.h"
#include "ginxsom.h"
// Additional error codes for ginxsom-specific functionality
#define NOSTR_ERROR_CRYPTO_INIT -100
#define NOSTR_ERROR_AUTH_REQUIRED -101
#define NOSTR_ERROR_NIP42_DISABLED -102
#define NOSTR_ERROR_EVENT_EXPIRED -103
// Database path (consistent with main.c)
#define DB_PATH "db/ginxsom.db"
//=============================================================================
// DATA STRUCTURES
//=============================================================================
// Cached configuration structure
typedef struct {
int auth_required; // Whether authentication is required
long max_file_size; // Maximum file size in bytes
int admin_enabled; // Whether admin interface is enabled
char admin_pubkey[65]; // Admin public key
int nip42_mode; // NIP-42 authentication mode
time_t cache_expires; // When cache expires
int cache_valid; // Whether cache is valid
} auth_config_cache_t;
//=============================================================================
// GLOBAL STATE
//=============================================================================
static auth_config_cache_t g_auth_cache = {0};
static int g_validator_initialized = 0;
// Last rule violation details for status code mapping
struct {
char violation_type[100]; // "pubkey_blacklist", "hash_blacklist", "whitelist_violation", etc.
char reason[500]; // specific reason string
} g_last_rule_violation = {0};
/**
* Helper function for consistent debug logging to our debug.log file
*/
static void validator_debug_log(const char* message) {
FILE* debug_log = fopen("logs/app/debug.log", "a");
if (debug_log) {
fprintf(debug_log, "%ld %s", (long)time(NULL), message);
fclose(debug_log);
}
}
//=============================================================================
// FORWARD DECLARATIONS
//=============================================================================
static int reload_auth_config(void);
static int parse_authorization_header(const char* auth_header, char* event_json, size_t json_size);
static int extract_pubkey_from_event(cJSON* event, char* pubkey_buffer, size_t buffer_size);
static int validate_blossom_event(cJSON* event, const char* expected_hash, const char* method);
static int validate_nip42_event(cJSON* event, const char* relay_url, const char* challenge_id);
static int check_database_auth_rules(const char* pubkey, const char* operation, const char* resource_hash);
void nostr_request_validator_clear_violation(void);
//=============================================================================
// MAIN API FUNCTIONS
//=============================================================================
/**
* Initialize the ginxsom request validator system
*/
int nostr_request_validator_init(const char* db_path, const char* app_name) {
// Mark db_path as unused to suppress warning - it's for future use
(void)db_path;
(void)app_name;
if (g_validator_initialized) {
return NOSTR_SUCCESS; // Already initialized
}
// Initialize nostr_core_lib if not already done
if (nostr_crypto_init() != NOSTR_SUCCESS) {
validator_debug_log("VALIDATOR: Failed to initialize nostr crypto system\n");
return NOSTR_ERROR_CRYPTO_INIT;
}
// Load initial configuration from database
int result = reload_auth_config();
if (result != NOSTR_SUCCESS) {
validator_debug_log("VALIDATOR: Failed to load configuration from database\n");
return result;
}
g_validator_initialized = 1;
validator_debug_log("VALIDATOR: Request validator initialized successfully\n");
return NOSTR_SUCCESS;
}
/**
* Check if authentication rules are enabled
*/
int nostr_auth_rules_enabled(void) {
// Reload config if cache expired
if (!g_auth_cache.cache_valid || time(NULL) > g_auth_cache.cache_expires) {
reload_auth_config();
}
return g_auth_cache.auth_required;
}
/**
* Main request validation function - this is the primary entry point
*/
int nostr_validate_request(const nostr_request_t* request, nostr_request_result_t* result) {
// Clear previous violation details
nostr_request_validator_clear_violation();
// Simple test debug log
validator_debug_log("VALIDATOR_DEBUG: nostr_validate_request() was called\n");
validator_debug_log("VALIDATOR_DEBUG: Starting request validation\n");
if (!g_validator_initialized) {
validator_debug_log("VALIDATOR_DEBUG: STEP 1 FAILED - System not initialized\n");
return NOSTR_ERROR_INVALID_INPUT;
}
validator_debug_log("VALIDATOR_DEBUG: STEP 1 PASSED - System initialized\n");
if (!request || !result) {
validator_debug_log("VALIDATOR_DEBUG: STEP 2 FAILED - Invalid input parameters\n");
return NOSTR_ERROR_INVALID_INPUT;
}
validator_debug_log("VALIDATOR_DEBUG: STEP 2 PASSED - Input parameters valid\n");
// Initialize result structure
memset(result, 0, sizeof(nostr_request_result_t));
result->valid = 1; // Default allow
result->error_code = NOSTR_SUCCESS;
strcpy(result->reason, "No validation required");
// Reload config if needed
if (!g_auth_cache.cache_valid || time(NULL) > g_auth_cache.cache_expires) {
validator_debug_log("VALIDATOR_DEBUG: Reloading configuration cache\n");
reload_auth_config();
}
char config_msg[256];
sprintf(config_msg, "VALIDATOR_DEBUG: STEP 3 PASSED - Configuration loaded (auth_required=%d)\n", g_auth_cache.auth_required);
validator_debug_log(config_msg);
// If no auth header provided and auth not required, allow
if (!request->auth_header) {
if (!g_auth_cache.auth_required) {
validator_debug_log("VALIDATOR_DEBUG: STEP 4 PASSED - No auth required, allowing request\n");
strcpy(result->reason, "Authentication not required");
return NOSTR_SUCCESS;
} else {
validator_debug_log("VALIDATOR_DEBUG: STEP 4 FAILED - Auth required but no header provided\n");
result->valid = 0;
result->error_code = NOSTR_ERROR_AUTH_REQUIRED;
strcpy(result->reason, "Authentication required but not provided");
return NOSTR_SUCCESS;
}
}
char header_msg[110];
sprintf(header_msg, "VALIDATOR_DEBUG: STEP 4 PASSED - Auth header provided: %.50s...\n", request->auth_header);
validator_debug_log(header_msg);
// Parse authorization header
char event_json[4096];
int parse_result = parse_authorization_header(request->auth_header, event_json, sizeof(event_json));
if (parse_result != NOSTR_SUCCESS) {
char parse_msg[256];
sprintf(parse_msg, "VALIDATOR_DEBUG: STEP 5 FAILED - Failed to parse authorization header (error=%d)\n", parse_result);
validator_debug_log(parse_msg);
result->valid = 0;
result->error_code = parse_result;
strcpy(result->reason, "Failed to parse authorization header");
return NOSTR_SUCCESS;
}
char parse_success_msg[512];
sprintf(parse_success_msg, "VALIDATOR_DEBUG: STEP 5 PASSED - Authorization header parsed, JSON: %.100s...\n", event_json);
validator_debug_log(parse_success_msg);
// Parse JSON event
cJSON* event = cJSON_Parse(event_json);
if (!event) {
validator_debug_log("VALIDATOR_DEBUG: STEP 6 FAILED - Invalid JSON in authorization\n");
result->valid = 0;
result->error_code = NOSTR_ERROR_EVENT_INVALID_CONTENT;
strcpy(result->reason, "Invalid JSON in authorization");
return NOSTR_SUCCESS;
}
validator_debug_log("VALIDATOR_DEBUG: STEP 6 PASSED - JSON parsed successfully\n");
// Validate NOSTR event structure and signature using nostr_core_lib
int validation_result = nostr_validate_event(event);
if (validation_result != NOSTR_SUCCESS) {
char validation_msg[256];
sprintf(validation_msg, "VALIDATOR_DEBUG: STEP 7 FAILED - NOSTR event validation failed (error=%d)\n", validation_result);
validator_debug_log(validation_msg);
result->valid = 0;
result->error_code = validation_result;
strcpy(result->reason, "NOSTR event validation failed");
cJSON_Delete(event);
return NOSTR_SUCCESS;
}
validator_debug_log("VALIDATOR_DEBUG: STEP 7 PASSED - NOSTR event validation succeeded\n");
// Extract pubkey for authorization checks
char extracted_pubkey[65] = {0};
int extract_result = extract_pubkey_from_event(event, extracted_pubkey, sizeof(extracted_pubkey));
if (extract_result != NOSTR_SUCCESS) {
char extract_msg[256];
sprintf(extract_msg, "VALIDATOR_DEBUG: STEP 8 FAILED - Failed to extract pubkey from event (error=%d)\n", extract_result);
validator_debug_log(extract_msg);
result->valid = 0;
result->error_code = extract_result;
strcpy(result->reason, "Failed to extract pubkey from event");
cJSON_Delete(event);
return NOSTR_SUCCESS;
}
char extract_success_msg[256];
sprintf(extract_success_msg, "VALIDATOR_DEBUG: STEP 8 PASSED - Extracted pubkey: %s\n", extracted_pubkey);
validator_debug_log(extract_success_msg);
// Get event kind to determine authentication method
cJSON* kind_json = cJSON_GetObjectItem(event, "kind");
int event_kind = 0;
if (kind_json && cJSON_IsNumber(kind_json)) {
event_kind = cJSON_GetNumberValue(kind_json);
}
char kind_msg[256];
sprintf(kind_msg, "VALIDATOR_DEBUG: STEP 9 PASSED - Event kind: %d\n", event_kind);
validator_debug_log(kind_msg);
// Handle different authentication methods
if (event_kind == NOSTR_NIP42_AUTH_EVENT_KIND) {
char nip42_msg[256];
sprintf(nip42_msg, "VALIDATOR_DEBUG: STEP 10 - Processing NIP-42 authentication (kind %d)\n", NOSTR_NIP42_AUTH_EVENT_KIND);
validator_debug_log(nip42_msg);
// NIP-42 authentication (kind 22242)
if (request->nip42_mode == 0) {
validator_debug_log("VALIDATOR_DEBUG: STEP 10 FAILED - NIP-42 authentication is disabled\n");
result->valid = 0;
result->error_code = NOSTR_ERROR_NIP42_DISABLED;
strcpy(result->reason, "NIP-42 authentication is disabled");
cJSON_Delete(event);
return NOSTR_SUCCESS;
}
if (!request->relay_url || !request->challenge_id) {
validator_debug_log("VALIDATOR_DEBUG: STEP 10 FAILED - NIP-42 requires relay_url and challenge_id\n");
result->valid = 0;
result->error_code = NOSTR_ERROR_NIP42_NOT_CONFIGURED;
strcpy(result->reason, "NIP-42 authentication requires relay_url and challenge_id");
cJSON_Delete(event);
return NOSTR_SUCCESS;
}
int nip42_result = validate_nip42_event(event, request->relay_url, request->challenge_id);
if (nip42_result != NOSTR_SUCCESS) {
char nip42_fail_msg[256];
sprintf(nip42_fail_msg, "VALIDATOR_DEBUG: STEP 10 FAILED - NIP-42 validation failed (error=%d)\n", nip42_result);
validator_debug_log(nip42_fail_msg);
result->valid = 0;
result->error_code = nip42_result;
strcpy(result->reason, "NIP-42 authentication failed");
cJSON_Delete(event);
return NOSTR_SUCCESS;
}
validator_debug_log("VALIDATOR_DEBUG: STEP 10 PASSED - NIP-42 authentication succeeded\n");
strcpy(result->reason, "NIP-42 authentication passed");
} else if (event_kind == 24242) {
validator_debug_log("VALIDATOR_DEBUG: STEP 10 - Processing Blossom authentication (kind 24242)\n");
// Blossom protocol authentication (kind 24242)
if (request->operation && request->resource_hash) {
char blossom_valid_msg[512];
sprintf(blossom_valid_msg, "VALIDATOR_DEBUG: Validating Blossom event for operation='%s', hash='%s'\n",
request->operation ? request->operation : "NULL",
request->resource_hash ? request->resource_hash : "NULL");
validator_debug_log(blossom_valid_msg);
int blossom_result = validate_blossom_event(event, request->resource_hash, request->operation);
if (blossom_result != NOSTR_SUCCESS) {
char blossom_fail_msg[256];
sprintf(blossom_fail_msg, "VALIDATOR_DEBUG: STEP 10 FAILED - Blossom validation failed (error=%d)\n", blossom_result);
validator_debug_log(blossom_fail_msg);
result->valid = 0;
result->error_code = blossom_result;
strcpy(result->reason, "Blossom event does not authorize this operation");
cJSON_Delete(event);
return NOSTR_SUCCESS;
}
} else {
validator_debug_log("VALIDATOR_DEBUG: Skipping Blossom validation (no operation/hash specified)\n");
}
validator_debug_log("VALIDATOR_DEBUG: STEP 10 PASSED - Blossom authentication succeeded\n");
strcpy(result->reason, "Blossom authentication passed");
} else {
char unsupported_msg[256];
sprintf(unsupported_msg, "VALIDATOR_DEBUG: STEP 10 FAILED - Unsupported event kind: %d\n", event_kind);
validator_debug_log(unsupported_msg);
result->valid = 0;
result->error_code = NOSTR_ERROR_EVENT_INVALID_KIND;
strcpy(result->reason, "Unsupported event kind for authentication");
cJSON_Delete(event);
return NOSTR_SUCCESS;
}
// Copy validated pubkey to result
if (strlen(extracted_pubkey) == 64) {
strncpy(result->pubkey, extracted_pubkey, 64);
result->pubkey[64] = '\0';
validator_debug_log("VALIDATOR_DEBUG: STEP 11 PASSED - Pubkey copied to result\n");
} else {
char pubkey_warning_msg[256];
sprintf(pubkey_warning_msg, "VALIDATOR_DEBUG: STEP 11 WARNING - Invalid pubkey length: %zu\n", strlen(extracted_pubkey));
validator_debug_log(pubkey_warning_msg);
}
cJSON_Delete(event);
// STEP 12 PASSED: Protocol validation complete - continue to database rule evaluation
validator_debug_log("VALIDATOR_DEBUG: STEP 12 PASSED - Protocol validation complete, proceeding to rule evaluation\n");
// Check if auth rules are enabled
if (!g_auth_cache.auth_required) {
validator_debug_log("VALIDATOR_DEBUG: STEP 13 PASSED - Auth rules disabled, allowing request\n");
result->valid = 1;
result->error_code = NOSTR_SUCCESS;
strcpy(result->reason, "Authentication rules disabled");
return NOSTR_SUCCESS;
}
validator_debug_log("VALIDATOR_DEBUG: STEP 13 PASSED - Auth rules enabled, checking database rules\n");
// Check database rules for authorization
int rules_result = check_database_auth_rules(extracted_pubkey, request->operation, request->resource_hash);
if (rules_result != NOSTR_SUCCESS) {
validator_debug_log("VALIDATOR_DEBUG: STEP 14 FAILED - Database rules denied request\n");
result->valid = 0;
result->error_code = rules_result;
// Determine specific failure reason based on rules evaluation
if (rules_result == NOSTR_ERROR_AUTH_REQUIRED) {
// This can be pubkey blacklist or whitelist violation - set generic message
// The specific reason will be detailed in the database check function
strcpy(result->reason, "Request denied by authorization rules");
} else {
strcpy(result->reason, "Authorization error");
}
return NOSTR_SUCCESS;
}
validator_debug_log("VALIDATOR_DEBUG: STEP 14 PASSED - Database rules allow request\n");
// All validations passed
result->valid = 1;
result->error_code = NOSTR_SUCCESS;
validator_debug_log("VALIDATOR_DEBUG: STEP 15 PASSED - All validations complete, request ALLOWED\n");
return NOSTR_SUCCESS;
}
/**
* Generate NIP-42 challenge for clients
*/
int nostr_request_validator_generate_nip42_challenge(void* challenge_struct, const char* client_ip) {
// Mark client_ip as unused to suppress warning - it's for future enhancement
(void)client_ip;
// Use nostr_core_lib NIP-42 functionality
char challenge_id[65];
int result = nostr_nip42_generate_challenge(challenge_id, 32);
if (result != NOSTR_SUCCESS) {
return result;
}
// Fill challenge structure (assuming it's a compatible structure)
// This is a simplified implementation - adjust based on actual structure needs
if (challenge_struct) {
// Cast to appropriate structure and fill fields
// For now, just return success
}
return NOSTR_SUCCESS;
}
/**
* Get the last rule violation type for status code mapping
*/
const char* nostr_request_validator_get_last_violation_type(void) {
return g_last_rule_violation.violation_type;
}
/**
* Clear the last rule violation details
*/
void nostr_request_validator_clear_violation(void) {
memset(&g_last_rule_violation, 0, sizeof(g_last_rule_violation));
}
/**
* Cleanup request validator resources
*/
void nostr_request_validator_cleanup(void) {
g_validator_initialized = 0;
memset(&g_auth_cache, 0, sizeof(g_auth_cache));
nostr_request_validator_clear_violation();
}
//=============================================================================
// HELPER FUNCTIONS
//=============================================================================
/**
* Reload authentication configuration from database
*/
static int reload_auth_config(void) {
sqlite3* db = NULL;
sqlite3_stmt* stmt = NULL;
int rc;
// Clear cache
memset(&g_auth_cache, 0, sizeof(g_auth_cache));
// Open database
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
if (rc != SQLITE_OK) {
validator_debug_log("VALIDATOR: Could not open database\n");
// Use defaults
g_auth_cache.auth_required = 0;
g_auth_cache.max_file_size = 104857600; // 100MB
g_auth_cache.admin_enabled = 0;
g_auth_cache.nip42_mode = 1; // Optional
g_auth_cache.cache_expires = time(NULL) + 300; // 5 minutes
g_auth_cache.cache_valid = 1;
return NOSTR_SUCCESS;
}
// Load configuration values from server_config table
const char* server_sql = "SELECT key, value FROM server_config WHERE key IN ('require_auth', 'max_file_size', 'admin_enabled', 'admin_pubkey')";
rc = sqlite3_prepare_v2(db, server_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char* key = (const char*)sqlite3_column_text(stmt, 0);
const char* value = (const char*)sqlite3_column_text(stmt, 1);
if (!key || !value) continue;
if (strcmp(key, "require_auth") == 0) {
g_auth_cache.auth_required = (strcmp(value, "true") == 0) ? 1 : 0;
} else if (strcmp(key, "max_file_size") == 0) {
g_auth_cache.max_file_size = atol(value);
} else if (strcmp(key, "admin_enabled") == 0) {
g_auth_cache.admin_enabled = (strcmp(value, "true") == 0) ? 1 : 0;
} else if (strcmp(key, "admin_pubkey") == 0) {
strncpy(g_auth_cache.admin_pubkey, value, sizeof(g_auth_cache.admin_pubkey) - 1);
}
}
sqlite3_finalize(stmt);
}
// Load auth-specific configuration from auth_config table
const char* auth_sql = "SELECT key, value FROM auth_config WHERE key IN ('auth_rules_enabled', 'require_nip42_auth')";
rc = sqlite3_prepare_v2(db, auth_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char* key = (const char*)sqlite3_column_text(stmt, 0);
const char* value = (const char*)sqlite3_column_text(stmt, 1);
if (!key || !value) continue;
if (strcmp(key, "auth_rules_enabled") == 0) {
// Override auth_required with auth_rules_enabled if present
g_auth_cache.auth_required = (strcmp(value, "true") == 0) ? 1 : 0;
} else if (strcmp(key, "require_nip42_auth") == 0) {
if (strcmp(value, "false") == 0) {
g_auth_cache.nip42_mode = 0;
} else if (strcmp(value, "required") == 0) {
g_auth_cache.nip42_mode = 2;
} else {
g_auth_cache.nip42_mode = 1; // Optional
}
}
}
sqlite3_finalize(stmt);
}
sqlite3_close(db);
// Set cache expiration (5 minutes from now)
g_auth_cache.cache_expires = time(NULL) + 300;
g_auth_cache.cache_valid = 1;
// Set defaults for missing values
if (g_auth_cache.max_file_size == 0) {
g_auth_cache.max_file_size = 104857600; // 100MB
}
// Note: This is the final debug statement, no need to log it to our debug file as it's just informational
fprintf(stderr, "VALIDATOR: Configuration loaded - auth_required: %d, max_file_size: %ld, nip42_mode: %d\n",
g_auth_cache.auth_required, g_auth_cache.max_file_size, g_auth_cache.nip42_mode);
return NOSTR_SUCCESS;
}
/**
* Parse NOSTR authorization header (base64 decode)
*/
static int parse_authorization_header(const char* auth_header, char* event_json, size_t json_size) {
if (!auth_header || !event_json) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Check for "Nostr " prefix (case-insensitive)
const char* prefix = "nostr ";
size_t prefix_len = strlen(prefix);
if (strncasecmp(auth_header, prefix, prefix_len) != 0) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Extract base64 encoded event after "Nostr "
const char* base64_event = auth_header + prefix_len;
// Decode base64 to JSON using nostr_core_lib base64 decode
unsigned char decoded_buffer[4096];
size_t decoded_len = base64_decode(base64_event, decoded_buffer);
if (decoded_len == 0 || decoded_len >= json_size) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Copy decoded JSON to output buffer
memcpy(event_json, decoded_buffer, decoded_len);
event_json[decoded_len] = '\0';
return NOSTR_SUCCESS;
}
/**
* Extract pubkey from validated NOSTR event
*/
static int extract_pubkey_from_event(cJSON* event, char* pubkey_buffer, size_t buffer_size) {
if (!event || !pubkey_buffer || buffer_size < 65) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Initialize buffer to prevent corruption
memset(pubkey_buffer, 0, buffer_size);
cJSON* pubkey_json = cJSON_GetObjectItem(event, "pubkey");
if (!pubkey_json || !cJSON_IsString(pubkey_json)) {
return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
}
const char* pubkey = cJSON_GetStringValue(pubkey_json);
if (!pubkey) {
return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
}
// Check the raw pubkey string before validation
size_t pubkey_len = strlen(pubkey);
if (pubkey_len != 64) {
return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
}
// Validate that pubkey contains only hex characters before copying
for (int i = 0; i < 64; i++) {
char c = pubkey[i];
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
}
}
// Safe copy with explicit length and null termination
memcpy(pubkey_buffer, pubkey, 64);
pubkey_buffer[64] = '\0';
return NOSTR_SUCCESS;
}
/**
* Validate Blossom protocol event (kind 24242)
*/
static int validate_blossom_event(cJSON* event, const char* expected_hash, const char* method) {
if (!event) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Check event kind (must be 24242 for Blossom operations)
cJSON* kind_json = cJSON_GetObjectItem(event, "kind");
if (!kind_json || !cJSON_IsNumber(kind_json)) {
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
int kind = cJSON_GetNumberValue(kind_json);
if (kind != 24242) {
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
// Look for required tags if method and hash are specified
if (method || expected_hash) {
cJSON* tags = cJSON_GetObjectItem(event, "tags");
if (!tags || !cJSON_IsArray(tags)) {
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
int found_method = (method == NULL);
int found_hash = (expected_hash == NULL);
time_t expiration = 0;
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (!cJSON_IsArray(tag)) continue;
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
if (!tag_name || !cJSON_IsString(tag_name)) continue;
const char* tag_name_str = cJSON_GetStringValue(tag_name);
if (strcmp(tag_name_str, "t") == 0 && method) {
cJSON* method_value = cJSON_GetArrayItem(tag, 1);
if (method_value && cJSON_IsString(method_value)) {
const char* event_method = cJSON_GetStringValue(method_value);
if (strcmp(event_method, method) == 0) {
found_method = 1;
}
}
} else if (strcmp(tag_name_str, "x") == 0 && expected_hash) {
cJSON* hash_value = cJSON_GetArrayItem(tag, 1);
if (hash_value && cJSON_IsString(hash_value)) {
const char* event_hash = cJSON_GetStringValue(hash_value);
if (strcmp(event_hash, expected_hash) == 0) {
found_hash = 1;
}
}
} else if (strcmp(tag_name_str, "expiration") == 0) {
cJSON* exp_value = cJSON_GetArrayItem(tag, 1);
if (exp_value && cJSON_IsString(exp_value)) {
expiration = (time_t)atol(cJSON_GetStringValue(exp_value));
}
}
}
if (!found_method || !found_hash) {
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
// Check expiration
time_t now = time(NULL);
if (expiration > 0 && now > expiration) {
return NOSTR_ERROR_EVENT_EXPIRED;
}
}
return NOSTR_SUCCESS;
}
/**
* Check database authentication rules for the request
* Implements the 6-step rule evaluation engine from AUTH_API.md
*/
static int check_database_auth_rules(const char* pubkey, const char* operation, const char* resource_hash) {
sqlite3* db = NULL;
sqlite3_stmt* stmt = NULL;
int rc;
if (!pubkey) {
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - Missing pubkey for rule evaluation\n");
return NOSTR_ERROR_INVALID_INPUT;
}
char rules_msg[256];
sprintf(rules_msg, "VALIDATOR_DEBUG: RULES ENGINE - Checking rules for pubkey=%.32s..., operation=%s\n",
pubkey, operation ? operation : "NULL");
validator_debug_log(rules_msg);
// Open database
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
if (rc != SQLITE_OK) {
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - Failed to open database\n");
return NOSTR_SUCCESS; // Default allow on DB error
}
// Step 1: Check pubkey blacklist (highest priority)
const char* blacklist_sql = "SELECT rule_type, description FROM auth_rules WHERE rule_type = 'pubkey_blacklist' AND rule_target = ? AND operation = ? AND enabled = 1 ORDER BY priority LIMIT 1";
rc = sqlite3_prepare_v2(db, blacklist_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, operation ? operation : "", -1, SQLITE_STATIC);
if (sqlite3_step(stmt) == SQLITE_ROW) {
const char* description = (const char*)sqlite3_column_text(stmt, 1);
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 1 FAILED - Pubkey blacklisted\n");
char blacklist_msg[256];
sprintf(blacklist_msg, "VALIDATOR_DEBUG: RULES ENGINE - Blacklist rule matched: %s\n", description ? description : "Unknown");
validator_debug_log(blacklist_msg);
// Set specific violation details for status code mapping
strcpy(g_last_rule_violation.violation_type, "pubkey_blacklist");
sprintf(g_last_rule_violation.reason, "%s: Public key blacklisted", description ? description : "TEST_PUBKEY_BLACKLIST");
sqlite3_finalize(stmt);
sqlite3_close(db);
return NOSTR_ERROR_AUTH_REQUIRED;
}
sqlite3_finalize(stmt);
}
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 1 PASSED - Pubkey not blacklisted\n");
// Step 2: Check hash blacklist
if (resource_hash) {
const char* hash_blacklist_sql = "SELECT rule_type, description FROM auth_rules WHERE rule_type = 'hash_blacklist' AND rule_target = ? AND operation = ? AND enabled = 1 ORDER BY priority LIMIT 1";
rc = sqlite3_prepare_v2(db, hash_blacklist_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, resource_hash, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, operation ? operation : "", -1, SQLITE_STATIC);
if (sqlite3_step(stmt) == SQLITE_ROW) {
const char* description = (const char*)sqlite3_column_text(stmt, 1);
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 FAILED - Hash blacklisted\n");
char hash_blacklist_msg[256];
sprintf(hash_blacklist_msg, "VALIDATOR_DEBUG: RULES ENGINE - Hash blacklist rule matched: %s\n", description ? description : "Unknown");
validator_debug_log(hash_blacklist_msg);
// Set specific violation details for status code mapping
strcpy(g_last_rule_violation.violation_type, "hash_blacklist");
sprintf(g_last_rule_violation.reason, "%s: File hash blacklisted", description ? description : "TEST_HASH_BLACKLIST");
sqlite3_finalize(stmt);
sqlite3_close(db);
return NOSTR_ERROR_AUTH_REQUIRED;
}
sqlite3_finalize(stmt);
}
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 PASSED - Hash not blacklisted\n");
} else {
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 SKIPPED - No resource hash provided\n");
}
// Step 3: Check pubkey whitelist
const char* whitelist_sql = "SELECT rule_type, description FROM auth_rules WHERE rule_type = 'pubkey_whitelist' AND rule_target = ? AND operation = ? AND enabled = 1 ORDER BY priority LIMIT 1";
rc = sqlite3_prepare_v2(db, whitelist_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, operation ? operation : "", -1, SQLITE_STATIC);
if (sqlite3_step(stmt) == SQLITE_ROW) {
const char* description = (const char*)sqlite3_column_text(stmt, 1);
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 PASSED - Pubkey whitelisted\n");
char whitelist_msg[256];
sprintf(whitelist_msg, "VALIDATOR_DEBUG: RULES ENGINE - Whitelist rule matched: %s\n", description ? description : "Unknown");
validator_debug_log(whitelist_msg);
sqlite3_finalize(stmt);
sqlite3_close(db);
return NOSTR_SUCCESS; // Allow whitelisted pubkey
}
sqlite3_finalize(stmt);
}
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 FAILED - Pubkey not whitelisted\n");
// Step 4: Check if any whitelist rules exist - if yes, deny by default
const char* whitelist_exists_sql = "SELECT COUNT(*) FROM auth_rules WHERE rule_type = 'pubkey_whitelist' AND operation = ? AND enabled = 1 LIMIT 1";
rc = sqlite3_prepare_v2(db, whitelist_exists_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, operation ? operation : "", -1, SQLITE_STATIC);
if (sqlite3_step(stmt) == SQLITE_ROW) {
int whitelist_count = sqlite3_column_int(stmt, 0);
if (whitelist_count > 0) {
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 4 FAILED - Whitelist exists but pubkey not in it\n");
// Set specific violation details for status code mapping
strcpy(g_last_rule_violation.violation_type, "whitelist_violation");
strcpy(g_last_rule_violation.reason, "Public key not whitelisted for this operation");
sqlite3_finalize(stmt);
sqlite3_close(db);
return NOSTR_ERROR_AUTH_REQUIRED;
}
}
sqlite3_finalize(stmt);
}
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 4 PASSED - No whitelist restrictions apply\n");
sqlite3_close(db);
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 5 PASSED - All rule checks completed, default ALLOW\n");
return NOSTR_SUCCESS; // Default allow if no restrictive rules matched
}
/**
* Validate NIP-42 authentication event (kind 22242)
*/
static int validate_nip42_event(cJSON* event, const char* relay_url, const char* challenge_id) {
if (!event || !relay_url || !challenge_id) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Check event kind (must be 22242 for NIP-42)
cJSON* kind_json = cJSON_GetObjectItem(event, "kind");
if (!kind_json || !cJSON_IsNumber(kind_json)) {
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
int kind = cJSON_GetNumberValue(kind_json);
if (kind != NOSTR_NIP42_AUTH_EVENT_KIND) {
return NOSTR_ERROR_EVENT_INVALID_CONTENT;
}
// Use the existing NIP-42 verification from nostr_core_lib
int verification_result = nostr_nip42_verify_auth_event(event, challenge_id,
relay_url, NOSTR_NIP42_DEFAULT_TIME_TOLERANCE);
if (verification_result != NOSTR_SUCCESS) {
return verification_result;
}
return NOSTR_SUCCESS;
}

1
test_auth_disabled.txt Normal file
View File

@@ -0,0 +1 @@
test content

1
test_file.txt Normal file
View File

@@ -0,0 +1 @@
test content

1
test_simple.txt Normal file
View File

@@ -0,0 +1 @@
test_content_for_whitelist

1
test_whitelist_debug.txt Normal file
View File

@@ -0,0 +1 @@
test_whitelist_debug

View File

@@ -52,51 +52,38 @@ record_test_result() {
fi
}
echo "=== Ginxsom Authentication System Test Suite ==="
echo "Testing unified nostr_core_lib authentication integration"
echo "Timestamp: $(date -Iseconds)"
echo
# Check prerequisites
echo "[INFO] Checking prerequisites..."
for cmd in nak curl jq sqlite3; do
if ! command -v $cmd &> /dev/null; then
echo "[ERROR] $cmd command not found"
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 "[ERROR] Server not running at $SERVER_URL"
echo "[INFO] Start with: ./restart-all.sh"
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 "[ERROR] Database not found at $DB_PATH"
echo "Database not found at $DB_PATH"
exit 1
fi
echo "[SUCCESS] All prerequisites met"
echo
# Setup test environment and auth rules ONCE at the beginning
echo "=== Setting up authentication rules ==="
mkdir -p "$TEST_DIR"
# Enable authentication rules
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO auth_config (key, value) VALUES ('auth_rules_enabled', 'true');"
# Delete ALL existing auth rules and cache (clean slate)
echo "Deleting all existing auth rules..."
sqlite3 "$DB_PATH" "DELETE FROM auth_rules;"
sqlite3 "$DB_PATH" "DELETE FROM auth_cache;"
# Set up all test rules at once
echo "Creating test auth rules..."
# 1. Whitelist for TEST_USER1 for upload operations (priority 10)
sqlite3 "$DB_PATH" "INSERT INTO auth_rules (rule_type, rule_target, operation, priority, enabled, description)
VALUES ('pubkey_whitelist', '$TEST_USER1_PUBKEY', 'upload', 10, 1, 'TEST_WHITELIST_USER1');"
@@ -111,13 +98,8 @@ BLACKLISTED_HASH=$(sha256sum "$TEST_DIR/blacklisted_file.txt" | cut -d' ' -f1)
sqlite3 "$DB_PATH" "INSERT INTO auth_rules (rule_type, rule_target, operation, priority, enabled, description)
VALUES ('hash_blacklist', '$BLACKLISTED_HASH', 'upload', 5, 1, 'TEST_HASH_BLACKLIST');"
echo "Hash blacklisted: $BLACKLISTED_HASH"
# Display the rules we created
echo
echo "Auth rules created:"
sqlite3 "$DB_PATH" -header -column "SELECT rule_type, rule_target, operation, priority, enabled, description FROM auth_rules WHERE description LIKE 'TEST_%' ORDER BY priority;"
echo
# (Auth rules configured for testing)
# Helper functions
create_test_file() {
@@ -173,8 +155,6 @@ test_upload() {
}
# Run the tests
echo "=== Running Authentication Tests ==="
echo
# Test 1: Whitelisted user (should succeed)
test_file1=$(create_test_file "whitelisted_upload.txt" "Content from whitelisted user")
@@ -194,20 +174,15 @@ RANDOM_PRIVKEY="abcd1234567890abcd1234567890abcd1234567890abcd1234567890abcd1234
test_upload "Test 4: Random User (No Rules)" "$RANDOM_PRIVKEY" "$test_file4" "ANY"
# Test 5: Test with authentication disabled
echo "=== Test 5: Authentication Disabled ==="
echo "Disabling authentication rules..."
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO auth_config (key, value) VALUES ('auth_rules_enabled', 'false');"
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
echo "Re-enabling authentication rules..."
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO auth_config (key, value) VALUES ('auth_rules_enabled', 'true');"
echo
# Test failure modes - comprehensive edge case testing
echo "=== Test 6: Invalid Authorization Header Formats ==="
# Helper function for failure mode tests
test_failure_mode() {
@@ -242,7 +217,7 @@ test_failure_mode "Test 6b: Invalid Authorization Prefix" "Bearer invalidtoken12
# Test 6c: Invalid Base64 in Authorization
test_failure_mode "Test 6c: Invalid Base64 in Authorization" "Nostr invalid!@#base64"
echo "=== Test 7: Malformed JSON Events ==="
# Test malformed JSON events
# Test 7a: Invalid JSON Structure
malformed_json='{"kind":24242,"content":"","created_at":' # Incomplete JSON
@@ -254,10 +229,9 @@ 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"
echo "=== Test 8: Invalid Key Formats ==="
# Test invalid key formats
# Test 8a: Short Public Key
echo "Test 8a: Short Public Key (32 chars instead of 64)"
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

View File

@@ -0,0 +1 @@
Content from whitelisted user for test

View File

@@ -1 +1 @@
3fb6a0ea1d337bd09f1f88f65f124174ad7161dd5ea0fae74c0dd0b0db43a24e
1c4c3b202bbe84869d7e688fd4abccf9f46a57073df1c0e3b515d4810d9b6525

127
tests/debug_auth.sh Executable file
View File

@@ -0,0 +1,127 @@
#!/bin/bash
# debug_auth.sh - Simplified authentication test for Test 1: Whitelisted User Upload
# Isolates the first failing test case to debug the pubkey extraction issue
# Configuration
SERVER_URL="http://localhost:9001"
UPLOAD_ENDPOINT="${SERVER_URL}/upload"
DB_PATH="db/ginxsom.db"
TEST_DIR="tests/auth_test_tmp"
# Test keys (same as Test 1)
TEST_USER1_PRIVKEY="5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a"
TEST_USER1_PUBKEY="87d3561f19b74adbe8bf840682992466068830a9d8c36b4a0c99d36f826cb6cb"
echo "=== Debug Authentication Test ==="
echo "Testing: Whitelisted User Upload"
echo "Expected: HTTP 200 (Allowed)"
echo "Server: $SERVER_URL"
echo
# Check prerequisites
echo "Checking prerequisites..."
for cmd in nak curl jq sqlite3; do
if ! command -v $cmd &> /dev/null; then
echo "[ERROR] $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
echo "Prerequisites OK"
echo
# Setup test environment
echo "=== Setting up authentication rules ==="
mkdir -p "$TEST_DIR"
# Enable authentication rules
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO auth_config (key, value) VALUES ('auth_rules_enabled', 'true');"
# Clean slate
sqlite3 "$DB_PATH" "DELETE FROM auth_rules;"
sqlite3 "$DB_PATH" "DELETE FROM auth_cache;"
# Create the whitelist rule (same as Test 1)
echo "Creating whitelist rule for pubkey: $TEST_USER1_PUBKEY"
sqlite3 "$DB_PATH" "INSERT INTO auth_rules (rule_type, rule_target, operation, priority, enabled, description)
VALUES ('pubkey_whitelist', '$TEST_USER1_PUBKEY', 'upload', 10, 1, 'TEST_WHITELIST_USER1');"
# Verify rule creation
echo
echo "Current auth rules:"
sqlite3 "$DB_PATH" -header -column "SELECT rule_type, rule_target, operation, priority, enabled, description FROM auth_rules ORDER BY priority;"
# Helper function to create auth event (exactly like auth_test.sh)
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[@]}"
}
# Create test file
echo
echo "=== Running Test 1: Whitelisted User Upload ==="
test_file="$TEST_DIR/debug_whitelisted.txt"
echo "Content from whitelisted user for test" > "$test_file"
# Get file hash
file_hash=$(sha256sum "$test_file" | cut -d' ' -f1)
# Create auth event
event=$(create_auth_event "$TEST_USER1_PRIVKEY" "upload" "$file_hash")
# Base64 encode for Authorization header
auth_header="Nostr $(echo "$event" | base64 -w 0)"
# Make the upload request
response_file=$(mktemp)
http_status=$(curl -s -w "%{http_code}" \
-H "Authorization: $auth_header" \
-H "Content-Type: text/plain" \
--data-binary "@$test_file" \
-X PUT "$UPLOAD_ENDPOINT" \
-o "$response_file" 2>/dev/null)
echo "HTTP Status: $http_status"
if [[ "$http_status" == "200" ]]; then
echo "✅ PASSED - Upload allowed as expected"
else
echo "❌ FAILED - Expected 200, got $http_status"
fi
echo
echo "Clean up: rm -f \"$test_file\""
# Cleanup
rm -f "$response_file"
echo
echo "=== Debug Test Complete ==="
echo "1. Check ./restart-all.sh --follow for detailed logs"
echo "2. Verify pubkey extraction in logs/app/debug.log"
echo "3. Clean up: sqlite3 db/ginxsom.db \"DELETE FROM auth_rules WHERE description LIKE 'TEST_%';\""