Adding nip42 to request_validator

This commit is contained in:
Your Name 2025-09-07 14:12:18 -04:00
parent 9a63550863
commit 564ff18a7e
3 changed files with 522 additions and 13 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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