Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01836a4b4c | ||
|
|
9f3b3dd773 | ||
|
|
3210b9e752 | ||
|
|
2d66b8bf1d |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@ src/version.h
|
||||
dev-config/
|
||||
db/
|
||||
copy_executable_local.sh
|
||||
nostr_login_lite/
|
||||
32
07.md
Normal file
32
07.md
Normal file
@@ -0,0 +1,32 @@
|
||||
NIP-07
|
||||
======
|
||||
|
||||
`window.nostr` capability for web browsers
|
||||
------------------------------------------
|
||||
|
||||
`draft` `optional`
|
||||
|
||||
The `window.nostr` object may be made available by web browsers or extensions and websites or web-apps may make use of it after checking its availability.
|
||||
|
||||
That object must define the following methods:
|
||||
|
||||
```
|
||||
async window.nostr.getPublicKey(): string // returns a public key as hex
|
||||
async window.nostr.signEvent(event: { created_at: number, kind: number, tags: string[][], content: string }): Event // takes an event object, adds `id`, `pubkey` and `sig` and returns it
|
||||
```
|
||||
|
||||
Aside from these two basic above, the following functions can also be implemented optionally:
|
||||
```
|
||||
async window.nostr.nip04.encrypt(pubkey, plaintext): string // returns ciphertext and iv as specified in nip-04 (deprecated)
|
||||
async window.nostr.nip04.decrypt(pubkey, ciphertext): string // takes ciphertext and iv as specified in nip-04 (deprecated)
|
||||
async window.nostr.nip44.encrypt(pubkey, plaintext): string // returns ciphertext as specified in nip-44
|
||||
async window.nostr.nip44.decrypt(pubkey, ciphertext): string // takes ciphertext as specified in nip-44
|
||||
```
|
||||
|
||||
### Recommendation to Extension Authors
|
||||
To make sure that the `window.nostr` is available to nostr clients on page load, the authors who create Chromium and Firefox extensions should load their scripts by specifying `"run_at": "document_end"` in the extension's manifest.
|
||||
|
||||
|
||||
### Implementation
|
||||
|
||||
See https://github.com/aljazceru/awesome-nostr#nip-07-browser-extensions.
|
||||
59
40.md
59
40.md
@@ -1,59 +0,0 @@
|
||||
NIP-40
|
||||
======
|
||||
|
||||
Expiration Timestamp
|
||||
--------------------
|
||||
|
||||
`draft` `optional`
|
||||
|
||||
The `expiration` tag enables users to specify a unix timestamp at which the message SHOULD be considered expired (by relays and clients) and SHOULD be deleted by relays.
|
||||
|
||||
#### Spec
|
||||
|
||||
```
|
||||
tag: expiration
|
||||
values:
|
||||
- [UNIX timestamp in seconds]: required
|
||||
```
|
||||
|
||||
#### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"pubkey": "<pub-key>",
|
||||
"created_at": 1000000000,
|
||||
"kind": 1,
|
||||
"tags": [
|
||||
["expiration", "1600000000"]
|
||||
],
|
||||
"content": "This message will expire at the specified timestamp and be deleted by relays.\n",
|
||||
"id": "<event-id>"
|
||||
}
|
||||
```
|
||||
|
||||
Note: The timestamp should be in the same format as the created_at timestamp and should be interpreted as the time at which the message should be deleted by relays.
|
||||
|
||||
Client Behavior
|
||||
---------------
|
||||
|
||||
Clients SHOULD use the `supported_nips` field to learn if a relay supports this NIP. Clients SHOULD NOT send expiration events to relays that do not support this NIP.
|
||||
|
||||
Clients SHOULD ignore events that have expired.
|
||||
|
||||
Relay Behavior
|
||||
--------------
|
||||
|
||||
Relays MAY NOT delete expired messages immediately on expiration and MAY persist them indefinitely.
|
||||
Relays SHOULD NOT send expired events to clients, even if they are stored.
|
||||
Relays SHOULD drop any events that are published to them if they are expired.
|
||||
An expiration timestamp does not affect storage of ephemeral events.
|
||||
|
||||
Suggested Use Cases
|
||||
-------------------
|
||||
|
||||
* Temporary announcements - This tag can be used to make temporary announcements. For example, an event organizer could use this tag to post announcements about an upcoming event.
|
||||
* Limited-time offers - This tag can be used by businesses to make limited-time offers that expire after a certain amount of time. For example, a business could use this tag to make a special offer that is only available for a limited time.
|
||||
|
||||
#### Warning
|
||||
The events could be downloaded by third parties as they are publicly accessible all the time on the relays.
|
||||
So don't consider expiring messages as a security feature for your conversations or other uses.
|
||||
109
42.md
109
42.md
@@ -1,109 +0,0 @@
|
||||
NIP-42
|
||||
======
|
||||
|
||||
Authentication of clients to relays
|
||||
-----------------------------------
|
||||
|
||||
`draft` `optional`
|
||||
|
||||
This NIP defines a way for clients to authenticate to relays by signing an ephemeral event.
|
||||
|
||||
## Motivation
|
||||
|
||||
A relay may want to require clients to authenticate to access restricted resources. For example,
|
||||
|
||||
- A relay may request payment or other forms of whitelisting to publish events -- this can naïvely be achieved by limiting publication to events signed by the whitelisted key, but with this NIP they may choose to accept any events as long as they are published from an authenticated user;
|
||||
- A relay may limit access to `kind: 4` DMs to only the parties involved in the chat exchange, and for that it may require authentication before clients can query for that kind.
|
||||
- A relay may limit subscriptions of any kind to paying users or users whitelisted through any other means, and require authentication.
|
||||
|
||||
## Definitions
|
||||
|
||||
### New client-relay protocol messages
|
||||
|
||||
This NIP defines a new message, `AUTH`, which relays CAN send when they support authentication and clients can send to relays when they want to authenticate. When sent by relays the message has the following form:
|
||||
|
||||
```
|
||||
["AUTH", <challenge-string>]
|
||||
```
|
||||
|
||||
And, when sent by clients, the following form:
|
||||
|
||||
```
|
||||
["AUTH", <signed-event-json>]
|
||||
```
|
||||
|
||||
Clients MAY provide signed events from multiple pubkeys in a sequence of `AUTH` messages. Relays MUST treat all pubkeys as authenticated accordingly.
|
||||
|
||||
`AUTH` messages sent by clients MUST be answered with an `OK` message, like any `EVENT` message.
|
||||
|
||||
### Canonical authentication event
|
||||
|
||||
The signed event is an ephemeral event not meant to be published or queried, it must be of `kind: 22242` and it should have at least two tags, one for the relay URL and one for the challenge string as received from the relay. Relays MUST exclude `kind: 22242` events from being broadcasted to any client. `created_at` should be the current time. Example:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"kind": 22242,
|
||||
"tags": [
|
||||
["relay", "wss://relay.example.com/"],
|
||||
["challenge", "challengestringhere"]
|
||||
],
|
||||
// other fields...
|
||||
}
|
||||
```
|
||||
|
||||
### `OK` and `CLOSED` machine-readable prefixes
|
||||
|
||||
This NIP defines two new prefixes that can be used in `OK` (in response to event writes by clients) and `CLOSED` (in response to rejected subscriptions by clients):
|
||||
|
||||
- `"auth-required: "` - for when a client has not performed `AUTH` and the relay requires that to fulfill the query or write the event.
|
||||
- `"restricted: "` - for when a client has already performed `AUTH` but the key used to perform it is still not allowed by the relay or is exceeding its authorization.
|
||||
|
||||
## Protocol flow
|
||||
|
||||
At any moment the relay may send an `AUTH` message to the client containing a challenge. The challenge is valid for the duration of the connection or until another challenge is sent by the relay. The client MAY decide to send its `AUTH` event at any point and the authenticated session is valid afterwards for the duration of the connection.
|
||||
|
||||
### `auth-required` in response to a `REQ` message
|
||||
|
||||
Given that a relay is likely to require clients to perform authentication only for certain jobs, like answering a `REQ` or accepting an `EVENT` write, these are some expected common flows:
|
||||
|
||||
```
|
||||
relay: ["AUTH", "<challenge>"]
|
||||
client: ["REQ", "sub_1", {"kinds": [4]}]
|
||||
relay: ["CLOSED", "sub_1", "auth-required: we can't serve DMs to unauthenticated users"]
|
||||
client: ["AUTH", {"id": "abcdef...", ...}]
|
||||
client: ["AUTH", {"id": "abcde2...", ...}]
|
||||
relay: ["OK", "abcdef...", true, ""]
|
||||
relay: ["OK", "abcde2...", true, ""]
|
||||
client: ["REQ", "sub_1", {"kinds": [4]}]
|
||||
relay: ["EVENT", "sub_1", {...}]
|
||||
relay: ["EVENT", "sub_1", {...}]
|
||||
relay: ["EVENT", "sub_1", {...}]
|
||||
relay: ["EVENT", "sub_1", {...}]
|
||||
...
|
||||
```
|
||||
|
||||
In this case, the `AUTH` message from the relay could be sent right as the client connects or it can be sent immediately before the `CLOSED` is sent. The only requirement is that _the client must have a stored challenge associated with that relay_ so it can act upon that in response to the `auth-required` `CLOSED` message.
|
||||
|
||||
### `auth-required` in response to an `EVENT` message
|
||||
|
||||
The same flow is valid for when a client wants to write an `EVENT` to the relay, except now the relay sends back an `OK` message instead of a `CLOSED` message:
|
||||
|
||||
```
|
||||
relay: ["AUTH", "<challenge>"]
|
||||
client: ["EVENT", {"id": "012345...", ...}]
|
||||
relay: ["OK", "012345...", false, "auth-required: we only accept events from registered users"]
|
||||
client: ["AUTH", {"id": "abcdef...", ...}]
|
||||
relay: ["OK", "abcdef...", true, ""]
|
||||
client: ["EVENT", {"id": "012345...", ...}]
|
||||
relay: ["OK", "012345...", true, ""]
|
||||
```
|
||||
|
||||
## Signed Event Verification
|
||||
|
||||
To verify `AUTH` messages, relays must ensure:
|
||||
|
||||
- that the `kind` is `22242`;
|
||||
- that the event `created_at` is close (e.g. within ~10 minutes) of the current time;
|
||||
- that the `"challenge"` tag matches the challenge sent before;
|
||||
- that the `"relay"` tag matches the relay URL:
|
||||
- URL normalization techniques can be applied. For most cases just checking if the domain name is correct should be enough.
|
||||
2128
api/index.html
Normal file
2128
api/index.html
Normal file
File diff suppressed because it is too large
Load Diff
3190
api/nostr-lite.js
Normal file
3190
api/nostr-lite.js
Normal file
File diff suppressed because it is too large
Load Diff
11534
api/nostr.bundle.js
Normal file
11534
api/nostr.bundle.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,10 +9,61 @@ echo "=== C Nostr Relay Build and Restart Script ==="
|
||||
PRESERVE_DATABASE=false
|
||||
HELP=false
|
||||
USE_TEST_KEYS=false
|
||||
ADMIN_KEY=""
|
||||
RELAY_KEY=""
|
||||
PORT_OVERRIDE=""
|
||||
|
||||
# Key validation function
|
||||
validate_hex_key() {
|
||||
local key="$1"
|
||||
local key_type="$2"
|
||||
|
||||
if [ ${#key} -ne 64 ]; then
|
||||
echo "ERROR: $key_type key must be exactly 64 characters"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! [[ "$key" =~ ^[0-9a-fA-F]{64}$ ]]; then
|
||||
echo "ERROR: $key_type key must contain only hex characters (0-9, a-f, A-F)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--preserve-database|-p)
|
||||
-a|--admin-key)
|
||||
if [ -z "$2" ]; then
|
||||
echo "ERROR: Admin key option requires a value"
|
||||
HELP=true
|
||||
shift
|
||||
else
|
||||
ADMIN_KEY="$2"
|
||||
shift 2
|
||||
fi
|
||||
;;
|
||||
-r|--relay-key)
|
||||
if [ -z "$2" ]; then
|
||||
echo "ERROR: Relay key option requires a value"
|
||||
HELP=true
|
||||
shift
|
||||
else
|
||||
RELAY_KEY="$2"
|
||||
shift 2
|
||||
fi
|
||||
;;
|
||||
-p|--port)
|
||||
if [ -z "$2" ]; then
|
||||
echo "ERROR: Port option requires a value"
|
||||
HELP=true
|
||||
shift
|
||||
else
|
||||
PORT_OVERRIDE="$2"
|
||||
shift 2
|
||||
fi
|
||||
;;
|
||||
--preserve-database)
|
||||
PRESERVE_DATABASE=true
|
||||
shift
|
||||
;;
|
||||
@@ -32,14 +83,38 @@ while [[ $# -gt 0 ]]; do
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate custom keys if provided
|
||||
if [ -n "$ADMIN_KEY" ]; then
|
||||
if ! validate_hex_key "$ADMIN_KEY" "Admin"; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$RELAY_KEY" ]; then
|
||||
if ! validate_hex_key "$RELAY_KEY" "Relay"; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate port if provided
|
||||
if [ -n "$PORT_OVERRIDE" ]; then
|
||||
if ! [[ "$PORT_OVERRIDE" =~ ^[0-9]+$ ]] || [ "$PORT_OVERRIDE" -lt 1 ] || [ "$PORT_OVERRIDE" -gt 65535 ]; then
|
||||
echo "ERROR: Port must be a number between 1 and 65535"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Show help
|
||||
if [ "$HELP" = true ]; then
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --preserve-database, -p Keep existing database files (don't delete for fresh start)"
|
||||
echo " --test-keys, -t Use deterministic test keys for development (admin: all 'a's, relay: all '1's)"
|
||||
echo " --help, -h Show this help message"
|
||||
echo " -a, --admin-key <hex> 64-character hex admin private key"
|
||||
echo " -r, --relay-key <hex> 64-character hex relay private key"
|
||||
echo " -p, --port <port> Custom port override (default: 8888)"
|
||||
echo " --preserve-database Keep existing database files (don't delete for fresh start)"
|
||||
echo " --test-keys, -t Use deterministic test keys for development (admin: all 'a's, relay: all '1's)"
|
||||
echo " --help, -h Show this help message"
|
||||
echo ""
|
||||
echo "Event-Based Configuration:"
|
||||
echo " This relay now uses event-based configuration stored directly in the database."
|
||||
@@ -47,11 +122,14 @@ if [ "$HELP" = true ]; then
|
||||
echo " Database file: <relay_pubkey>.db (created automatically)"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 # Fresh start with new keys (default)"
|
||||
echo " $0 -p # Preserve existing database and keys"
|
||||
echo " $0 -t # Use test keys for consistent development"
|
||||
echo " $0 -t -p # Use test keys and preserve database"
|
||||
echo " $0 # Fresh start with random keys"
|
||||
echo " $0 -a <admin-hex> -r <relay-hex> # Use custom keys"
|
||||
echo " $0 -a <admin-hex> -p 9000 # Custom admin key on port 9000"
|
||||
echo " $0 --preserve-database # Preserve existing database and keys"
|
||||
echo " $0 --test-keys # Use test keys for consistent development"
|
||||
echo " $0 -t --preserve-database # Use test keys and preserve database"
|
||||
echo ""
|
||||
echo "Key Format: Keys must be exactly 64 hexadecimal characters (0-9, a-f, A-F)"
|
||||
echo "Default behavior: Deletes existing database files to start fresh with new keys"
|
||||
echo " for development purposes"
|
||||
exit 0
|
||||
@@ -152,14 +230,36 @@ echo "Database will be initialized automatically on startup if needed"
|
||||
echo "Starting relay server..."
|
||||
echo "Debug: Current processes: $(ps aux | grep 'c_relay_' | grep -v grep || echo 'None')"
|
||||
|
||||
# Build command line arguments for relay binary
|
||||
RELAY_ARGS=""
|
||||
|
||||
if [ -n "$ADMIN_KEY" ]; then
|
||||
RELAY_ARGS="$RELAY_ARGS -a $ADMIN_KEY"
|
||||
echo "Using custom admin key: ${ADMIN_KEY:0:16}..."
|
||||
fi
|
||||
|
||||
if [ -n "$RELAY_KEY" ]; then
|
||||
RELAY_ARGS="$RELAY_ARGS -r $RELAY_KEY"
|
||||
echo "Using custom relay key: ${RELAY_KEY:0:16}..."
|
||||
fi
|
||||
|
||||
if [ -n "$PORT_OVERRIDE" ]; then
|
||||
RELAY_ARGS="$RELAY_ARGS -p $PORT_OVERRIDE"
|
||||
echo "Using custom port: $PORT_OVERRIDE"
|
||||
fi
|
||||
|
||||
# Change to build directory before starting relay so database files are created there
|
||||
cd build
|
||||
# Start relay in background and capture its PID
|
||||
if [ "$USE_TEST_KEYS" = true ]; then
|
||||
echo "Using deterministic test keys for development..."
|
||||
./$(basename $BINARY_PATH) -a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -r 1111111111111111111111111111111111111111111111111111111111111111 > ../relay.log 2>&1 &
|
||||
elif [ -n "$RELAY_ARGS" ]; then
|
||||
echo "Starting relay with custom configuration..."
|
||||
./$(basename $BINARY_PATH) $RELAY_ARGS > ../relay.log 2>&1 &
|
||||
else
|
||||
# No command line arguments needed for random key generation
|
||||
echo "Starting relay with random key generation..."
|
||||
./$(basename $BINARY_PATH) > ../relay.log 2>&1 &
|
||||
fi
|
||||
RELAY_PID=$!
|
||||
|
||||
1399
src/config.c
1399
src/config.c
File diff suppressed because it is too large
Load Diff
123
src/config.h
123
src/config.h
@@ -4,6 +4,7 @@
|
||||
#include <sqlite3.h>
|
||||
#include <cjson/cJSON.h>
|
||||
#include <time.h>
|
||||
#include <pthread.h>
|
||||
|
||||
// Configuration constants
|
||||
#define CONFIG_VALUE_MAX_LENGTH 1024
|
||||
@@ -23,14 +24,71 @@
|
||||
// Database path for event-based config
|
||||
extern char g_database_path[512];
|
||||
|
||||
// Configuration manager structure
|
||||
// Unified configuration cache structure (consolidates all caching systems)
|
||||
typedef struct {
|
||||
sqlite3* db;
|
||||
char relay_pubkey[65];
|
||||
// Critical keys (frequently accessed)
|
||||
char admin_pubkey[65];
|
||||
time_t last_config_check;
|
||||
char config_file_path[512]; // Temporary for compatibility
|
||||
} config_manager_t;
|
||||
char relay_pubkey[65];
|
||||
|
||||
// Auth config (from request_validator)
|
||||
int auth_required;
|
||||
long max_file_size;
|
||||
int admin_enabled;
|
||||
int nip42_mode;
|
||||
int nip42_challenge_timeout;
|
||||
int nip42_time_tolerance;
|
||||
|
||||
// Static buffer for config values (replaces static buffers in get_config_value functions)
|
||||
char temp_buffer[CONFIG_VALUE_MAX_LENGTH];
|
||||
|
||||
// NIP-11 relay information (migrated from g_relay_info in main.c)
|
||||
struct {
|
||||
char name[RELAY_NAME_MAX_LENGTH];
|
||||
char description[RELAY_DESCRIPTION_MAX_LENGTH];
|
||||
char banner[RELAY_URL_MAX_LENGTH];
|
||||
char icon[RELAY_URL_MAX_LENGTH];
|
||||
char pubkey[RELAY_PUBKEY_MAX_LENGTH];
|
||||
char contact[RELAY_CONTACT_MAX_LENGTH];
|
||||
char software[RELAY_URL_MAX_LENGTH];
|
||||
char version[64];
|
||||
char privacy_policy[RELAY_URL_MAX_LENGTH];
|
||||
char terms_of_service[RELAY_URL_MAX_LENGTH];
|
||||
cJSON* supported_nips;
|
||||
cJSON* limitation;
|
||||
cJSON* retention;
|
||||
cJSON* relay_countries;
|
||||
cJSON* language_tags;
|
||||
cJSON* tags;
|
||||
char posting_policy[RELAY_URL_MAX_LENGTH];
|
||||
cJSON* fees;
|
||||
char payments_url[RELAY_URL_MAX_LENGTH];
|
||||
} relay_info;
|
||||
|
||||
// NIP-13 PoW configuration (migrated from g_pow_config in main.c)
|
||||
struct {
|
||||
int enabled;
|
||||
int min_pow_difficulty;
|
||||
int validation_flags;
|
||||
int require_nonce_tag;
|
||||
int reject_lower_targets;
|
||||
int strict_format;
|
||||
int anti_spam_mode;
|
||||
} pow_config;
|
||||
|
||||
// NIP-40 Expiration configuration (migrated from g_expiration_config in main.c)
|
||||
struct {
|
||||
int enabled;
|
||||
int strict_mode;
|
||||
int filter_responses;
|
||||
int delete_expired;
|
||||
long grace_period;
|
||||
} expiration_config;
|
||||
|
||||
// Cache management
|
||||
time_t cache_expires;
|
||||
int cache_valid;
|
||||
pthread_mutex_t cache_lock;
|
||||
} unified_config_cache_t;
|
||||
|
||||
// Command line options structure for first-time startup
|
||||
typedef struct {
|
||||
@@ -39,8 +97,8 @@ typedef struct {
|
||||
char relay_privkey_override[65]; // Empty string = not set, 64-char hex = override
|
||||
} cli_options_t;
|
||||
|
||||
// Global configuration manager
|
||||
extern config_manager_t g_config_manager;
|
||||
// Global unified configuration cache
|
||||
extern unified_config_cache_t g_unified_cache;
|
||||
|
||||
// Core configuration functions (temporary compatibility)
|
||||
int init_configuration_system(const char* config_dir_override, const char* config_file_override);
|
||||
@@ -90,4 +148,53 @@ int parse_auth_required_kinds(const char* kinds_str, int* kinds_array, int max_k
|
||||
int is_nip42_auth_required_for_kind(int event_kind);
|
||||
int is_nip42_auth_globally_required(void);
|
||||
|
||||
// ================================
|
||||
// NEW ADMIN API FUNCTIONS
|
||||
// ================================
|
||||
|
||||
// Config table management functions (config table created via embedded schema)
|
||||
const char* get_config_value_from_table(const char* key);
|
||||
int set_config_value_in_table(const char* key, const char* value, const char* data_type,
|
||||
const char* description, const char* category, int requires_restart);
|
||||
int update_config_in_table(const char* key, const char* value);
|
||||
int populate_default_config_values(void);
|
||||
int add_pubkeys_to_config_table(void);
|
||||
|
||||
// Admin event processing functions
|
||||
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size);
|
||||
int process_admin_config_event(cJSON* event, char* error_message, size_t error_size);
|
||||
int process_admin_auth_event(cJSON* event, char* error_message, size_t error_size);
|
||||
|
||||
// Auth rules management functions
|
||||
int add_auth_rule_from_config(const char* rule_type, const char* pattern_type,
|
||||
const char* pattern_value, const char* action);
|
||||
int remove_auth_rule_from_config(const char* rule_type, const char* pattern_type,
|
||||
const char* pattern_value);
|
||||
|
||||
// Unified configuration cache management
|
||||
void force_config_cache_refresh(void);
|
||||
const char* get_admin_pubkey_cached(void);
|
||||
const char* get_relay_pubkey_cached(void);
|
||||
void invalidate_config_cache(void);
|
||||
int reload_config_from_table(void);
|
||||
|
||||
// Hybrid config access functions
|
||||
const char* get_config_value_hybrid(const char* key);
|
||||
int is_config_table_ready(void);
|
||||
|
||||
// Migration support functions
|
||||
int initialize_config_system_with_migration(void);
|
||||
int migrate_config_from_events_to_table(void);
|
||||
int populate_config_table_from_event(const cJSON* event);
|
||||
|
||||
// Startup configuration processing functions
|
||||
int process_startup_config_event(const cJSON* event);
|
||||
int process_startup_config_event_with_fallback(const cJSON* event);
|
||||
|
||||
// Dynamic event generation functions for WebSocket configuration fetching
|
||||
cJSON* generate_config_event_from_table(void);
|
||||
int req_filter_requests_config_events(const cJSON* filter);
|
||||
cJSON* generate_synthetic_config_event_for_subscription(const char* sub_id, const cJSON* filters);
|
||||
char* generate_config_event_json(void);
|
||||
|
||||
#endif /* CONFIG_H */
|
||||
558
src/main.c
558
src/main.c
@@ -68,8 +68,8 @@ struct relay_info {
|
||||
char payments_url[RELAY_URL_MAX_LENGTH];
|
||||
};
|
||||
|
||||
// Global relay information instance
|
||||
static struct relay_info g_relay_info = {0};
|
||||
// Global relay information instance moved to unified cache
|
||||
// static struct relay_info g_relay_info = {0}; // REMOVED - now in g_unified_cache.relay_info
|
||||
|
||||
// NIP-13 PoW configuration structure
|
||||
struct pow_config {
|
||||
@@ -227,6 +227,9 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length);
|
||||
// Forward declaration for configuration event handling (kind 33334)
|
||||
int handle_configuration_event(cJSON* event, char* error_message, size_t error_size);
|
||||
|
||||
// Forward declaration for admin event processing (kinds 33334 and 33335)
|
||||
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size);
|
||||
|
||||
// Forward declaration for NOTICE message support
|
||||
void send_notice_message(struct lws* wsi, const char* message);
|
||||
|
||||
@@ -695,7 +698,12 @@ int broadcast_event_to_subscriptions(cJSON* event) {
|
||||
}
|
||||
|
||||
// Check if event is expired and should not be broadcast (NIP-40)
|
||||
if (g_expiration_config.enabled && g_expiration_config.filter_responses) {
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
int expiration_enabled = g_unified_cache.expiration_config.enabled;
|
||||
int filter_responses = g_unified_cache.expiration_config.filter_responses;
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
if (expiration_enabled && filter_responses) {
|
||||
time_t current_time = time(NULL);
|
||||
if (is_event_expired(event, current_time)) {
|
||||
char debug_msg[256];
|
||||
@@ -1477,131 +1485,147 @@ int mark_event_as_deleted(const char* event_id, const char* deletion_event_id, c
|
||||
|
||||
// Initialize relay information using configuration system
|
||||
void init_relay_info() {
|
||||
// Load relay information from configuration system
|
||||
// Get all config values first (without holding mutex to avoid deadlock)
|
||||
const char* relay_name = get_config_value("relay_name");
|
||||
if (relay_name) {
|
||||
strncpy(g_relay_info.name, relay_name, sizeof(g_relay_info.name) - 1);
|
||||
} else {
|
||||
strncpy(g_relay_info.name, "C Nostr Relay", sizeof(g_relay_info.name) - 1);
|
||||
}
|
||||
|
||||
const char* relay_description = get_config_value("relay_description");
|
||||
if (relay_description) {
|
||||
strncpy(g_relay_info.description, relay_description, sizeof(g_relay_info.description) - 1);
|
||||
} else {
|
||||
strncpy(g_relay_info.description, "A high-performance Nostr relay implemented in C with SQLite storage", sizeof(g_relay_info.description) - 1);
|
||||
}
|
||||
|
||||
const char* relay_software = get_config_value("relay_software");
|
||||
if (relay_software) {
|
||||
strncpy(g_relay_info.software, relay_software, sizeof(g_relay_info.software) - 1);
|
||||
} else {
|
||||
strncpy(g_relay_info.software, "https://git.laantungir.net/laantungir/c-relay.git", sizeof(g_relay_info.software) - 1);
|
||||
}
|
||||
|
||||
const char* relay_version = get_config_value("relay_version");
|
||||
if (relay_version) {
|
||||
strncpy(g_relay_info.version, relay_version, sizeof(g_relay_info.version) - 1);
|
||||
} else {
|
||||
strncpy(g_relay_info.version, "0.2.0", sizeof(g_relay_info.version) - 1);
|
||||
}
|
||||
|
||||
// Load optional fields
|
||||
const char* relay_contact = get_config_value("relay_contact");
|
||||
if (relay_contact) {
|
||||
strncpy(g_relay_info.contact, relay_contact, sizeof(g_relay_info.contact) - 1);
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
|
||||
// Get config values for limitations
|
||||
int max_message_length = get_config_int("max_message_length", 16384);
|
||||
int max_subscriptions_per_client = get_config_int("max_subscriptions_per_client", 20);
|
||||
int max_limit = get_config_int("max_limit", 5000);
|
||||
int max_event_tags = get_config_int("max_event_tags", 100);
|
||||
int max_content_length = get_config_int("max_content_length", 8196);
|
||||
int default_limit = get_config_int("default_limit", 500);
|
||||
int admin_enabled = get_config_bool("admin_enabled", 0);
|
||||
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
|
||||
// Update relay information fields
|
||||
if (relay_name) {
|
||||
strncpy(g_unified_cache.relay_info.name, relay_name, sizeof(g_unified_cache.relay_info.name) - 1);
|
||||
} else {
|
||||
strncpy(g_unified_cache.relay_info.name, "C Nostr Relay", sizeof(g_unified_cache.relay_info.name) - 1);
|
||||
}
|
||||
|
||||
if (relay_description) {
|
||||
strncpy(g_unified_cache.relay_info.description, relay_description, sizeof(g_unified_cache.relay_info.description) - 1);
|
||||
} else {
|
||||
strncpy(g_unified_cache.relay_info.description, "A high-performance Nostr relay implemented in C with SQLite storage", sizeof(g_unified_cache.relay_info.description) - 1);
|
||||
}
|
||||
|
||||
if (relay_software) {
|
||||
strncpy(g_unified_cache.relay_info.software, relay_software, sizeof(g_unified_cache.relay_info.software) - 1);
|
||||
} else {
|
||||
strncpy(g_unified_cache.relay_info.software, "https://git.laantungir.net/laantungir/c-relay.git", sizeof(g_unified_cache.relay_info.software) - 1);
|
||||
}
|
||||
|
||||
if (relay_version) {
|
||||
strncpy(g_unified_cache.relay_info.version, relay_version, sizeof(g_unified_cache.relay_info.version) - 1);
|
||||
} else {
|
||||
strncpy(g_unified_cache.relay_info.version, "0.2.0", sizeof(g_unified_cache.relay_info.version) - 1);
|
||||
}
|
||||
|
||||
if (relay_contact) {
|
||||
strncpy(g_unified_cache.relay_info.contact, relay_contact, sizeof(g_unified_cache.relay_info.contact) - 1);
|
||||
}
|
||||
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
if (relay_pubkey) {
|
||||
strncpy(g_relay_info.pubkey, relay_pubkey, sizeof(g_relay_info.pubkey) - 1);
|
||||
strncpy(g_unified_cache.relay_info.pubkey, relay_pubkey, sizeof(g_unified_cache.relay_info.pubkey) - 1);
|
||||
}
|
||||
|
||||
// Initialize supported NIPs array
|
||||
g_relay_info.supported_nips = cJSON_CreateArray();
|
||||
if (g_relay_info.supported_nips) {
|
||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(1)); // NIP-01: Basic protocol
|
||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(9)); // NIP-09: Event deletion
|
||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(11)); // NIP-11: Relay information
|
||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(13)); // NIP-13: Proof of Work
|
||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(15)); // NIP-15: EOSE
|
||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(20)); // NIP-20: Command results
|
||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(40)); // NIP-40: Expiration Timestamp
|
||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(42)); // NIP-42: Authentication
|
||||
g_unified_cache.relay_info.supported_nips = cJSON_CreateArray();
|
||||
if (g_unified_cache.relay_info.supported_nips) {
|
||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(1)); // NIP-01: Basic protocol
|
||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(9)); // NIP-09: Event deletion
|
||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(11)); // NIP-11: Relay information
|
||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(13)); // NIP-13: Proof of Work
|
||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(15)); // NIP-15: EOSE
|
||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(20)); // NIP-20: Command results
|
||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(40)); // NIP-40: Expiration Timestamp
|
||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(42)); // NIP-42: Authentication
|
||||
}
|
||||
|
||||
// Initialize server limitations using configuration
|
||||
g_relay_info.limitation = cJSON_CreateObject();
|
||||
if (g_relay_info.limitation) {
|
||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_message_length", get_config_int("max_message_length", 16384));
|
||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_subscriptions", get_config_int("max_subscriptions_per_client", 20));
|
||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_limit", get_config_int("max_limit", 5000));
|
||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH);
|
||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_event_tags", get_config_int("max_event_tags", 100));
|
||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_content_length", get_config_int("max_content_length", 8196));
|
||||
cJSON_AddNumberToObject(g_relay_info.limitation, "min_pow_difficulty", g_pow_config.min_pow_difficulty);
|
||||
cJSON_AddBoolToObject(g_relay_info.limitation, "auth_required", get_config_bool("admin_enabled", 0) ? cJSON_True : cJSON_False);
|
||||
cJSON_AddBoolToObject(g_relay_info.limitation, "payment_required", cJSON_False);
|
||||
cJSON_AddBoolToObject(g_relay_info.limitation, "restricted_writes", cJSON_False);
|
||||
cJSON_AddNumberToObject(g_relay_info.limitation, "created_at_lower_limit", 0);
|
||||
cJSON_AddNumberToObject(g_relay_info.limitation, "created_at_upper_limit", 2147483647);
|
||||
cJSON_AddNumberToObject(g_relay_info.limitation, "default_limit", get_config_int("default_limit", 500));
|
||||
g_unified_cache.relay_info.limitation = cJSON_CreateObject();
|
||||
if (g_unified_cache.relay_info.limitation) {
|
||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_message_length", max_message_length);
|
||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_subscriptions", max_subscriptions_per_client);
|
||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_limit", max_limit);
|
||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH);
|
||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_event_tags", max_event_tags);
|
||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_content_length", max_content_length);
|
||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "min_pow_difficulty", g_unified_cache.pow_config.min_pow_difficulty);
|
||||
cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "auth_required", admin_enabled ? cJSON_True : cJSON_False);
|
||||
cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "payment_required", cJSON_False);
|
||||
cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "restricted_writes", cJSON_False);
|
||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "created_at_lower_limit", 0);
|
||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "created_at_upper_limit", 2147483647);
|
||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "default_limit", default_limit);
|
||||
}
|
||||
|
||||
// Initialize empty retention policies (can be configured later)
|
||||
g_relay_info.retention = cJSON_CreateArray();
|
||||
g_unified_cache.relay_info.retention = cJSON_CreateArray();
|
||||
|
||||
// Initialize language tags - set to global for now
|
||||
g_relay_info.language_tags = cJSON_CreateArray();
|
||||
if (g_relay_info.language_tags) {
|
||||
cJSON_AddItemToArray(g_relay_info.language_tags, cJSON_CreateString("*"));
|
||||
g_unified_cache.relay_info.language_tags = cJSON_CreateArray();
|
||||
if (g_unified_cache.relay_info.language_tags) {
|
||||
cJSON_AddItemToArray(g_unified_cache.relay_info.language_tags, cJSON_CreateString("*"));
|
||||
}
|
||||
|
||||
// Initialize relay countries - set to global for now
|
||||
g_relay_info.relay_countries = cJSON_CreateArray();
|
||||
if (g_relay_info.relay_countries) {
|
||||
cJSON_AddItemToArray(g_relay_info.relay_countries, cJSON_CreateString("*"));
|
||||
g_unified_cache.relay_info.relay_countries = cJSON_CreateArray();
|
||||
if (g_unified_cache.relay_info.relay_countries) {
|
||||
cJSON_AddItemToArray(g_unified_cache.relay_info.relay_countries, cJSON_CreateString("*"));
|
||||
}
|
||||
|
||||
// Initialize content tags as empty array
|
||||
g_relay_info.tags = cJSON_CreateArray();
|
||||
g_unified_cache.relay_info.tags = cJSON_CreateArray();
|
||||
|
||||
// Initialize fees as empty object (no payment required by default)
|
||||
g_relay_info.fees = cJSON_CreateObject();
|
||||
g_unified_cache.relay_info.fees = cJSON_CreateObject();
|
||||
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
log_success("Relay information initialized with default values");
|
||||
}
|
||||
|
||||
// Clean up relay information JSON objects
|
||||
void cleanup_relay_info() {
|
||||
if (g_relay_info.supported_nips) {
|
||||
cJSON_Delete(g_relay_info.supported_nips);
|
||||
g_relay_info.supported_nips = NULL;
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
if (g_unified_cache.relay_info.supported_nips) {
|
||||
cJSON_Delete(g_unified_cache.relay_info.supported_nips);
|
||||
g_unified_cache.relay_info.supported_nips = NULL;
|
||||
}
|
||||
if (g_relay_info.limitation) {
|
||||
cJSON_Delete(g_relay_info.limitation);
|
||||
g_relay_info.limitation = NULL;
|
||||
if (g_unified_cache.relay_info.limitation) {
|
||||
cJSON_Delete(g_unified_cache.relay_info.limitation);
|
||||
g_unified_cache.relay_info.limitation = NULL;
|
||||
}
|
||||
if (g_relay_info.retention) {
|
||||
cJSON_Delete(g_relay_info.retention);
|
||||
g_relay_info.retention = NULL;
|
||||
if (g_unified_cache.relay_info.retention) {
|
||||
cJSON_Delete(g_unified_cache.relay_info.retention);
|
||||
g_unified_cache.relay_info.retention = NULL;
|
||||
}
|
||||
if (g_relay_info.language_tags) {
|
||||
cJSON_Delete(g_relay_info.language_tags);
|
||||
g_relay_info.language_tags = NULL;
|
||||
if (g_unified_cache.relay_info.language_tags) {
|
||||
cJSON_Delete(g_unified_cache.relay_info.language_tags);
|
||||
g_unified_cache.relay_info.language_tags = NULL;
|
||||
}
|
||||
if (g_relay_info.relay_countries) {
|
||||
cJSON_Delete(g_relay_info.relay_countries);
|
||||
g_relay_info.relay_countries = NULL;
|
||||
if (g_unified_cache.relay_info.relay_countries) {
|
||||
cJSON_Delete(g_unified_cache.relay_info.relay_countries);
|
||||
g_unified_cache.relay_info.relay_countries = NULL;
|
||||
}
|
||||
if (g_relay_info.tags) {
|
||||
cJSON_Delete(g_relay_info.tags);
|
||||
g_relay_info.tags = NULL;
|
||||
if (g_unified_cache.relay_info.tags) {
|
||||
cJSON_Delete(g_unified_cache.relay_info.tags);
|
||||
g_unified_cache.relay_info.tags = NULL;
|
||||
}
|
||||
if (g_relay_info.fees) {
|
||||
cJSON_Delete(g_relay_info.fees);
|
||||
g_relay_info.fees = NULL;
|
||||
if (g_unified_cache.relay_info.fees) {
|
||||
cJSON_Delete(g_unified_cache.relay_info.fees);
|
||||
g_unified_cache.relay_info.fees = NULL;
|
||||
}
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
}
|
||||
|
||||
// Generate NIP-11 compliant JSON document
|
||||
@@ -1612,79 +1636,83 @@ cJSON* generate_relay_info_json() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
|
||||
// Add basic relay information
|
||||
if (strlen(g_relay_info.name) > 0) {
|
||||
cJSON_AddStringToObject(info, "name", g_relay_info.name);
|
||||
if (strlen(g_unified_cache.relay_info.name) > 0) {
|
||||
cJSON_AddStringToObject(info, "name", g_unified_cache.relay_info.name);
|
||||
}
|
||||
if (strlen(g_relay_info.description) > 0) {
|
||||
cJSON_AddStringToObject(info, "description", g_relay_info.description);
|
||||
if (strlen(g_unified_cache.relay_info.description) > 0) {
|
||||
cJSON_AddStringToObject(info, "description", g_unified_cache.relay_info.description);
|
||||
}
|
||||
if (strlen(g_relay_info.banner) > 0) {
|
||||
cJSON_AddStringToObject(info, "banner", g_relay_info.banner);
|
||||
if (strlen(g_unified_cache.relay_info.banner) > 0) {
|
||||
cJSON_AddStringToObject(info, "banner", g_unified_cache.relay_info.banner);
|
||||
}
|
||||
if (strlen(g_relay_info.icon) > 0) {
|
||||
cJSON_AddStringToObject(info, "icon", g_relay_info.icon);
|
||||
if (strlen(g_unified_cache.relay_info.icon) > 0) {
|
||||
cJSON_AddStringToObject(info, "icon", g_unified_cache.relay_info.icon);
|
||||
}
|
||||
if (strlen(g_relay_info.pubkey) > 0) {
|
||||
cJSON_AddStringToObject(info, "pubkey", g_relay_info.pubkey);
|
||||
if (strlen(g_unified_cache.relay_info.pubkey) > 0) {
|
||||
cJSON_AddStringToObject(info, "pubkey", g_unified_cache.relay_info.pubkey);
|
||||
}
|
||||
if (strlen(g_relay_info.contact) > 0) {
|
||||
cJSON_AddStringToObject(info, "contact", g_relay_info.contact);
|
||||
if (strlen(g_unified_cache.relay_info.contact) > 0) {
|
||||
cJSON_AddStringToObject(info, "contact", g_unified_cache.relay_info.contact);
|
||||
}
|
||||
|
||||
// Add supported NIPs
|
||||
if (g_relay_info.supported_nips) {
|
||||
cJSON_AddItemToObject(info, "supported_nips", cJSON_Duplicate(g_relay_info.supported_nips, 1));
|
||||
if (g_unified_cache.relay_info.supported_nips) {
|
||||
cJSON_AddItemToObject(info, "supported_nips", cJSON_Duplicate(g_unified_cache.relay_info.supported_nips, 1));
|
||||
}
|
||||
|
||||
// Add software information
|
||||
if (strlen(g_relay_info.software) > 0) {
|
||||
cJSON_AddStringToObject(info, "software", g_relay_info.software);
|
||||
if (strlen(g_unified_cache.relay_info.software) > 0) {
|
||||
cJSON_AddStringToObject(info, "software", g_unified_cache.relay_info.software);
|
||||
}
|
||||
if (strlen(g_relay_info.version) > 0) {
|
||||
cJSON_AddStringToObject(info, "version", g_relay_info.version);
|
||||
if (strlen(g_unified_cache.relay_info.version) > 0) {
|
||||
cJSON_AddStringToObject(info, "version", g_unified_cache.relay_info.version);
|
||||
}
|
||||
|
||||
// Add policies
|
||||
if (strlen(g_relay_info.privacy_policy) > 0) {
|
||||
cJSON_AddStringToObject(info, "privacy_policy", g_relay_info.privacy_policy);
|
||||
if (strlen(g_unified_cache.relay_info.privacy_policy) > 0) {
|
||||
cJSON_AddStringToObject(info, "privacy_policy", g_unified_cache.relay_info.privacy_policy);
|
||||
}
|
||||
if (strlen(g_relay_info.terms_of_service) > 0) {
|
||||
cJSON_AddStringToObject(info, "terms_of_service", g_relay_info.terms_of_service);
|
||||
if (strlen(g_unified_cache.relay_info.terms_of_service) > 0) {
|
||||
cJSON_AddStringToObject(info, "terms_of_service", g_unified_cache.relay_info.terms_of_service);
|
||||
}
|
||||
if (strlen(g_relay_info.posting_policy) > 0) {
|
||||
cJSON_AddStringToObject(info, "posting_policy", g_relay_info.posting_policy);
|
||||
if (strlen(g_unified_cache.relay_info.posting_policy) > 0) {
|
||||
cJSON_AddStringToObject(info, "posting_policy", g_unified_cache.relay_info.posting_policy);
|
||||
}
|
||||
|
||||
// Add server limitations
|
||||
if (g_relay_info.limitation) {
|
||||
cJSON_AddItemToObject(info, "limitation", cJSON_Duplicate(g_relay_info.limitation, 1));
|
||||
if (g_unified_cache.relay_info.limitation) {
|
||||
cJSON_AddItemToObject(info, "limitation", cJSON_Duplicate(g_unified_cache.relay_info.limitation, 1));
|
||||
}
|
||||
|
||||
// Add retention policies if configured
|
||||
if (g_relay_info.retention && cJSON_GetArraySize(g_relay_info.retention) > 0) {
|
||||
cJSON_AddItemToObject(info, "retention", cJSON_Duplicate(g_relay_info.retention, 1));
|
||||
if (g_unified_cache.relay_info.retention && cJSON_GetArraySize(g_unified_cache.relay_info.retention) > 0) {
|
||||
cJSON_AddItemToObject(info, "retention", cJSON_Duplicate(g_unified_cache.relay_info.retention, 1));
|
||||
}
|
||||
|
||||
// Add geographical and language information
|
||||
if (g_relay_info.relay_countries) {
|
||||
cJSON_AddItemToObject(info, "relay_countries", cJSON_Duplicate(g_relay_info.relay_countries, 1));
|
||||
if (g_unified_cache.relay_info.relay_countries) {
|
||||
cJSON_AddItemToObject(info, "relay_countries", cJSON_Duplicate(g_unified_cache.relay_info.relay_countries, 1));
|
||||
}
|
||||
if (g_relay_info.language_tags) {
|
||||
cJSON_AddItemToObject(info, "language_tags", cJSON_Duplicate(g_relay_info.language_tags, 1));
|
||||
if (g_unified_cache.relay_info.language_tags) {
|
||||
cJSON_AddItemToObject(info, "language_tags", cJSON_Duplicate(g_unified_cache.relay_info.language_tags, 1));
|
||||
}
|
||||
if (g_relay_info.tags && cJSON_GetArraySize(g_relay_info.tags) > 0) {
|
||||
cJSON_AddItemToObject(info, "tags", cJSON_Duplicate(g_relay_info.tags, 1));
|
||||
if (g_unified_cache.relay_info.tags && cJSON_GetArraySize(g_unified_cache.relay_info.tags) > 0) {
|
||||
cJSON_AddItemToObject(info, "tags", cJSON_Duplicate(g_unified_cache.relay_info.tags, 1));
|
||||
}
|
||||
|
||||
// Add payment information if configured
|
||||
if (strlen(g_relay_info.payments_url) > 0) {
|
||||
cJSON_AddStringToObject(info, "payments_url", g_relay_info.payments_url);
|
||||
if (strlen(g_unified_cache.relay_info.payments_url) > 0) {
|
||||
cJSON_AddStringToObject(info, "payments_url", g_unified_cache.relay_info.payments_url);
|
||||
}
|
||||
if (g_relay_info.fees && cJSON_GetObjectItem(g_relay_info.fees, "admission")) {
|
||||
cJSON_AddItemToObject(info, "fees", cJSON_Duplicate(g_relay_info.fees, 1));
|
||||
if (g_unified_cache.relay_info.fees && cJSON_GetObjectItem(g_unified_cache.relay_info.fees, "admission")) {
|
||||
cJSON_AddItemToObject(info, "fees", cJSON_Duplicate(g_unified_cache.relay_info.fees, 1));
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -1862,34 +1890,40 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
|
||||
void init_pow_config() {
|
||||
log_info("Initializing NIP-13 Proof of Work configuration");
|
||||
|
||||
// Load PoW settings from configuration system
|
||||
g_pow_config.enabled = get_config_bool("pow_enabled", 1);
|
||||
g_pow_config.min_pow_difficulty = get_config_int("pow_min_difficulty", 0);
|
||||
|
||||
// Get PoW mode from configuration
|
||||
// Get all config values first (without holding mutex to avoid deadlock)
|
||||
int pow_enabled = get_config_bool("pow_enabled", 1);
|
||||
int pow_min_difficulty = get_config_int("pow_min_difficulty", 0);
|
||||
const char* pow_mode = get_config_value("pow_mode");
|
||||
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
|
||||
// Load PoW settings from configuration system
|
||||
g_unified_cache.pow_config.enabled = pow_enabled;
|
||||
g_unified_cache.pow_config.min_pow_difficulty = pow_min_difficulty;
|
||||
|
||||
// Configure PoW mode
|
||||
if (pow_mode) {
|
||||
if (strcmp(pow_mode, "strict") == 0) {
|
||||
g_pow_config.validation_flags = NOSTR_POW_VALIDATE_ANTI_SPAM | NOSTR_POW_STRICT_FORMAT;
|
||||
g_pow_config.require_nonce_tag = 1;
|
||||
g_pow_config.reject_lower_targets = 1;
|
||||
g_pow_config.strict_format = 1;
|
||||
g_pow_config.anti_spam_mode = 1;
|
||||
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_ANTI_SPAM | NOSTR_POW_STRICT_FORMAT;
|
||||
g_unified_cache.pow_config.require_nonce_tag = 1;
|
||||
g_unified_cache.pow_config.reject_lower_targets = 1;
|
||||
g_unified_cache.pow_config.strict_format = 1;
|
||||
g_unified_cache.pow_config.anti_spam_mode = 1;
|
||||
log_info("PoW configured in strict anti-spam mode");
|
||||
} else if (strcmp(pow_mode, "full") == 0) {
|
||||
g_pow_config.validation_flags = NOSTR_POW_VALIDATE_FULL;
|
||||
g_pow_config.require_nonce_tag = 1;
|
||||
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_FULL;
|
||||
g_unified_cache.pow_config.require_nonce_tag = 1;
|
||||
log_info("PoW configured in full validation mode");
|
||||
} else if (strcmp(pow_mode, "basic") == 0) {
|
||||
g_pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC;
|
||||
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC;
|
||||
log_info("PoW configured in basic validation mode");
|
||||
} else if (strcmp(pow_mode, "disabled") == 0) {
|
||||
g_pow_config.enabled = 0;
|
||||
g_unified_cache.pow_config.enabled = 0;
|
||||
log_info("PoW validation disabled via configuration");
|
||||
}
|
||||
} else {
|
||||
// Default to basic mode
|
||||
g_pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC;
|
||||
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC;
|
||||
log_info("PoW configured in basic validation mode (default)");
|
||||
}
|
||||
|
||||
@@ -1897,17 +1931,25 @@ void init_pow_config() {
|
||||
char config_msg[512];
|
||||
snprintf(config_msg, sizeof(config_msg),
|
||||
"PoW Configuration: enabled=%s, min_difficulty=%d, validation_flags=0x%x, mode=%s",
|
||||
g_pow_config.enabled ? "true" : "false",
|
||||
g_pow_config.min_pow_difficulty,
|
||||
g_pow_config.validation_flags,
|
||||
g_pow_config.anti_spam_mode ? "anti-spam" :
|
||||
(g_pow_config.validation_flags & NOSTR_POW_VALIDATE_FULL) ? "full" : "basic");
|
||||
g_unified_cache.pow_config.enabled ? "true" : "false",
|
||||
g_unified_cache.pow_config.min_pow_difficulty,
|
||||
g_unified_cache.pow_config.validation_flags,
|
||||
g_unified_cache.pow_config.anti_spam_mode ? "anti-spam" :
|
||||
(g_unified_cache.pow_config.validation_flags & NOSTR_POW_VALIDATE_FULL) ? "full" : "basic");
|
||||
log_info(config_msg);
|
||||
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
}
|
||||
|
||||
// Validate event Proof of Work according to NIP-13
|
||||
int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
||||
if (!g_pow_config.enabled) {
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
int enabled = g_unified_cache.pow_config.enabled;
|
||||
int min_pow_difficulty = g_unified_cache.pow_config.min_pow_difficulty;
|
||||
int validation_flags = g_unified_cache.pow_config.validation_flags;
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
if (!enabled) {
|
||||
return 0; // PoW validation disabled
|
||||
}
|
||||
|
||||
@@ -1918,7 +1960,7 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
||||
|
||||
// If min_pow_difficulty is 0, only validate events that have nonce tags
|
||||
// This allows events without PoW when difficulty requirement is 0
|
||||
if (g_pow_config.min_pow_difficulty == 0) {
|
||||
if (min_pow_difficulty == 0) {
|
||||
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||
int has_nonce_tag = 0;
|
||||
|
||||
@@ -1946,8 +1988,8 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
||||
|
||||
// Perform PoW validation using nostr_core_lib
|
||||
nostr_pow_result_t pow_result;
|
||||
int validation_result = nostr_validate_pow(event, g_pow_config.min_pow_difficulty,
|
||||
g_pow_config.validation_flags, &pow_result);
|
||||
int validation_result = nostr_validate_pow(event, min_pow_difficulty,
|
||||
validation_flags, &pow_result);
|
||||
|
||||
if (validation_result != NOSTR_SUCCESS) {
|
||||
// Handle specific error cases with appropriate messages
|
||||
@@ -1955,12 +1997,12 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
||||
case NOSTR_ERROR_NIP13_INSUFFICIENT:
|
||||
snprintf(error_message, error_size,
|
||||
"pow: insufficient difficulty: %d < %d",
|
||||
pow_result.actual_difficulty, g_pow_config.min_pow_difficulty);
|
||||
pow_result.actual_difficulty, min_pow_difficulty);
|
||||
log_warning("Event rejected: insufficient PoW difficulty");
|
||||
break;
|
||||
case NOSTR_ERROR_NIP13_NO_NONCE_TAG:
|
||||
// This should not happen with min_difficulty=0 after our check above
|
||||
if (g_pow_config.min_pow_difficulty > 0) {
|
||||
if (min_pow_difficulty > 0) {
|
||||
snprintf(error_message, error_size, "pow: missing required nonce tag");
|
||||
log_warning("Event rejected: missing nonce tag");
|
||||
} else {
|
||||
@@ -1974,7 +2016,7 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
||||
case NOSTR_ERROR_NIP13_TARGET_MISMATCH:
|
||||
snprintf(error_message, error_size,
|
||||
"pow: committed target (%d) lower than minimum (%d)",
|
||||
pow_result.committed_target, g_pow_config.min_pow_difficulty);
|
||||
pow_result.committed_target, min_pow_difficulty);
|
||||
log_warning("Event rejected: committed target too low (anti-spam protection)");
|
||||
break;
|
||||
case NOSTR_ERROR_NIP13_CALCULATION:
|
||||
@@ -1994,7 +2036,7 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
||||
}
|
||||
|
||||
// Log successful PoW validation (only if minimum difficulty is required)
|
||||
if (g_pow_config.min_pow_difficulty > 0 || pow_result.has_nonce_tag) {
|
||||
if (min_pow_difficulty > 0 || pow_result.has_nonce_tag) {
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg),
|
||||
"PoW validated: difficulty=%d, target=%d, nonce=%llu%s",
|
||||
@@ -2018,28 +2060,39 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
||||
void init_expiration_config() {
|
||||
log_info("Initializing NIP-40 Expiration Timestamp configuration");
|
||||
|
||||
// Get all config values first (without holding mutex to avoid deadlock)
|
||||
int expiration_enabled = get_config_bool("expiration_enabled", 1);
|
||||
int expiration_strict = get_config_bool("expiration_strict", 1);
|
||||
int expiration_filter = get_config_bool("expiration_filter", 1);
|
||||
int expiration_delete = get_config_bool("expiration_delete", 0);
|
||||
long expiration_grace_period = get_config_int("expiration_grace_period", 1);
|
||||
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
|
||||
// Load expiration settings from configuration system
|
||||
g_expiration_config.enabled = get_config_bool("expiration_enabled", 1);
|
||||
g_expiration_config.strict_mode = get_config_bool("expiration_strict", 1);
|
||||
g_expiration_config.filter_responses = get_config_bool("expiration_filter", 1);
|
||||
g_expiration_config.delete_expired = get_config_bool("expiration_delete", 0);
|
||||
g_expiration_config.grace_period = get_config_int("expiration_grace_period", 1);
|
||||
g_unified_cache.expiration_config.enabled = expiration_enabled;
|
||||
g_unified_cache.expiration_config.strict_mode = expiration_strict;
|
||||
g_unified_cache.expiration_config.filter_responses = expiration_filter;
|
||||
g_unified_cache.expiration_config.delete_expired = expiration_delete;
|
||||
g_unified_cache.expiration_config.grace_period = expiration_grace_period;
|
||||
|
||||
// Validate grace period bounds
|
||||
if (g_expiration_config.grace_period < 0 || g_expiration_config.grace_period > 86400) {
|
||||
if (g_unified_cache.expiration_config.grace_period < 0 || g_unified_cache.expiration_config.grace_period > 86400) {
|
||||
log_warning("Invalid grace period, using default of 300 seconds");
|
||||
g_expiration_config.grace_period = 300;
|
||||
g_unified_cache.expiration_config.grace_period = 300;
|
||||
}
|
||||
|
||||
// Log final configuration
|
||||
char config_msg[512];
|
||||
snprintf(config_msg, sizeof(config_msg),
|
||||
"Expiration Configuration: enabled=%s, strict_mode=%s, filter_responses=%s, grace_period=%ld seconds",
|
||||
g_expiration_config.enabled ? "true" : "false",
|
||||
g_expiration_config.strict_mode ? "true" : "false",
|
||||
g_expiration_config.filter_responses ? "true" : "false",
|
||||
g_expiration_config.grace_period);
|
||||
g_unified_cache.expiration_config.enabled ? "true" : "false",
|
||||
g_unified_cache.expiration_config.strict_mode ? "true" : "false",
|
||||
g_unified_cache.expiration_config.filter_responses ? "true" : "false",
|
||||
g_unified_cache.expiration_config.grace_period);
|
||||
log_info(config_msg);
|
||||
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
}
|
||||
|
||||
// Extract expiration timestamp from event tags
|
||||
@@ -2109,12 +2162,22 @@ int is_event_expired(cJSON* event, time_t current_time) {
|
||||
}
|
||||
|
||||
// Check if current time exceeds expiration + grace period
|
||||
return (current_time > (expiration_ts + g_expiration_config.grace_period));
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
long grace_period = g_unified_cache.expiration_config.grace_period;
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
return (current_time > (expiration_ts + grace_period));
|
||||
}
|
||||
|
||||
// Validate event expiration according to NIP-40
|
||||
int validate_event_expiration(cJSON* event, char* error_message, size_t error_size) {
|
||||
if (!g_expiration_config.enabled) {
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
int enabled = g_unified_cache.expiration_config.enabled;
|
||||
int strict_mode = g_unified_cache.expiration_config.strict_mode;
|
||||
long grace_period = g_unified_cache.expiration_config.grace_period;
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
if (!enabled) {
|
||||
return 0; // Expiration validation disabled
|
||||
}
|
||||
|
||||
@@ -2126,13 +2189,13 @@ int validate_event_expiration(cJSON* event, char* error_message, size_t error_si
|
||||
// Check if event is expired
|
||||
time_t current_time = time(NULL);
|
||||
if (is_event_expired(event, current_time)) {
|
||||
if (g_expiration_config.strict_mode) {
|
||||
if (strict_mode) {
|
||||
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||
long expiration_ts = extract_expiration_timestamp(tags);
|
||||
|
||||
snprintf(error_message, error_size,
|
||||
"invalid: event expired (expiration=%ld, current=%ld, grace=%ld)",
|
||||
expiration_ts, (long)current_time, g_expiration_config.grace_period);
|
||||
expiration_ts, (long)current_time, grace_period);
|
||||
log_warning("Event rejected: expired timestamp");
|
||||
return -1;
|
||||
} else {
|
||||
@@ -2628,6 +2691,54 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check for kind 33334 configuration event requests BEFORE creating subscription
|
||||
int config_events_sent = 0;
|
||||
int has_config_request = 0;
|
||||
|
||||
// Check if any filter requests kind 33334 (configuration events)
|
||||
for (int i = 0; i < cJSON_GetArraySize(filters); i++) {
|
||||
cJSON* filter = cJSON_GetArrayItem(filters, i);
|
||||
if (filter && cJSON_IsObject(filter)) {
|
||||
if (req_filter_requests_config_events(filter)) {
|
||||
has_config_request = 1;
|
||||
|
||||
// Generate synthetic config event for this subscription
|
||||
cJSON* filters_array = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(filters_array, cJSON_Duplicate(filter, 1));
|
||||
|
||||
cJSON* event_msg = generate_synthetic_config_event_for_subscription(sub_id, filters_array);
|
||||
|
||||
if (event_msg) {
|
||||
char* msg_str = cJSON_Print(event_msg);
|
||||
if (msg_str) {
|
||||
size_t msg_len = strlen(msg_str);
|
||||
unsigned char* buf = malloc(LWS_PRE + msg_len);
|
||||
if (buf) {
|
||||
memcpy(buf + LWS_PRE, msg_str, msg_len);
|
||||
lws_write(wsi, buf + LWS_PRE, msg_len, LWS_WRITE_TEXT);
|
||||
config_events_sent++;
|
||||
free(buf);
|
||||
}
|
||||
free(msg_str);
|
||||
}
|
||||
cJSON_Delete(event_msg);
|
||||
}
|
||||
|
||||
cJSON_Delete(filters_array);
|
||||
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg),
|
||||
"Generated %d synthetic config events for subscription %s",
|
||||
config_events_sent, sub_id);
|
||||
log_info(debug_msg);
|
||||
break; // Only generate once per subscription
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If only config events were requested, we can return early after sending EOSE
|
||||
// But still create the subscription for future config updates
|
||||
|
||||
// Check session subscription limits
|
||||
if (pss && pss->subscription_count >= g_subscription_manager.max_subscriptions_per_client) {
|
||||
log_error("Maximum subscriptions per client exceeded");
|
||||
@@ -2651,14 +2762,14 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
||||
}
|
||||
cJSON_Delete(closed_msg);
|
||||
|
||||
return 0;
|
||||
return has_config_request ? config_events_sent : 0;
|
||||
}
|
||||
|
||||
// Create persistent subscription
|
||||
subscription_t* subscription = create_subscription(sub_id, wsi, filters, pss ? pss->client_ip : "unknown");
|
||||
if (!subscription) {
|
||||
log_error("Failed to create subscription");
|
||||
return 0;
|
||||
return has_config_request ? config_events_sent : 0;
|
||||
}
|
||||
|
||||
// Add to global manager
|
||||
@@ -2685,7 +2796,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
||||
}
|
||||
cJSON_Delete(closed_msg);
|
||||
|
||||
return 0;
|
||||
return has_config_request ? config_events_sent : 0;
|
||||
}
|
||||
|
||||
// Add to session's subscription list (if session data available)
|
||||
@@ -2697,7 +2808,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
||||
pthread_mutex_unlock(&pss->session_lock);
|
||||
}
|
||||
|
||||
int events_sent = 0;
|
||||
int events_sent = config_events_sent; // Start with synthetic config events
|
||||
|
||||
// Process each filter in the array
|
||||
for (int i = 0; i < cJSON_GetArraySize(filters); i++) {
|
||||
@@ -2844,7 +2955,12 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
||||
cJSON_AddItemToObject(event, "tags", tags);
|
||||
|
||||
// Check expiration filtering (NIP-40) at application level
|
||||
if (g_expiration_config.enabled && g_expiration_config.filter_responses) {
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
int expiration_enabled = g_unified_cache.expiration_config.enabled;
|
||||
int filter_responses = g_unified_cache.expiration_config.filter_responses;
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
if (expiration_enabled && filter_responses) {
|
||||
time_t current_time = time(NULL);
|
||||
if (is_event_expired(event, current_time)) {
|
||||
// Skip this expired event
|
||||
@@ -2955,8 +3071,13 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Get real client IP address
|
||||
char client_ip[CLIENT_IP_MAX_LENGTH];
|
||||
lws_get_peer_simple(wsi, client_ip, sizeof(client_ip));
|
||||
strncpy(pss->client_ip, client_ip, CLIENT_IP_MAX_LENGTH - 1);
|
||||
pss->client_ip[CLIENT_IP_MAX_LENGTH - 1] = '\0';
|
||||
|
||||
// Ensure client_ip is null-terminated and copy safely
|
||||
client_ip[CLIENT_IP_MAX_LENGTH - 1] = '\0';
|
||||
size_t ip_len = strlen(client_ip);
|
||||
size_t copy_len = (ip_len < CLIENT_IP_MAX_LENGTH - 1) ? ip_len : CLIENT_IP_MAX_LENGTH - 1;
|
||||
memcpy(pss->client_ip, client_ip, copy_len);
|
||||
pss->client_ip[copy_len] = '\0';
|
||||
|
||||
// Initialize NIP-42 authentication state
|
||||
pss->authenticated = 0;
|
||||
@@ -3092,17 +3213,50 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Cleanup event JSON string
|
||||
free(event_json_str);
|
||||
|
||||
// Store event in database and broadcast to subscriptions
|
||||
// Check for admin events (kinds 33334 and 33335) and intercept them
|
||||
if (result == 0) {
|
||||
// Store the event in the database first
|
||||
if (store_event(event) != 0) {
|
||||
log_error("Failed to store event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
if (kind_obj && cJSON_IsNumber(kind_obj)) {
|
||||
int event_kind = (int)cJSON_GetNumberValue(kind_obj);
|
||||
|
||||
if (event_kind == 33334 || event_kind == 33335) {
|
||||
// This is an admin event - process it through the admin API instead of normal storage
|
||||
log_info("Admin event detected, processing through admin API");
|
||||
|
||||
char admin_error[512] = {0};
|
||||
if (process_admin_event_in_config(event, admin_error, sizeof(admin_error)) != 0) {
|
||||
log_error("Failed to process admin event through admin API");
|
||||
result = -1;
|
||||
size_t error_len = strlen(admin_error);
|
||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||
memcpy(error_message, admin_error, copy_len);
|
||||
error_message[copy_len] = '\0';
|
||||
} else {
|
||||
log_success("Admin event processed successfully through admin API");
|
||||
// Admin events are processed by the admin API, not broadcast to subscriptions
|
||||
}
|
||||
} else {
|
||||
// Regular event - store in database and broadcast
|
||||
if (store_event(event) != 0) {
|
||||
log_error("Failed to store event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
log_info("Event stored successfully in database");
|
||||
// Broadcast event to matching persistent subscriptions
|
||||
broadcast_event_to_subscriptions(event);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log_info("Event stored successfully in database");
|
||||
// Broadcast event to matching persistent subscriptions
|
||||
broadcast_event_to_subscriptions(event);
|
||||
// Event without valid kind - try normal storage
|
||||
if (store_event(event) != 0) {
|
||||
log_error("Failed to store event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
log_info("Event stored successfully in database");
|
||||
broadcast_event_to_subscriptions(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3651,10 +3805,29 @@ int main(int argc, char* argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Systematically add pubkeys to config table
|
||||
if (add_pubkeys_to_config_table() != 0) {
|
||||
log_warning("Failed to add pubkeys to config table systematically");
|
||||
} else {
|
||||
log_success("Pubkeys added to config table systematically");
|
||||
}
|
||||
|
||||
// Retry storing the configuration event now that database is initialized
|
||||
if (retry_store_initial_config_event() != 0) {
|
||||
log_warning("Failed to store initial configuration event after database init");
|
||||
}
|
||||
|
||||
// Now store the pubkeys in config table since database is available
|
||||
const char* admin_pubkey = get_admin_pubkey_cached();
|
||||
const char* relay_pubkey_from_cache = get_relay_pubkey_cached();
|
||||
if (admin_pubkey && strlen(admin_pubkey) == 64) {
|
||||
set_config_value_in_table("admin_pubkey", admin_pubkey, "string", "Administrator public key", "authentication", 0);
|
||||
log_success("Admin pubkey stored in config table for first-time startup");
|
||||
}
|
||||
if (relay_pubkey_from_cache && strlen(relay_pubkey_from_cache) == 64) {
|
||||
set_config_value_in_table("relay_pubkey", relay_pubkey_from_cache, "string", "Relay public key", "relay", 0);
|
||||
log_success("Relay pubkey stored in config table for first-time startup");
|
||||
}
|
||||
} else {
|
||||
log_info("Existing relay detected");
|
||||
|
||||
@@ -3724,6 +3897,21 @@ int main(int argc, char* argv[]) {
|
||||
log_warning("Failed to apply configuration from database");
|
||||
} else {
|
||||
log_success("Configuration loaded from database");
|
||||
|
||||
// Extract admin pubkey from the config event and store in config table for unified cache access
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(config_event, "pubkey");
|
||||
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
|
||||
|
||||
// Store both admin and relay pubkeys in config table for unified cache
|
||||
if (admin_pubkey && strlen(admin_pubkey) == 64) {
|
||||
set_config_value_in_table("admin_pubkey", admin_pubkey, "string", "Administrator public key", "authentication", 0);
|
||||
log_info("Admin pubkey stored in config table for existing relay");
|
||||
}
|
||||
|
||||
if (relay_pubkey && strlen(relay_pubkey) == 64) {
|
||||
set_config_value_in_table("relay_pubkey", relay_pubkey, "string", "Relay public key", "relay", 0);
|
||||
log_info("Relay pubkey stored in config table for existing relay");
|
||||
}
|
||||
}
|
||||
cJSON_Delete(config_event);
|
||||
} else {
|
||||
|
||||
@@ -132,24 +132,11 @@ typedef struct {
|
||||
int time_tolerance_seconds;
|
||||
} nip42_challenge_manager_t;
|
||||
|
||||
// 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
|
||||
int nip42_challenge_timeout; // NIP-42 challenge timeout in seconds
|
||||
int nip42_time_tolerance; // NIP-42 time tolerance in seconds
|
||||
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};
|
||||
// No longer using local auth cache - using unified cache from config.c
|
||||
static nip42_challenge_manager_t g_challenge_manager = {0};
|
||||
static int g_validator_initialized = 0;
|
||||
|
||||
@@ -222,15 +209,15 @@ int ginxsom_request_validator_init(const char *db_path, const char *app_name) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Initialize NIP-42 challenge manager
|
||||
// Initialize NIP-42 challenge manager using unified config
|
||||
memset(&g_challenge_manager, 0, sizeof(g_challenge_manager));
|
||||
g_challenge_manager.timeout_seconds =
|
||||
g_auth_cache.nip42_challenge_timeout > 0
|
||||
? g_auth_cache.nip42_challenge_timeout
|
||||
: 600;
|
||||
g_challenge_manager.time_tolerance_seconds =
|
||||
g_auth_cache.nip42_time_tolerance > 0 ? g_auth_cache.nip42_time_tolerance
|
||||
: 300;
|
||||
|
||||
const char* nip42_timeout = get_config_value("nip42_challenge_timeout");
|
||||
g_challenge_manager.timeout_seconds = nip42_timeout ? atoi(nip42_timeout) : 600;
|
||||
|
||||
const char* nip42_tolerance = get_config_value("nip42_time_tolerance");
|
||||
g_challenge_manager.time_tolerance_seconds = nip42_tolerance ? atoi(nip42_tolerance) : 300;
|
||||
|
||||
g_challenge_manager.last_cleanup = time(NULL);
|
||||
|
||||
g_validator_initialized = 1;
|
||||
@@ -243,12 +230,15 @@ int ginxsom_request_validator_init(const char *db_path, const char *app_name) {
|
||||
* 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();
|
||||
// Use unified cache from config.c
|
||||
const char* auth_enabled = get_config_value("auth_enabled");
|
||||
if (auth_enabled && strcmp(auth_enabled, "true") == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return g_auth_cache.auth_required;
|
||||
|
||||
// Also check legacy key
|
||||
const char* auth_rules_enabled = get_config_value("auth_rules_enabled");
|
||||
return (auth_rules_enabled && strcmp(auth_rules_enabled, "true") == 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -306,14 +296,12 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
|
||||
int event_kind = (int)cJSON_GetNumberValue(kind);
|
||||
|
||||
// 5. Reload config if needed
|
||||
if (!g_auth_cache.cache_valid || time(NULL) > g_auth_cache.cache_expires) {
|
||||
reload_auth_config();
|
||||
}
|
||||
// 5. Check configuration using unified cache
|
||||
int auth_required = nostr_auth_rules_enabled();
|
||||
|
||||
char config_msg[256];
|
||||
sprintf(config_msg, "VALIDATOR_DEBUG: STEP 5 PASSED - Event kind: %d, auth_required: %d\n",
|
||||
event_kind, g_auth_cache.auth_required);
|
||||
event_kind, auth_required);
|
||||
validator_debug_log(config_msg);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@@ -352,7 +340,9 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
if (event_kind == 22242) {
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 8 - Processing NIP-42 challenge response\n");
|
||||
|
||||
if (g_auth_cache.nip42_mode == 0) {
|
||||
// Check NIP-42 mode using unified cache
|
||||
const char* nip42_enabled = get_config_value("nip42_auth_enabled");
|
||||
if (nip42_enabled && strcmp(nip42_enabled, "false") == 0) {
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 8 FAILED - NIP-42 is disabled\n");
|
||||
cJSON_Delete(event);
|
||||
return NOSTR_ERROR_NIP42_DISABLED;
|
||||
@@ -370,7 +360,7 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
// 9. Check if authentication rules are enabled
|
||||
if (!g_auth_cache.auth_required) {
|
||||
if (!auth_required) {
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 9 - Authentication disabled, skipping database auth rules\n");
|
||||
} else {
|
||||
// 10. Check database authentication rules (only if auth enabled)
|
||||
@@ -404,17 +394,23 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
// 11. NIP-13 Proof of Work validation
|
||||
if (g_pow_config.enabled && g_pow_config.min_pow_difficulty > 0) {
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
int pow_enabled = g_unified_cache.pow_config.enabled;
|
||||
int pow_min_difficulty = g_unified_cache.pow_config.min_pow_difficulty;
|
||||
int pow_validation_flags = g_unified_cache.pow_config.validation_flags;
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
if (pow_enabled && pow_min_difficulty > 0) {
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 11 - Validating NIP-13 Proof of Work\n");
|
||||
|
||||
nostr_pow_result_t pow_result;
|
||||
int pow_validation_result = nostr_validate_pow(event, g_pow_config.min_pow_difficulty,
|
||||
g_pow_config.validation_flags, &pow_result);
|
||||
int pow_validation_result = nostr_validate_pow(event, pow_min_difficulty,
|
||||
pow_validation_flags, &pow_result);
|
||||
|
||||
if (pow_validation_result != NOSTR_SUCCESS) {
|
||||
char pow_msg[256];
|
||||
sprintf(pow_msg, "VALIDATOR_DEBUG: STEP 11 FAILED - PoW validation failed (error=%d, difficulty=%d/%d)\n",
|
||||
pow_validation_result, pow_result.actual_difficulty, g_pow_config.min_pow_difficulty);
|
||||
pow_validation_result, pow_result.actual_difficulty, pow_min_difficulty);
|
||||
validator_debug_log(pow_msg);
|
||||
cJSON_Delete(event);
|
||||
return pow_validation_result;
|
||||
@@ -553,7 +549,6 @@ void nostr_request_validator_clear_violation(void) {
|
||||
*/
|
||||
void ginxsom_request_validator_cleanup(void) {
|
||||
g_validator_initialized = 0;
|
||||
memset(&g_auth_cache, 0, sizeof(g_auth_cache));
|
||||
nostr_request_validator_clear_violation();
|
||||
}
|
||||
|
||||
@@ -573,145 +568,22 @@ void nostr_request_result_free_file_data(nostr_request_result_t *result) {
|
||||
// HELPER FUNCTIONS
|
||||
//=============================================================================
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Force cache refresh - use unified cache system
|
||||
*/
|
||||
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");
|
||||
// Use unified cache refresh from config.c
|
||||
force_config_cache_refresh();
|
||||
validator_debug_log("VALIDATOR: Cache forcibly invalidated via unified cache\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload authentication configuration from unified config table
|
||||
* This function is no longer needed - configuration is handled by unified cache
|
||||
*/
|
||||
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 using global database path
|
||||
if (strlen(g_database_path) == 0) {
|
||||
validator_debug_log("VALIDATOR: No database path available\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
|
||||
int cache_timeout = get_cache_timeout();
|
||||
g_auth_cache.cache_expires = time(NULL) + cache_timeout;
|
||||
g_auth_cache.cache_valid = 1;
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
rc = sqlite3_open_v2(g_database_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
|
||||
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 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', "
|
||||
"'nip42_require_auth', 'nip42_challenge_timeout', "
|
||||
"'nip42_time_tolerance')";
|
||||
rc = sqlite3_prepare_v2(db, config_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, "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);
|
||||
} else if (strcmp(key, "nip42_require_auth") == 0) {
|
||||
if (strcmp(value, "false") == 0) {
|
||||
g_auth_cache.nip42_mode = 0; // Disabled
|
||||
} else if (strcmp(value, "required") == 0) {
|
||||
g_auth_cache.nip42_mode = 2; // Required
|
||||
} else if (strcmp(value, "true") == 0) {
|
||||
g_auth_cache.nip42_mode = 1; // Optional/Enabled
|
||||
} else {
|
||||
g_auth_cache.nip42_mode = 1; // Default to Optional/Enabled
|
||||
}
|
||||
} else if (strcmp(key, "nip42_challenge_timeout") == 0) {
|
||||
g_auth_cache.nip42_challenge_timeout = atoi(value);
|
||||
} else if (strcmp(key, "nip42_time_tolerance") == 0) {
|
||||
g_auth_cache.nip42_time_tolerance = atoi(value);
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
sqlite3_close(db);
|
||||
|
||||
// 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
|
||||
if (g_auth_cache.max_file_size == 0) {
|
||||
g_auth_cache.max_file_size = 104857600; // 100MB
|
||||
}
|
||||
|
||||
// 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);
|
||||
fprintf(stderr,
|
||||
"VALIDATOR: NIP-42 mode details - nip42_mode=%d (0=disabled, "
|
||||
"1=optional/enabled, 2=required)\n",
|
||||
g_auth_cache.nip42_mode);
|
||||
|
||||
// Configuration is now handled by the unified cache in config.c
|
||||
validator_debug_log("VALIDATOR: Using unified cache system for configuration\n");
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/* Embedded SQL Schema for C Nostr Relay
|
||||
* Generated from db/schema.sql - Do not edit manually
|
||||
* Schema Version: 6
|
||||
* Schema Version: 7
|
||||
*/
|
||||
#ifndef SQL_SCHEMA_H
|
||||
#define SQL_SCHEMA_H
|
||||
|
||||
/* Schema version constant */
|
||||
#define EMBEDDED_SCHEMA_VERSION "6"
|
||||
#define EMBEDDED_SCHEMA_VERSION "7"
|
||||
|
||||
/* Embedded SQL schema as C string literal */
|
||||
static const char* const EMBEDDED_SCHEMA_SQL =
|
||||
@@ -15,7 +15,7 @@ static const char* const EMBEDDED_SCHEMA_SQL =
|
||||
-- Event-based configuration system using kind 33334 Nostr events\n\
|
||||
\n\
|
||||
-- Schema version tracking\n\
|
||||
PRAGMA user_version = 6;\n\
|
||||
PRAGMA user_version = 7;\n\
|
||||
\n\
|
||||
-- Enable foreign key support\n\
|
||||
PRAGMA foreign_keys = ON;\n\
|
||||
@@ -58,8 +58,8 @@ CREATE TABLE schema_info (\n\
|
||||
\n\
|
||||
-- Insert schema metadata\n\
|
||||
INSERT INTO schema_info (key, value) VALUES\n\
|
||||
('version', '6'),\n\
|
||||
('description', 'Event-based Nostr relay schema with secure relay private key storage'),\n\
|
||||
('version', '7'),\n\
|
||||
('description', 'Hybrid Nostr relay schema with event-based and table-based configuration'),\n\
|
||||
('created_at', strftime('%s', 'now'));\n\
|
||||
\n\
|
||||
-- Helper views for common queries\n\
|
||||
@@ -154,6 +154,60 @@ CREATE INDEX idx_auth_rules_pattern ON auth_rules(pattern_type, pattern_value);\
|
||||
CREATE INDEX idx_auth_rules_type ON auth_rules(rule_type);\n\
|
||||
CREATE INDEX idx_auth_rules_active ON auth_rules(active);\n\
|
||||
\n\
|
||||
-- Configuration Table for Table-Based Config Management\n\
|
||||
-- Hybrid system supporting both event-based and table-based configuration\n\
|
||||
CREATE TABLE config (\n\
|
||||
key TEXT PRIMARY KEY,\n\
|
||||
value TEXT NOT NULL,\n\
|
||||
data_type TEXT NOT NULL CHECK (data_type IN ('string', 'integer', 'boolean', 'json')),\n\
|
||||
description TEXT,\n\
|
||||
category TEXT DEFAULT 'general',\n\
|
||||
requires_restart INTEGER DEFAULT 0,\n\
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),\n\
|
||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))\n\
|
||||
);\n\
|
||||
\n\
|
||||
-- Indexes for config table performance\n\
|
||||
CREATE INDEX idx_config_category ON config(category);\n\
|
||||
CREATE INDEX idx_config_restart ON config(requires_restart);\n\
|
||||
CREATE INDEX idx_config_updated ON config(updated_at DESC);\n\
|
||||
\n\
|
||||
-- Trigger to update config timestamp on changes\n\
|
||||
CREATE TRIGGER update_config_timestamp\n\
|
||||
AFTER UPDATE ON config\n\
|
||||
FOR EACH ROW\n\
|
||||
BEGIN\n\
|
||||
UPDATE config SET updated_at = strftime('%s', 'now') WHERE key = NEW.key;\n\
|
||||
END;\n\
|
||||
\n\
|
||||
-- Insert default configuration values\n\
|
||||
INSERT INTO config (key, value, data_type, description, category, requires_restart) VALUES\n\
|
||||
('relay_description', 'A C Nostr Relay', 'string', 'Relay description', 'general', 0),\n\
|
||||
('relay_contact', '', 'string', 'Relay contact information', 'general', 0),\n\
|
||||
('relay_software', 'https://github.com/laanwj/c-relay', 'string', 'Relay software URL', 'general', 0),\n\
|
||||
('relay_version', '1.0.0', 'string', 'Relay version', 'general', 0),\n\
|
||||
('relay_port', '8888', 'integer', 'Relay port number', 'network', 1),\n\
|
||||
('max_connections', '1000', 'integer', 'Maximum concurrent connections', 'network', 1),\n\
|
||||
('auth_enabled', 'false', 'boolean', 'Enable NIP-42 authentication', 'auth', 0),\n\
|
||||
('nip42_auth_required_events', 'false', 'boolean', 'Require auth for event publishing', 'auth', 0),\n\
|
||||
('nip42_auth_required_subscriptions', 'false', 'boolean', 'Require auth for subscriptions', 'auth', 0),\n\
|
||||
('nip42_auth_required_kinds', '[]', 'json', 'Event kinds requiring authentication', 'auth', 0),\n\
|
||||
('nip42_challenge_expiration', '600', 'integer', 'Auth challenge expiration seconds', 'auth', 0),\n\
|
||||
('pow_min_difficulty', '0', 'integer', 'Minimum proof-of-work difficulty', 'validation', 0),\n\
|
||||
('pow_mode', 'optional', 'string', 'Proof-of-work mode', 'validation', 0),\n\
|
||||
('nip40_expiration_enabled', 'true', 'boolean', 'Enable event expiration', 'validation', 0),\n\
|
||||
('nip40_expiration_strict', 'false', 'boolean', 'Strict expiration mode', 'validation', 0),\n\
|
||||
('nip40_expiration_filter', 'true', 'boolean', 'Filter expired events in queries', 'validation', 0),\n\
|
||||
('nip40_expiration_grace_period', '60', 'integer', 'Expiration grace period seconds', 'validation', 0),\n\
|
||||
('max_subscriptions_per_client', '25', 'integer', 'Maximum subscriptions per client', 'limits', 0),\n\
|
||||
('max_total_subscriptions', '1000', 'integer', 'Maximum total subscriptions', 'limits', 0),\n\
|
||||
('max_filters_per_subscription', '10', 'integer', 'Maximum filters per subscription', 'limits', 0),\n\
|
||||
('max_event_tags', '2000', 'integer', 'Maximum tags per event', 'limits', 0),\n\
|
||||
('max_content_length', '100000', 'integer', 'Maximum event content length', 'limits', 0),\n\
|
||||
('max_message_length', '131072', 'integer', 'Maximum WebSocket message length', 'limits', 0),\n\
|
||||
('default_limit', '100', 'integer', 'Default query limit', 'limits', 0),\n\
|
||||
('max_limit', '5000', 'integer', 'Maximum query limit', 'limits', 0);\n\
|
||||
\n\
|
||||
-- Persistent Subscriptions Logging Tables (Phase 2)\n\
|
||||
-- Optional database logging for subscription analytics and debugging\n\
|
||||
\n\
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"kind":1,"id":"6ed088c045874d91eabd02127d613e8babf6240a10532eb25f4c61437cabe710","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1757711333,"tags":[],"content":"Testing unified validation system","sig":"9f96975a831317d9948a097a9c4ae73063f4f0414a463b37a21e733f16d7788a51e72e8e48144974d82c217c31c45b987589219a5d5e2f8d7ec81448b523a474"}
|
||||
@@ -1 +0,0 @@
|
||||
5e01b634b759df55fe19be40e8ce632fe0717506c5bc0e0558a4d7aed2232380
|
||||
Reference in New Issue
Block a user