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 # 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 ## 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 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. 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 TARGET = $(BUILDDIR)/ginxsom-fcgi
# Source files # 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) OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(BUILDDIR)/%.o)
# Default target # 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 # Comprehensive Blossom Protocol Implementation
# Main context - specify error log here to override system default # Main context - specify error log here to override system default
error_log logs/error.log debug; error_log logs/nginx/error.log debug;
pid logs/nginx.pid; pid logs/nginx/nginx.pid;
events { events {
worker_connections 1024; worker_connections 1024;
@@ -23,7 +23,7 @@ http {
default_type application/octet-stream; default_type application/octet-stream;
# Logging (relative to prefix directory) # Logging (relative to prefix directory)
access_log logs/access.log; access_log logs/nginx/access.log;
# FastCGI upstream configuration # FastCGI upstream configuration
upstream fastcgi_backend { 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 # Combines nginx and FastCGI restart operations for debugging
# Configuration # 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" FCGI_BINARY="./build/ginxsom-fcgi"
SOCKET_PATH="/tmp/ginxsom-fcgi.sock" SOCKET_PATH="/tmp/ginxsom-fcgi.sock"
PID_FILE="/tmp/ginxsom-fcgi.pid" PID_FILE="/tmp/ginxsom-fcgi.pid"
@@ -14,6 +30,13 @@ GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
NC='\033[0m' # No Color 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 -e "${YELLOW}=== Ginxsom Development Environment Restart ===${NC}"
echo "Starting full restart sequence..." echo "Starting full restart sequence..."
@@ -43,16 +66,28 @@ wait_for_stop() {
# Step 1: Stop nginx # Step 1: Stop nginx
echo -e "\n${YELLOW}1. Stopping nginx...${NC}" 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 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 nginx -p . -c "${NGINX_CONFIG}" -s stop 2>/dev/null
sleep 2 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 # Force kill any remaining nginx processes
NGINX_PIDS=$(pgrep -f "nginx.*${NGINX_CONFIG}") NGINX_PIDS=$(pgrep nginx)
if [ ! -z "$NGINX_PIDS" ]; then if [ ! -z "$NGINX_PIDS" ]; then
echo "Force killing remaining nginx processes: $NGINX_PIDS" 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 fi
echo -e "${GREEN}nginx stopped${NC}" echo -e "${GREEN}nginx stopped${NC}"
else else
@@ -103,30 +138,15 @@ fi
echo -e "${GREEN}FastCGI cleanup complete${NC}" echo -e "${GREEN}FastCGI cleanup complete${NC}"
# Step 3: Check if binary exists and is up to date # Step 3: Always rebuild FastCGI binary with clean build
echo -e "\n${YELLOW}3. Checking FastCGI binary...${NC}" echo -e "\n${YELLOW}3. Rebuilding FastCGI binary (clean build)...${NC}"
if [ ! -f "$FCGI_BINARY" ]; then echo "Performing clean rebuild to ensure all changes are compiled..."
echo -e "${RED}Error: FastCGI binary not found at $FCGI_BINARY${NC}" make clean && make
echo "Building application..." if [ $? -ne 0 ]; then
make
if [ $? -ne 0 ]; then
echo -e "${RED}Build failed! Cannot continue.${NC}" echo -e "${RED}Build failed! Cannot continue.${NC}"
exit 1 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 fi
echo -e "${GREEN}Clean rebuild complete${NC}"
# Step 4: Start FastCGI # Step 4: Start FastCGI
echo -e "\n${YELLOW}4. Starting FastCGI application...${NC}" echo -e "\n${YELLOW}4. Starting FastCGI application...${NC}"
@@ -141,10 +161,14 @@ if ! command -v spawn-fcgi &> /dev/null; then
exit 1 exit 1
fi fi
# Start FastCGI application with stderr logging (no wrapper needed) # Start FastCGI application with proper logging (daemonized but with redirected streams)
# Create stderr log with timestamp # Set debug environment variable for pubkey extraction diagnostics
echo "FastCGI starting at $(date)" > logs/fcgi-stderr.log echo "Setting GINX_DEBUG environment for pubkey extraction diagnostics"
spawn-fcgi -s "$SOCKET_PATH" -M 666 -u "$USER" -g "$USER" -f "$FCGI_BINARY" -P "$PID_FILE" 2>>logs/fcgi-stderr.log 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 if [ $? -eq 0 ] && [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE") PID=$(cat "$PID_FILE")

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,6 @@
#include <fcgi_stdio.h> #include <fcgi_stdio.h>
#include <sqlite3.h> #include <sqlite3.h>
#include "../nostr_core_lib/cjson/cJSON.h" #include "../nostr_core_lib/cjson/cJSON.h"
#include "../nostr_core_lib/nostr_core/nostr_core.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { 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 // Request validation system - implemented in request_validator.c
// Use nostr_validate_request() from request_validator.h for all authentication needs // 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 // Upload handling
void handle_upload_request(void); void handle_upload_request(void);

View File

@@ -16,7 +16,6 @@
#include <stdint.h> #include <stdint.h>
#include <curl/curl.h> #include <curl/curl.h>
#include "ginxsom.h" #include "ginxsom.h"
#include "../nostr_core_lib/nostr_core/request_validator.h"
// Debug macros removed // 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 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); 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 // NIP-42 function declarations
void handle_auth_challenge_request(void); void handle_auth_challenge_request(void);
int get_nip42_mode_config(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) { switch (status_code) {
case 400: status_text = "Bad Request"; break; case 400: status_text = "Bad Request"; break;
case 401: status_text = "Unauthorized"; break; case 401: status_text = "Unauthorized"; break;
case 403: status_text = "Forbidden"; break;
case 409: status_text = "Conflict"; break; case 409: status_text = "Conflict"; break;
case 413: status_text = "Payload Too Large"; break; case 413: status_text = "Payload Too Large"; break;
case 500: status_text = "Internal Server Error"; 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); int auth_result = nostr_validate_request(&request, &result);
if (auth_result != NOSTR_SUCCESS || !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* error_type = "authentication_failed";
const char* message = "Invalid or expired authentication"; 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"; 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 // Determine status code and error type based on violation type
if (strstr(result.reason, "whitelist")) { 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"; error_type = "pubkey_not_whitelisted";
message = "Public key not authorized"; 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")) { } else if (strstr(result.reason, "blacklist")) {
error_type = "access_denied"; error_type = "access_denied";
message = "Access denied by policy"; message = "Access denied by policy";
status_code = 403; // Access control policy denial
} }
send_error_response(401, error_type, message, details); send_error_response(status_code, error_type, message, details);
log_request("GET", "/list", "failed", 401); log_request("GET", "/list", "failed", status_code);
return; return;
} }
auth_status = "authenticated"; auth_status = "authenticated";
@@ -820,25 +843,52 @@ void handle_delete_request(const char* sha256) {
.app_context = NULL .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; nostr_request_result_t result;
int auth_result = nostr_validate_request(&request, &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) { 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* error_type = "authentication_failed";
const char* message = "Invalid or expired authentication"; 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"; 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 // Determine status code and error type based on violation type
if (strstr(result.reason, "whitelist")) { 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"; error_type = "pubkey_not_whitelisted";
message = "Public key not authorized"; 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")) { } else if (strstr(result.reason, "blacklist")) {
error_type = "access_denied"; error_type = "access_denied";
message = "Access denied by policy"; message = "Access denied by policy";
status_code = 403; // Access control policy denial
} }
send_error_response(401, error_type, message, details); send_error_response(status_code, error_type, message, details);
log_request("DELETE", "/delete", "failed", 401); log_request("DELETE", "/delete", "failed", status_code);
return; return;
} }
@@ -1088,7 +1138,14 @@ void handle_upload_request(void) {
return; 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"); fprintf(stderr, "AUTH: About to perform authentication validation\r\n");
// Create request structure for validation // Create request structure for validation
@@ -1104,58 +1161,107 @@ void handle_upload_request(void) {
}; };
nostr_request_result_t result; 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); 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 // Write debug output to a file for debugging
FILE* debug_file = fopen("debug_auth.log", "a"); FILE* debug_file = fopen("debug_auth.log", "a");
if (debug_file) { if (debug_file) {
fprintf(debug_file, "AUTH: nostr_validate_request returned: %d, valid: %d, reason: %s\n", fprintf(debug_file, "AUTH: nostr_validate_request returned: %d, valid: %d, reason: %s\n",
auth_result, result.valid, result.reason); 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); 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: 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: operation: '%s'\n", request.operation ? request.operation : "NULL");
fprintf(debug_file, "AUTH: auth_header present: %s\n", auth_header ? "YES" : "NO"); fprintf(debug_file, "AUTH: auth_header present: %s\n", auth_header ? "YES" : "NO");
fclose(debug_file); fclose(debug_file);
} }
// If auth header is provided, validate it regardless of require_auth setting // Authentication failed - reject the request
if (auth_header && (auth_result != NOSTR_SUCCESS || !result.valid)) { if (auth_result != NOSTR_SUCCESS || !result.valid) {
free(file_data); 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 // Use the detailed reason from the authentication system
const char* error_type = "authentication_failed"; const char* error_type = "authentication_failed";
const char* message = "Authentication failed"; const char* message = "Authentication failed";
const char* details = result.reason[0] ? result.reason : "The request failed authentication"; 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 // Determine status code and error type based on violation type
if (strstr(result.reason, "whitelist")) { 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"; error_type = "pubkey_not_whitelisted";
message = "Public key not authorized"; 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")) { } else if (strstr(result.reason, "blacklist")) {
error_type = "access_denied"; error_type = "access_denied";
message = "Access denied by policy"; message = "Access denied by policy";
status_code = 403; // Access control policy denial
} else if (strstr(result.reason, "expired")) { } else if (strstr(result.reason, "expired")) {
error_type = "event_expired"; error_type = "event_expired";
message = "Authentication event expired"; message = "Authentication event expired";
status_code = 401; // Authentication format/validity issue
} else if (strstr(result.reason, "signature")) { } else if (strstr(result.reason, "signature")) {
error_type = "invalid_signature"; error_type = "invalid_signature";
message = "Invalid cryptographic signature"; message = "Invalid cryptographic signature";
status_code = 401; // Authentication format/validity issue
} else if (strstr(result.reason, "size")) { } else if (strstr(result.reason, "size")) {
error_type = "file_too_large"; error_type = "file_too_large";
message = "File size exceeds policy limits"; message = "File size exceeds policy limits";
status_code = 403; // Access control policy denial
} else if (strstr(result.reason, "MIME") || strstr(result.reason, "mime")) { } else if (strstr(result.reason, "MIME") || strstr(result.reason, "mime")) {
error_type = "unsupported_type"; error_type = "unsupported_type";
message = "File type not allowed by policy"; message = "File type not allowed by policy";
status_code = 403; // Access control policy denial
} else if (strstr(result.reason, "hash")) { } else if (strstr(result.reason, "hash")) {
error_type = "hash_blocked"; error_type = "hash_blocked";
message = "File hash blocked by policy"; message = "File hash blocked by policy";
status_code = 403; // Access control policy denial
} else if (strstr(result.reason, "format") || strstr(result.reason, "invalid")) { } else if (strstr(result.reason, "format") || strstr(result.reason, "invalid")) {
error_type = "invalid_format"; error_type = "invalid_format";
message = "Invalid authorization format"; message = "Invalid authorization format";
status_code = 401; // Authentication format/validity issue
} }
send_error_response(401, error_type, message, details); send_error_response(status_code, error_type, message, details);
log_request("PUT", "/upload", "auth_failed", 401); log_request("PUT", "/upload", "auth_failed", status_code);
return; return;
} }
@@ -1169,6 +1275,7 @@ void handle_upload_request(void) {
process_file_upload:
// Get dimensions from in-memory buffer before saving file // Get dimensions from in-memory buffer before saving file
int width = 0, height = 0; int width = 0, height = 0;
nip94_get_dimensions(file_data, content_length, content_type, &width, &height); nip94_get_dimensions(file_data, content_length, content_type, &width, &height);
@@ -1391,8 +1498,6 @@ void handle_auth_challenge_request(void) {
int main(void) { int main(void) {
fprintf(stderr, "STARTUP: FastCGI application starting up\r\n");
fflush(stderr);
// Initialize server configuration and identity // Initialize server configuration and identity
// Try file-based config first, then fall back to database config // Try file-based config first, then fall back to database config
@@ -1431,7 +1536,9 @@ int main(void) {
// Initialize request validator system // Initialize request validator system
fprintf(stderr, "STARTUP: Initializing request validator system...\r\n"); 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"); fprintf(stderr, "FATAL ERROR: Failed to initialize request validator system\r\n");
return 1; 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 fi
} }
echo "=== Ginxsom Authentication System Test Suite ==="
echo "Testing unified nostr_core_lib authentication integration"
echo "Timestamp: $(date -Iseconds)"
echo
# Check prerequisites # Check prerequisites
echo "[INFO] Checking prerequisites..."
for cmd in nak curl jq sqlite3; do for cmd in nak curl jq sqlite3; do
if ! command -v $cmd &> /dev/null; then if ! command -v $cmd &> /dev/null; then
echo "[ERROR] $cmd command not found" echo "$cmd command not found"
exit 1 exit 1
fi fi
done done
# Check if server is running # Check if server is running
if ! curl -s -f "${SERVER_URL}/" > /dev/null 2>&1; then if ! curl -s -f "${SERVER_URL}/" > /dev/null 2>&1; then
echo "[ERROR] Server not running at $SERVER_URL" echo "Server not running at $SERVER_URL"
echo "[INFO] Start with: ./restart-all.sh" echo "Start with: ./restart-all.sh"
exit 1 exit 1
fi fi
# Check if database exists # Check if database exists
if [[ ! -f "$DB_PATH" ]]; then if [[ ! -f "$DB_PATH" ]]; then
echo "[ERROR] Database not found at $DB_PATH" echo "Database not found at $DB_PATH"
exit 1 exit 1
fi fi
echo "[SUCCESS] All prerequisites met"
echo
# Setup test environment and auth rules ONCE at the beginning # Setup test environment and auth rules ONCE at the beginning
echo "=== Setting up authentication rules ==="
mkdir -p "$TEST_DIR" mkdir -p "$TEST_DIR"
# Enable authentication rules # Enable authentication rules
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO auth_config (key, value) VALUES ('auth_rules_enabled', 'true');" 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) # 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_rules;"
sqlite3 "$DB_PATH" "DELETE FROM auth_cache;" sqlite3 "$DB_PATH" "DELETE FROM auth_cache;"
# Set up all test rules at once # Set up all test rules at once
echo "Creating test auth rules..."
# 1. Whitelist for TEST_USER1 for upload operations (priority 10) # 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) 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');" 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) 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');" VALUES ('hash_blacklist', '$BLACKLISTED_HASH', 'upload', 5, 1, 'TEST_HASH_BLACKLIST');"
echo "Hash blacklisted: $BLACKLISTED_HASH"
# Display the rules we created # Display the rules we created
echo # (Auth rules configured for testing)
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
# Helper functions # Helper functions
create_test_file() { create_test_file() {
@@ -173,8 +155,6 @@ test_upload() {
} }
# Run the tests # Run the tests
echo "=== Running Authentication Tests ==="
echo
# Test 1: Whitelisted user (should succeed) # Test 1: Whitelisted user (should succeed)
test_file1=$(create_test_file "whitelisted_upload.txt" "Content from whitelisted user") 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_upload "Test 4: Random User (No Rules)" "$RANDOM_PRIVKEY" "$test_file4" "ANY"
# Test 5: Test with authentication disabled # 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');" 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_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" test_upload "Test 5: Upload with Authentication Disabled" "$TEST_USER2_PRIVKEY" "$test_file5" "200"
# Re-enable authentication # Re-enable authentication
echo "Re-enabling authentication rules..."
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO auth_config (key, value) VALUES ('auth_rules_enabled', 'true');" sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO auth_config (key, value) VALUES ('auth_rules_enabled', 'true');"
echo
# Test failure modes - comprehensive edge case testing # Test failure modes - comprehensive edge case testing
echo "=== Test 6: Invalid Authorization Header Formats ==="
# Helper function for failure mode tests # Helper function for failure mode tests
test_failure_mode() { test_failure_mode() {
@@ -242,7 +217,7 @@ test_failure_mode "Test 6b: Invalid Authorization Prefix" "Bearer invalidtoken12
# Test 6c: Invalid Base64 in Authorization # Test 6c: Invalid Base64 in Authorization
test_failure_mode "Test 6c: Invalid Base64 in Authorization" "Nostr invalid!@#base64" 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 # Test 7a: Invalid JSON Structure
malformed_json='{"kind":24242,"content":"","created_at":' # Incomplete JSON 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) missing_fields_b64=$(echo -n "$missing_fields_json" | base64 -w 0)
test_failure_mode "Test 7b: Missing Required Fields (no pubkey)" "Nostr $missing_fields_b64" test_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 # 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" echo "short_key_test" > "$TEST_DIR/short_key.txt"
file_hash=$(sha256sum "$TEST_DIR/short_key.txt" | cut -d' ' -f1) file_hash=$(sha256sum "$TEST_DIR/short_key.txt" | cut -d' ' -f1)
short_pubkey="1234567890abcdef1234567890abcdef" # 32 chars instead of 64 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_%';\""