Adding nip42 to request_validator
This commit is contained in:
parent
9a63550863
commit
564ff18a7e
|
@ -61,6 +61,11 @@
|
|||
#define NOSTR_ERROR_NIP42_INVALID_MESSAGE_FORMAT -206
|
||||
#define NOSTR_ERROR_NIP42_CHALLENGE_TOO_SHORT -207
|
||||
#define NOSTR_ERROR_NIP42_CHALLENGE_TOO_LONG -208
|
||||
#define NOSTR_ERROR_NIP42_CHALLENGE_NOT_FOUND -209
|
||||
#define NOSTR_ERROR_NIP42_CHALLENGE_USED -210
|
||||
#define NOSTR_ERROR_NIP42_INVALID_RELAY_URL -211
|
||||
#define NOSTR_ERROR_NIP42_NOT_CONFIGURED -212
|
||||
#define NOSTR_ERROR_NIP42_BACKEND_NOT_AVAILABLE -213
|
||||
|
||||
// Constants
|
||||
#define NOSTR_PRIVATE_KEY_SIZE 32
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* authentication system originally built for ginxsom.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE // For strncasecmp
|
||||
#include "request_validator.h"
|
||||
#include "nip001.h"
|
||||
#include "utils.h"
|
||||
|
@ -51,6 +52,12 @@ static int sqlite_auth_rule_update(const nostr_auth_rule_t* rule);
|
|||
static int sqlite_auth_rule_list(const char* operation, nostr_auth_rule_t** rules, int* count);
|
||||
static int sqlite_auth_cache_clear(void);
|
||||
|
||||
// SQLite NIP-42 backend functions
|
||||
static int sqlite_nip42_challenge_store(const nostr_nip42_challenge_t* challenge);
|
||||
static int sqlite_nip42_challenge_get(const char* challenge_id, nostr_nip42_challenge_t* challenge);
|
||||
static int sqlite_nip42_challenge_use(const char* challenge_id);
|
||||
static int sqlite_nip42_challenge_cleanup_expired(void);
|
||||
|
||||
// Rule evaluation functions (extracted from ginxsom)
|
||||
static int check_pubkey_whitelist(const char* pubkey, const char* operation, nostr_request_result_t* result);
|
||||
static int check_pubkey_blacklist(const char* pubkey, const char* operation, nostr_request_result_t* result);
|
||||
|
@ -76,7 +83,11 @@ static nostr_auth_db_interface_t sqlite_backend = {
|
|||
.rule_remove = sqlite_auth_rule_remove,
|
||||
.rule_update = sqlite_auth_rule_update,
|
||||
.rule_list = sqlite_auth_rule_list,
|
||||
.cache_clear = sqlite_auth_cache_clear
|
||||
.cache_clear = sqlite_auth_cache_clear,
|
||||
.nip42_challenge_store = sqlite_nip42_challenge_store,
|
||||
.nip42_challenge_get = sqlite_nip42_challenge_get,
|
||||
.nip42_challenge_use = sqlite_nip42_challenge_use,
|
||||
.nip42_challenge_cleanup_expired = sqlite_nip42_challenge_cleanup_expired
|
||||
};
|
||||
|
||||
// Global database handle for SQLite backend
|
||||
|
@ -165,16 +176,51 @@ int nostr_validate_request(const nostr_request_t* request, nostr_request_result_
|
|||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
// Additional validation for operation-specific requirements
|
||||
if (request->operation && request->resource_hash) {
|
||||
int blossom_result = validate_nostr_event(event, request->resource_hash, request->operation);
|
||||
if (blossom_result != NOSTR_SUCCESS) {
|
||||
// Check event kind to determine authentication method
|
||||
struct cJSON* kind_json = cJSON_GetObjectItem(event, "kind");
|
||||
int event_kind = 0;
|
||||
if (kind_json && cJSON_IsNumber(kind_json)) {
|
||||
event_kind = cJSON_GetNumberValue(kind_json);
|
||||
}
|
||||
|
||||
if (event_kind == NOSTR_NIP42_AUTH_EVENT_KIND) {
|
||||
// NIP-42 authentication (kind 22242)
|
||||
if (request->relay_url && request->challenge_id) {
|
||||
int nip42_result = nostr_nip42_validate_auth_event(event, request->relay_url, request->challenge_id);
|
||||
if (nip42_result != NOSTR_SUCCESS) {
|
||||
result->valid = 0;
|
||||
result->error_code = nip42_result;
|
||||
strcpy(result->reason, "NIP-42 authentication failed");
|
||||
cJSON_Delete(event);
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
strcpy(result->reason, "NIP-42 authentication passed");
|
||||
} else {
|
||||
result->valid = 0;
|
||||
result->error_code = blossom_result;
|
||||
strcpy(result->reason, "Event does not authorize this operation");
|
||||
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;
|
||||
}
|
||||
} else if (event_kind == 24242) {
|
||||
// Blossom protocol authentication (kind 24242)
|
||||
if (request->operation && request->resource_hash) {
|
||||
int blossom_result = validate_nostr_event(event, request->resource_hash, request->operation);
|
||||
if (blossom_result != NOSTR_SUCCESS) {
|
||||
result->valid = 0;
|
||||
result->error_code = blossom_result;
|
||||
strcpy(result->reason, "Blossom event does not authorize this operation");
|
||||
cJSON_Delete(event);
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
}
|
||||
strcpy(result->reason, "Blossom authentication passed");
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
// Extract pubkey from validated event
|
||||
|
@ -241,8 +287,9 @@ void nostr_request_validator_cleanup(void) {
|
|||
// CONVENIENCE FUNCTIONS
|
||||
//=============================================================================
|
||||
|
||||
int nostr_auth_check_upload(const char* pubkey, const char* auth_header,
|
||||
int nostr_auth_check_upload(const char* pubkey, const char* auth_header,
|
||||
const char* hash, const char* mime_type, long file_size) {
|
||||
(void)pubkey; // Unused parameter - pubkey is extracted from auth_header
|
||||
nostr_request_t request = {
|
||||
.operation = "upload",
|
||||
.auth_header = auth_header,
|
||||
|
@ -265,6 +312,7 @@ int nostr_auth_check_upload(const char* pubkey, const char* auth_header,
|
|||
}
|
||||
|
||||
int nostr_auth_check_delete(const char* pubkey, const char* auth_header, const char* hash) {
|
||||
(void)pubkey; // Unused parameter - pubkey is extracted from auth_header
|
||||
nostr_request_t request = {
|
||||
.operation = "delete",
|
||||
.auth_header = auth_header,
|
||||
|
@ -287,6 +335,7 @@ int nostr_auth_check_delete(const char* pubkey, const char* auth_header, const c
|
|||
}
|
||||
|
||||
int nostr_auth_check_publish(const char* pubkey, struct cJSON* event) {
|
||||
(void)pubkey; // Unused parameter - pubkey is extracted from event
|
||||
if (!event) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
@ -459,6 +508,7 @@ static int validate_nostr_event(struct cJSON* event, const char* expected_hash,
|
|||
//=============================================================================
|
||||
|
||||
static int sqlite_auth_init(const char* db_path, const char* app_name) {
|
||||
(void)app_name; // Unused parameter - could be used for logging in future
|
||||
if (g_auth_db) {
|
||||
return NOSTR_SUCCESS; // Already initialized
|
||||
}
|
||||
|
@ -495,6 +545,15 @@ static int sqlite_auth_init(const char* db_path, const char* app_name) {
|
|||
"CREATE TABLE IF NOT EXISTS auth_config ("
|
||||
"key TEXT PRIMARY KEY, "
|
||||
"value TEXT"
|
||||
")",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS nip42_challenges ("
|
||||
"challenge_id TEXT PRIMARY KEY, "
|
||||
"relay_url TEXT NOT NULL, "
|
||||
"created_at INTEGER NOT NULL, "
|
||||
"expires_at INTEGER NOT NULL, "
|
||||
"used INTEGER DEFAULT 0, "
|
||||
"client_ip TEXT"
|
||||
")"
|
||||
};
|
||||
|
||||
|
@ -765,6 +824,7 @@ static int sqlite_auth_rule_update(const nostr_auth_rule_t* rule) {
|
|||
}
|
||||
|
||||
static int sqlite_auth_rule_list(const char* operation, nostr_auth_rule_t** rules, int* count) {
|
||||
(void)operation; // Unused parameter - would be used for filtering in full implementation
|
||||
if (!g_auth_db || !rules || !count) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
@ -1137,12 +1197,16 @@ static void generate_auth_cache_key(const char* pubkey, const char* operation, c
|
|||
// Generate SHA-256 hash of the key components for consistent cache keys
|
||||
unsigned char hash_bytes[32];
|
||||
if (nostr_sha256((unsigned char*)temp_buffer, strlen(temp_buffer), hash_bytes) == NOSTR_SUCCESS) {
|
||||
nostr_bytes_to_hex(hash_bytes, 32, cache_key);
|
||||
cache_key[64] = '\0'; // Ensure null termination
|
||||
if (key_size >= 65) { // Ensure we have enough space for 64 hex chars + null terminator
|
||||
nostr_bytes_to_hex(hash_bytes, 32, cache_key);
|
||||
cache_key[64] = '\0'; // Ensure null termination
|
||||
} else {
|
||||
// Fallback to truncated hash if buffer too small
|
||||
snprintf(cache_key, key_size, "%.*s", (int)(key_size - 1), temp_buffer);
|
||||
}
|
||||
} else {
|
||||
// Fallback if hashing fails
|
||||
strncpy(cache_key, temp_buffer, key_size - 1);
|
||||
cache_key[key_size - 1] = '\0';
|
||||
// Fallback if hashing fails - use safe string copy
|
||||
snprintf(cache_key, key_size, "%.*s", (int)(key_size - 1), temp_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1179,6 +1243,7 @@ int nostr_auth_rule_list(const char* operation, nostr_auth_rule_t** rules, int*
|
|||
}
|
||||
|
||||
void nostr_auth_rules_free(nostr_auth_rule_t* rules, int count) {
|
||||
(void)count; // Unused parameter - would be used for array cleanup in full implementation
|
||||
if (rules) {
|
||||
free(rules);
|
||||
}
|
||||
|
@ -1200,6 +1265,7 @@ int nostr_auth_cache_stats(int* hit_count, int* miss_count, int* entries) {
|
|||
}
|
||||
|
||||
int nostr_auth_register_db_backend(const nostr_auth_db_interface_t* backend) {
|
||||
(void)backend; // Unused parameter - would be used for backend registration in full implementation
|
||||
// For now, only SQLite backend is supported
|
||||
return NOSTR_ERROR_AUTH_RULES_BACKEND_NOT_FOUND;
|
||||
}
|
||||
|
@ -1211,3 +1277,329 @@ int nostr_auth_set_db_backend(const char* backend_name) {
|
|||
g_db_backend = &sqlite_backend;
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// NIP-42 AUTHENTICATION FUNCTIONS IMPLEMENTATION
|
||||
//=============================================================================
|
||||
|
||||
// Global NIP-42 configuration
|
||||
static struct {
|
||||
char relay_url[512];
|
||||
nostr_nip42_mode_t mode;
|
||||
int challenge_ttl;
|
||||
int initialized;
|
||||
} g_nip42_config = {0};
|
||||
|
||||
int nostr_nip42_configure(const char* relay_url, nostr_nip42_mode_t mode, int challenge_ttl) {
|
||||
if (!relay_url) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
strncpy(g_nip42_config.relay_url, relay_url, sizeof(g_nip42_config.relay_url) - 1);
|
||||
g_nip42_config.relay_url[sizeof(g_nip42_config.relay_url) - 1] = '\0';
|
||||
g_nip42_config.mode = mode;
|
||||
g_nip42_config.challenge_ttl = (challenge_ttl > 0) ? challenge_ttl : 600; // Default 10 minutes
|
||||
g_nip42_config.initialized = 1;
|
||||
|
||||
// Store configuration in database if available
|
||||
if (g_db_backend && g_db_backend->set_config) {
|
||||
char mode_str[16];
|
||||
char ttl_str[16];
|
||||
snprintf(mode_str, sizeof(mode_str), "%d", mode);
|
||||
snprintf(ttl_str, sizeof(ttl_str), "%d", g_nip42_config.challenge_ttl);
|
||||
|
||||
g_db_backend->set_config("nip42_relay_url", relay_url);
|
||||
g_db_backend->set_config("nip42_mode", mode_str);
|
||||
g_db_backend->set_config("nip42_challenge_ttl", ttl_str);
|
||||
}
|
||||
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
int nostr_request_validator_generate_nip42_challenge(nostr_nip42_challenge_t* challenge, const char* client_ip) {
|
||||
if (!challenge) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
// Generate challenge ID (64 hex characters)
|
||||
int result = nostr_nip42_generate_challenge(challenge->challenge_id, 32);
|
||||
if (result != NOSTR_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Set up challenge structure
|
||||
strncpy(challenge->relay_url, g_nip42_config.relay_url, sizeof(challenge->relay_url) - 1);
|
||||
challenge->relay_url[sizeof(challenge->relay_url) - 1] = '\0';
|
||||
|
||||
challenge->created_at = time(NULL);
|
||||
challenge->expires_at = challenge->created_at + g_nip42_config.challenge_ttl;
|
||||
challenge->used = 0;
|
||||
|
||||
if (client_ip) {
|
||||
strncpy(challenge->client_ip, client_ip, sizeof(challenge->client_ip) - 1);
|
||||
challenge->client_ip[sizeof(challenge->client_ip) - 1] = '\0';
|
||||
} else {
|
||||
challenge->client_ip[0] = '\0';
|
||||
}
|
||||
|
||||
// Store challenge in database if NIP-42 backend is available
|
||||
if (g_db_backend && g_db_backend->nip42_challenge_store) {
|
||||
g_db_backend->nip42_challenge_store(challenge);
|
||||
}
|
||||
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
int nostr_nip42_validate_auth_event(const struct 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)
|
||||
struct cJSON* kind_json = cJSON_GetObjectItem((struct cJSON*)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;
|
||||
}
|
||||
|
||||
// Retrieve and validate stored challenge
|
||||
nostr_nip42_challenge_t stored_challenge;
|
||||
if (g_db_backend && g_db_backend->nip42_challenge_get) {
|
||||
if (g_db_backend->nip42_challenge_get(challenge_id, &stored_challenge) != NOSTR_SUCCESS) {
|
||||
return NOSTR_ERROR_NIP42_CHALLENGE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// Check if challenge is still valid
|
||||
time_t now = time(NULL);
|
||||
if (stored_challenge.used) {
|
||||
return NOSTR_ERROR_NIP42_CHALLENGE_USED;
|
||||
}
|
||||
if (now > stored_challenge.expires_at) {
|
||||
return NOSTR_ERROR_NIP42_CHALLENGE_EXPIRED;
|
||||
}
|
||||
|
||||
// Verify relay URL matches
|
||||
if (strcmp(stored_challenge.relay_url, relay_url) != 0) {
|
||||
return NOSTR_ERROR_NIP42_INVALID_RELAY_URL;
|
||||
}
|
||||
}
|
||||
|
||||
// Use the existing NIP-42 verification from nip042.h
|
||||
int verification_result = nostr_nip42_verify_auth_event((struct cJSON*)event, challenge_id,
|
||||
relay_url, NOSTR_NIP42_DEFAULT_TIME_TOLERANCE);
|
||||
if (verification_result != NOSTR_SUCCESS) {
|
||||
return verification_result;
|
||||
}
|
||||
|
||||
// Mark challenge as used
|
||||
if (g_db_backend && g_db_backend->nip42_challenge_use) {
|
||||
g_db_backend->nip42_challenge_use(challenge_id);
|
||||
}
|
||||
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
int nostr_nip42_get_config(char* relay_url, nostr_nip42_mode_t* mode, int* challenge_ttl) {
|
||||
if (!g_nip42_config.initialized) {
|
||||
return NOSTR_ERROR_NIP42_NOT_CONFIGURED;
|
||||
}
|
||||
|
||||
if (relay_url) {
|
||||
strncpy(relay_url, g_nip42_config.relay_url, 512);
|
||||
relay_url[511] = '\0';
|
||||
}
|
||||
if (mode) {
|
||||
*mode = g_nip42_config.mode;
|
||||
}
|
||||
if (challenge_ttl) {
|
||||
*challenge_ttl = g_nip42_config.challenge_ttl;
|
||||
}
|
||||
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
int nostr_nip42_challenge_valid(const char* challenge_id, nostr_nip42_challenge_t* challenge) {
|
||||
if (!challenge_id) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!g_db_backend || !g_db_backend->nip42_challenge_get) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
nostr_nip42_challenge_t stored_challenge;
|
||||
if (g_db_backend->nip42_challenge_get(challenge_id, &stored_challenge) != NOSTR_SUCCESS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
time_t now = time(NULL);
|
||||
if (stored_challenge.used || now > stored_challenge.expires_at) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (challenge) {
|
||||
*challenge = stored_challenge;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int nostr_nip42_challenge_consume(const char* challenge_id) {
|
||||
if (!challenge_id) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
if (!g_db_backend || !g_db_backend->nip42_challenge_use) {
|
||||
return NOSTR_ERROR_NIP42_BACKEND_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
return g_db_backend->nip42_challenge_use(challenge_id);
|
||||
}
|
||||
|
||||
int nostr_nip42_cleanup_expired_challenges(void) {
|
||||
if (!g_db_backend || !g_db_backend->nip42_challenge_cleanup_expired) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return g_db_backend->nip42_challenge_cleanup_expired();
|
||||
}
|
||||
|
||||
int nostr_nip42_set_mode(nostr_nip42_mode_t mode) {
|
||||
g_nip42_config.mode = mode;
|
||||
|
||||
// Update database configuration
|
||||
if (g_db_backend && g_db_backend->set_config) {
|
||||
char mode_str[16];
|
||||
snprintf(mode_str, sizeof(mode_str), "%d", mode);
|
||||
g_db_backend->set_config("nip42_mode", mode_str);
|
||||
}
|
||||
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
nostr_nip42_mode_t nostr_nip42_get_mode(void) {
|
||||
return g_nip42_config.mode;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// SQLITE NIP-42 BACKEND IMPLEMENTATION
|
||||
//=============================================================================
|
||||
|
||||
static int sqlite_nip42_challenge_store(const nostr_nip42_challenge_t* challenge) {
|
||||
if (!g_auth_db || !challenge) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
const char* sql = "INSERT OR REPLACE INTO nip42_challenges "
|
||||
"(challenge_id, relay_url, created_at, expires_at, used, client_ip) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?)";
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_auth_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
return NOSTR_ERROR_AUTH_RULES_DB_FAILED;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, challenge->challenge_id, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, challenge->relay_url, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_int64(stmt, 3, challenge->created_at);
|
||||
sqlite3_bind_int64(stmt, 4, challenge->expires_at);
|
||||
sqlite3_bind_int(stmt, 5, challenge->used ? 1 : 0);
|
||||
sqlite3_bind_text(stmt, 6, challenge->client_ip, -1, SQLITE_STATIC);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return (rc == SQLITE_DONE) ? NOSTR_SUCCESS : NOSTR_ERROR_AUTH_RULES_DB_FAILED;
|
||||
}
|
||||
|
||||
static int sqlite_nip42_challenge_get(const char* challenge_id, nostr_nip42_challenge_t* challenge) {
|
||||
if (!g_auth_db || !challenge_id || !challenge) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
const char* sql = "SELECT challenge_id, relay_url, created_at, expires_at, used, client_ip "
|
||||
"FROM nip42_challenges WHERE challenge_id = ?";
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_auth_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
return NOSTR_ERROR_AUTH_RULES_DB_FAILED;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, challenge_id, -1, SQLITE_STATIC);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc == SQLITE_ROW) {
|
||||
const char* stored_id = (const char*)sqlite3_column_text(stmt, 0);
|
||||
const char* relay_url = (const char*)sqlite3_column_text(stmt, 1);
|
||||
const char* client_ip = (const char*)sqlite3_column_text(stmt, 5);
|
||||
|
||||
strncpy(challenge->challenge_id, stored_id ? stored_id : "", sizeof(challenge->challenge_id) - 1);
|
||||
challenge->challenge_id[sizeof(challenge->challenge_id) - 1] = '\0';
|
||||
|
||||
strncpy(challenge->relay_url, relay_url ? relay_url : "", sizeof(challenge->relay_url) - 1);
|
||||
challenge->relay_url[sizeof(challenge->relay_url) - 1] = '\0';
|
||||
|
||||
challenge->created_at = sqlite3_column_int64(stmt, 2);
|
||||
challenge->expires_at = sqlite3_column_int64(stmt, 3);
|
||||
challenge->used = sqlite3_column_int(stmt, 4);
|
||||
|
||||
if (client_ip) {
|
||||
strncpy(challenge->client_ip, client_ip, sizeof(challenge->client_ip) - 1);
|
||||
challenge->client_ip[sizeof(challenge->client_ip) - 1] = '\0';
|
||||
} else {
|
||||
challenge->client_ip[0] = '\0';
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
return NOSTR_ERROR_NIP42_CHALLENGE_NOT_FOUND;
|
||||
}
|
||||
|
||||
static int sqlite_nip42_challenge_use(const char* challenge_id) {
|
||||
if (!g_auth_db || !challenge_id) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
const char* sql = "UPDATE nip42_challenges SET used = 1 WHERE challenge_id = ?";
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_auth_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
return NOSTR_ERROR_AUTH_RULES_DB_FAILED;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt, 1, challenge_id, -1, SQLITE_STATIC);
|
||||
rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return (rc == SQLITE_DONE) ? NOSTR_SUCCESS : NOSTR_ERROR_AUTH_RULES_DB_FAILED;
|
||||
}
|
||||
|
||||
static int sqlite_nip42_challenge_cleanup_expired(void) {
|
||||
if (!g_auth_db) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
const char* sql = "DELETE FROM nip42_challenges WHERE expires_at < strftime('%s', 'now')";
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_auth_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
int changes = sqlite3_changes(g_auth_db);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return (rc == SQLITE_DONE) ? changes : -1;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#define NOSTR_REQUEST_VALIDATOR_H
|
||||
|
||||
#include "nostr_common.h"
|
||||
#include "nip042.h" // Include existing NIP-42 implementation
|
||||
#include <time.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
|
@ -35,6 +36,13 @@ typedef enum {
|
|||
NOSTR_AUTH_RULE_CUSTOM
|
||||
} nostr_auth_rule_type_t;
|
||||
|
||||
// NIP-42 authentication mode
|
||||
typedef enum {
|
||||
NIP42_MODE_DISABLED, // NIP-42 authentication disabled
|
||||
NIP42_MODE_OPTIONAL, // NIP-42 authentication optional (fallback to Blossom)
|
||||
NIP42_MODE_REQUIRED // NIP-42 authentication required
|
||||
} nostr_nip42_mode_t;
|
||||
|
||||
// Authentication request context
|
||||
typedef struct {
|
||||
const char* operation; // Operation type ("upload", "delete", "list", "publish")
|
||||
|
@ -49,6 +57,11 @@ typedef struct {
|
|||
// Client context
|
||||
const char* client_ip; // Client IP for rate limiting (optional)
|
||||
void* app_context; // Application-specific context (optional)
|
||||
|
||||
// NIP-42 specific context
|
||||
const char* relay_url; // Relay URL for NIP-42 validation (optional)
|
||||
const char* challenge_id; // Challenge ID for NIP-42 verification (optional)
|
||||
nostr_nip42_mode_t nip42_mode; // NIP-42 authentication mode for this request
|
||||
} nostr_request_t;
|
||||
|
||||
// Authentication result
|
||||
|
@ -76,6 +89,16 @@ typedef struct {
|
|||
time_t created_at; // Creation timestamp
|
||||
} nostr_auth_rule_t;
|
||||
|
||||
// NIP-42 challenge structure
|
||||
typedef struct {
|
||||
char challenge_id[65]; // Challenge ID (hex string)
|
||||
char relay_url[512]; // Relay URL that issued the challenge
|
||||
time_t created_at; // Challenge creation timestamp
|
||||
time_t expires_at; // Challenge expiration timestamp
|
||||
int used; // 1 if challenge has been used, 0 otherwise
|
||||
char client_ip[46]; // Client IP address (IPv4/IPv6)
|
||||
} nostr_nip42_challenge_t;
|
||||
|
||||
// Database backend interface (pluggable)
|
||||
typedef struct nostr_auth_db_interface {
|
||||
const char* name; // Backend name ("sqlite", "redis", etc.)
|
||||
|
@ -99,6 +122,12 @@ typedef struct nostr_auth_db_interface {
|
|||
int (*cache_get)(const char* cache_key, nostr_request_result_t* result);
|
||||
int (*cache_set)(const char* cache_key, const nostr_request_result_t* result, int ttl);
|
||||
int (*cache_clear)(void);
|
||||
|
||||
// NIP-42 challenge operations (optional - can be NULL if NIP-42 not supported)
|
||||
int (*nip42_challenge_store)(const nostr_nip42_challenge_t* challenge);
|
||||
int (*nip42_challenge_get)(const char* challenge_id, nostr_nip42_challenge_t* challenge);
|
||||
int (*nip42_challenge_use)(const char* challenge_id);
|
||||
int (*nip42_challenge_cleanup_expired)(void);
|
||||
} nostr_auth_db_interface_t;
|
||||
|
||||
//=============================================================================
|
||||
|
@ -267,6 +296,89 @@ int nostr_auth_cache_clear(void);
|
|||
*/
|
||||
int nostr_auth_cache_stats(int* hit_count, int* miss_count, int* entries);
|
||||
|
||||
//=============================================================================
|
||||
// NIP-42 AUTHENTICATION FUNCTIONS
|
||||
//=============================================================================
|
||||
|
||||
/**
|
||||
* Configure NIP-42 authentication settings
|
||||
*
|
||||
* @param relay_url The relay URL for NIP-42 authentication
|
||||
* @param mode NIP-42 authentication mode (disabled/optional/required)
|
||||
* @param challenge_ttl Challenge time-to-live in seconds (default: 600)
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_nip42_configure(const char* relay_url, nostr_nip42_mode_t mode, int challenge_ttl);
|
||||
|
||||
/**
|
||||
* Generate a new NIP-42 challenge for request validation
|
||||
* (Uses the underlying nostr_nip42_generate_challenge from nip042.h)
|
||||
*
|
||||
* @param challenge Pointer to receive the generated challenge structure
|
||||
* @param client_ip Client IP address for tracking (optional)
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_request_validator_generate_nip42_challenge(nostr_nip42_challenge_t* challenge, const char* client_ip);
|
||||
|
||||
/**
|
||||
* Validate a NIP-42 authentication event (kind 22242)
|
||||
*
|
||||
* @param event The authentication event to validate
|
||||
* @param relay_url Expected relay URL
|
||||
* @param challenge_id Expected challenge ID
|
||||
* @return NOSTR_SUCCESS if valid, error code if invalid
|
||||
*/
|
||||
int nostr_nip42_validate_auth_event(const struct cJSON* event, const char* relay_url, const char* challenge_id);
|
||||
|
||||
/**
|
||||
* Get NIP-42 configuration status
|
||||
*
|
||||
* @param relay_url Buffer to receive current relay URL (512 bytes)
|
||||
* @param mode Pointer to receive current NIP-42 mode
|
||||
* @param challenge_ttl Pointer to receive current challenge TTL
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_nip42_get_config(char* relay_url, nostr_nip42_mode_t* mode, int* challenge_ttl);
|
||||
|
||||
/**
|
||||
* Check if a challenge exists and is valid
|
||||
*
|
||||
* @param challenge_id Challenge ID to check
|
||||
* @param challenge Pointer to receive challenge details (optional)
|
||||
* @return 1 if valid, 0 if invalid/expired/used
|
||||
*/
|
||||
int nostr_nip42_challenge_valid(const char* challenge_id, nostr_nip42_challenge_t* challenge);
|
||||
|
||||
/**
|
||||
* Mark a challenge as used
|
||||
*
|
||||
* @param challenge_id Challenge ID to mark as used
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_nip42_challenge_consume(const char* challenge_id);
|
||||
|
||||
/**
|
||||
* Clean up expired challenges (maintenance function)
|
||||
*
|
||||
* @return Number of challenges cleaned up, or negative error code
|
||||
*/
|
||||
int nostr_nip42_cleanup_expired_challenges(void);
|
||||
|
||||
/**
|
||||
* Enable/disable NIP-42 authentication
|
||||
*
|
||||
* @param mode NIP-42 authentication mode
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_nip42_set_mode(nostr_nip42_mode_t mode);
|
||||
|
||||
/**
|
||||
* Get current NIP-42 mode
|
||||
*
|
||||
* @return Current NIP-42 mode
|
||||
*/
|
||||
nostr_nip42_mode_t nostr_nip42_get_mode(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue