v0.0.10 - Working on auth system

This commit is contained in:
Your Name
2025-09-09 10:42:59 -04:00
parent dd0d8a8b65
commit a3c8918491
23 changed files with 1284 additions and 113 deletions

View File

@@ -19,6 +19,7 @@ void handle_admin_api_request(const char* method, const char* uri);
void handle_stats_api(void);
void handle_config_get_api(void);
void handle_config_put_api(void);
void handle_config_key_put_api(const char* key);
void handle_files_api(void);
void handle_health_api(void);
int authenticate_admin_request(const char* auth_header);
@@ -51,7 +52,7 @@ static int admin_nip94_get_origin(char* out, size_t out_size) {
return 1;
}
const char* sql = "SELECT value FROM server_config WHERE key = 'cdn_origin'";
const char* sql = "SELECT value FROM config WHERE key = 'cdn_origin'";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
rc = sqlite3_step(stmt);
@@ -128,14 +129,15 @@ void handle_admin_api_request(const char* method, const char* uri) {
return;
}
// Authentication required for all admin operations except health check
if (strcmp(path, "/health") != 0) {
const char* auth_header = getenv("HTTP_AUTHORIZATION");
if (!authenticate_admin_request(auth_header)) {
send_json_error(401, "admin_auth_required", "Valid admin authentication required");
return;
}
}
// TODO: Re-enable authentication later
// Authentication temporarily disabled for testing
// if (strcmp(path, "/health") != 0) {
// const char* auth_header = getenv("HTTP_AUTHORIZATION");
// if (!authenticate_admin_request(auth_header)) {
// send_json_error(401, "admin_auth_required", "Valid admin authentication required");
// return;
// }
// }
// Route to appropriate handler
if (strcmp(method, "GET") == 0) {
@@ -153,6 +155,13 @@ void handle_admin_api_request(const char* method, const char* uri) {
} else if (strcmp(method, "PUT") == 0) {
if (strcmp(path, "/config") == 0) {
handle_config_put_api();
} else if (strncmp(path, "/config/", 8) == 0) {
const char* key = path + 8; // Skip "/config/"
if (strlen(key) > 0) {
handle_config_key_put_api(key);
} else {
send_json_error(400, "invalid_key", "Configuration key cannot be empty");
}
} else {
send_json_error(405, "method_not_allowed", "Method not allowed");
}
@@ -209,7 +218,7 @@ int verify_admin_pubkey(const char* event_pubkey) {
return 0;
}
const char* sql = "SELECT value FROM server_config WHERE key = 'admin_pubkey'";
const char* sql = "SELECT value FROM config WHERE key = 'admin_pubkey'";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
rc = sqlite3_step(stmt);
@@ -236,7 +245,7 @@ int is_admin_enabled(void) {
return 0; // Default disabled if can't access DB
}
const char* sql = "SELECT value FROM server_config WHERE key = 'admin_enabled'";
const char* sql = "SELECT value FROM config WHERE key = 'admin_enabled'";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
rc = sqlite3_step(stmt);
@@ -363,8 +372,8 @@ void handle_config_get_api(void) {
cJSON_AddStringToObject(response, "status", "success");
cJSON_AddItemToObject(response, "data", data);
// Query all server config settings
const char* sql = "SELECT key, value FROM server_config ORDER BY key";
// Query all config settings
const char* sql = "SELECT key, value FROM config ORDER BY key";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
@@ -438,7 +447,7 @@ void handle_config_put_api(void) {
cJSON* updated_keys = cJSON_CreateArray();
// Update each config value
const char* update_sql = "INSERT OR REPLACE INTO server_config (key, value) VALUES (?, ?)";
const char* update_sql = "INSERT OR REPLACE INTO config (key, value, updated_at) VALUES (?, ?, ?)";
cJSON* item = NULL;
cJSON_ArrayForEach(item, config_data) {
@@ -447,6 +456,7 @@ void handle_config_put_api(void) {
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, item->string, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, cJSON_GetStringValue(item), -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 3, time(NULL));
rc = sqlite3_step(stmt);
if (rc == SQLITE_DONE) {
@@ -471,6 +481,126 @@ void handle_config_put_api(void) {
send_json_response(200, response_str);
free(response_str);
cJSON_Delete(response);
// Force cache refresh after configuration update
nostr_request_validator_force_cache_refresh();
}
void handle_config_key_put_api(const char* key) {
if (!key || strlen(key) == 0) {
send_json_error(400, "invalid_key", "Configuration key cannot be empty");
return;
}
// Read request body
const char* content_length_str = getenv("CONTENT_LENGTH");
if (!content_length_str) {
send_json_error(411, "length_required", "Content-Length header required");
return;
}
long content_length = atol(content_length_str);
if (content_length <= 0 || content_length > 4096) {
send_json_error(400, "invalid_content_length", "Invalid content length");
return;
}
char* json_body = malloc(content_length + 1);
if (!json_body) {
send_json_error(500, "memory_error", "Failed to allocate memory");
return;
}
size_t bytes_read = fread(json_body, 1, content_length, stdin);
if (bytes_read != (size_t)content_length) {
free(json_body);
send_json_error(400, "incomplete_body", "Failed to read complete request body");
return;
}
json_body[content_length] = '\0';
// Parse JSON - expect {"value": "..."}
cJSON* request_data = cJSON_Parse(json_body);
if (!request_data) {
free(json_body);
send_json_error(400, "invalid_json", "Invalid JSON in request body");
return;
}
cJSON* value_item = cJSON_GetObjectItem(request_data, "value");
if (!cJSON_IsString(value_item)) {
free(json_body);
cJSON_Delete(request_data);
send_json_error(400, "missing_value", "Request must contain 'value' field");
return;
}
const char* value = cJSON_GetStringValue(value_item);
if (!value) {
free(json_body);
cJSON_Delete(request_data);
send_json_error(400, "invalid_value", "Value must be a string");
return;
}
// Make a safe copy of the value string BEFORE deleting cJSON object
char safe_value[256];
strncpy(safe_value, value, sizeof(safe_value) - 1);
safe_value[sizeof(safe_value) - 1] = '\0';
// Update database
sqlite3* db;
sqlite3_stmt* stmt;
int rc;
rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READWRITE, NULL);
if (rc) {
free(json_body);
cJSON_Delete(request_data);
send_json_error(500, "database_error", "Failed to open database");
return;
}
// Update or insert the config value
const char* update_sql = "INSERT OR REPLACE INTO config (key, value, updated_at) VALUES (?, ?, ?)";
rc = sqlite3_prepare_v2(db, update_sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
free(json_body);
cJSON_Delete(request_data);
sqlite3_close(db);
send_json_error(500, "database_error", "Failed to prepare update statement");
return;
}
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, safe_value, -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 3, time(NULL));
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
sqlite3_close(db);
free(json_body);
cJSON_Delete(request_data);
if (rc != SQLITE_DONE) {
send_json_error(500, "database_error", "Failed to update configuration");
return;
}
cJSON* response = cJSON_CreateObject();
cJSON_AddStringToObject(response, "status", "success");
cJSON_AddStringToObject(response, "message", "Configuration updated successfully");
cJSON_AddStringToObject(response, "key", key);
cJSON_AddStringToObject(response, "value", safe_value);
char* response_str = cJSON_PrintUnformatted(response);
send_json_response(200, response_str);
free(response_str);
cJSON_Delete(response);
// Force cache refresh after configuration update
nostr_request_validator_force_cache_refresh();
}
void handle_files_api(void) {

View File

@@ -102,6 +102,7 @@ int nostr_validate_request(const nostr_request_t* request, nostr_request_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);
void nostr_request_validator_force_cache_refresh(void);
int nostr_request_validator_generate_nip42_challenge(void* challenge_struct, const char* client_ip);
// Upload handling
@@ -225,6 +226,7 @@ void handle_admin_api_request(const char* method, const char* uri);
void handle_stats_api(void);
void handle_config_get_api(void);
void handle_config_put_api(void);
void handle_config_key_put_api(const char* key);
void handle_files_api(void);
void handle_health_api(void);

View File

@@ -90,7 +90,7 @@ int initialize_server_config(void) {
}
// Load admin_pubkey
const char* sql = "SELECT value FROM server_config WHERE key = ?";
const char* sql = "SELECT value FROM config WHERE key = ?";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, "admin_pubkey", -1, SQLITE_STATIC);
@@ -230,7 +230,7 @@ int apply_config_from_event(cJSON* event) {
const char* admin_pubkey = cJSON_GetStringValue(pubkey_json);
// Store admin pubkey in database
const char* insert_sql = "INSERT OR REPLACE INTO server_config (key, value, description) VALUES (?, ?, ?)";
const char* insert_sql = "INSERT OR REPLACE INTO config (key, value, description) VALUES (?, ?, ?)";
rc = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, "admin_pubkey", -1, SQLITE_STATIC);

View File

@@ -166,19 +166,20 @@ int nostr_validate_request(const nostr_request_t* request, nostr_request_result_
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
// Check if authentication is disabled first (regardless of header presence)
if (!g_auth_cache.auth_required) {
validator_debug_log("VALIDATOR_DEBUG: STEP 4 PASSED - Authentication disabled, allowing request\n");
strcpy(result->reason, "Authentication disabled");
return NOSTR_SUCCESS;
}
// If no auth header provided but auth is required, fail
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;
}
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);
@@ -345,14 +346,6 @@ int nostr_validate_request(const nostr_request_t* request, nostr_request_result_
// 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
@@ -432,7 +425,35 @@ void nostr_request_validator_cleanup(void) {
//=============================================================================
/**
* Reload authentication configuration from database
* Get cache timeout from environment variable or default
*/
static int get_cache_timeout(void) {
char* no_cache = getenv("GINX_NO_CACHE");
char* cache_timeout = getenv("GINX_CACHE_TIMEOUT");
if (no_cache && strcmp(no_cache, "1") == 0) {
return 0; // No caching
}
if (cache_timeout) {
int timeout = atoi(cache_timeout);
return (timeout >= 0) ? timeout : 300; // Use provided value or default
}
return 300; // Default 5 minutes
}
/**
* Force cache refresh - invalidates current cache
*/
void nostr_request_validator_force_cache_refresh(void) {
g_auth_cache.cache_valid = 0;
g_auth_cache.cache_expires = 0;
validator_debug_log("VALIDATOR: Cache forcibly invalidated\n");
}
/**
* Reload authentication configuration from unified config table
*/
static int reload_auth_config(void) {
sqlite3* db = NULL;
@@ -451,14 +472,15 @@ static int reload_auth_config(void) {
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
int cache_timeout = get_cache_timeout();
g_auth_cache.cache_expires = time(NULL) + cache_timeout;
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);
// Load configuration values from unified config table
const char* config_sql = "SELECT key, value FROM config WHERE key IN ('require_auth', 'auth_rules_enabled', 'max_file_size', 'admin_enabled', 'admin_pubkey', 'require_nip42_auth')";
rc = sqlite3_prepare_v2(db, config_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
@@ -469,31 +491,15 @@ static int reload_auth_config(void) {
if (strcmp(key, "require_auth") == 0) {
g_auth_cache.auth_required = (strcmp(value, "true") == 0) ? 1 : 0;
} else if (strcmp(key, "auth_rules_enabled") == 0) {
// Override auth_required with auth_rules_enabled if present (higher priority)
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;
@@ -509,8 +515,9 @@ static int reload_auth_config(void) {
sqlite3_close(db);
// Set cache expiration (5 minutes from now)
g_auth_cache.cache_expires = time(NULL) + 300;
// Set cache expiration with environment variable support
int cache_timeout = get_cache_timeout();
g_auth_cache.cache_expires = time(NULL) + cache_timeout;
g_auth_cache.cache_valid = 1;
// Set defaults for missing values
@@ -518,9 +525,9 @@ static int reload_auth_config(void) {
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);
// Debug logging
fprintf(stderr, "VALIDATOR: Configuration loaded from unified config table - auth_required: %d, max_file_size: %ld, nip42_mode: %d, cache_timeout: %d\n",
g_auth_cache.auth_required, g_auth_cache.max_file_size, g_auth_cache.nip42_mode, cache_timeout);
return NOSTR_SUCCESS;
}