v0.3.8 - safety push
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
* Two-file architecture:
|
* Two-file architecture:
|
||||||
* 1. Load nostr.bundle.js (official nostr-tools bundle)
|
* 1. Load nostr.bundle.js (official nostr-tools bundle)
|
||||||
* 2. Load nostr-lite.js (this file - NOSTR_LOGIN_LITE library with CSS-only themes)
|
* 2. Load nostr-lite.js (this file - NOSTR_LOGIN_LITE library with CSS-only themes)
|
||||||
* Generated on: 2025-09-16T15:52:30.145Z
|
* Generated on: 2025-09-16T22:12:00.192Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Verify dependencies are loaded
|
// Verify dependencies are loaded
|
||||||
@@ -20,509 +20,10 @@ if (typeof window !== 'undefined') {
|
|||||||
|
|
||||||
console.log('NOSTR_LOGIN_LITE: Dependencies verified ✓');
|
console.log('NOSTR_LOGIN_LITE: Dependencies verified ✓');
|
||||||
console.log('NOSTR_LOGIN_LITE: NostrTools available with keys:', Object.keys(window.NostrTools));
|
console.log('NOSTR_LOGIN_LITE: NostrTools available with keys:', Object.keys(window.NostrTools));
|
||||||
|
console.log('NOSTR_LOGIN_LITE: NIP-06 available:', !!window.NostrTools.nip06);
|
||||||
console.log('NOSTR_LOGIN_LITE: NIP-46 available:', !!window.NostrTools.nip46);
|
console.log('NOSTR_LOGIN_LITE: NIP-46 available:', !!window.NostrTools.nip46);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== NIP-46 Extension Integration =====
|
|
||||||
// Add NIP-46 functionality to NostrTools if not already present
|
|
||||||
if (typeof window.NostrTools !== 'undefined' && !window.NostrTools.nip46) {
|
|
||||||
console.log('NOSTR_LOGIN_LITE: Adding NIP-46 extension to NostrTools');
|
|
||||||
|
|
||||||
const { nip44, generateSecretKey, getPublicKey, finalizeEvent, verifyEvent, utils } = window.NostrTools;
|
|
||||||
|
|
||||||
// NIP-05 regex for parsing
|
|
||||||
const NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w_-]+(.[\w_-]+)+)$/;
|
|
||||||
const BUNKER_REGEX = /^bunker:\/\/([0-9a-f]{64})\??([?\/\w:.=&%-]*)$/;
|
|
||||||
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
||||||
|
|
||||||
// Event kinds
|
|
||||||
const NostrConnect = 24133;
|
|
||||||
const ClientAuth = 22242;
|
|
||||||
const Handlerinformation = 31990;
|
|
||||||
|
|
||||||
// Fetch implementation
|
|
||||||
let _fetch;
|
|
||||||
try {
|
|
||||||
_fetch = fetch;
|
|
||||||
} catch {
|
|
||||||
_fetch = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function useFetchImplementation(fetchImplementation) {
|
|
||||||
_fetch = fetchImplementation;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple Pool implementation for NIP-46
|
|
||||||
class SimplePool {
|
|
||||||
constructor() {
|
|
||||||
this.relays = new Map();
|
|
||||||
this.subscriptions = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
async ensureRelay(url) {
|
|
||||||
if (!this.relays.has(url)) {
|
|
||||||
console.log(`NIP-46: Connecting to relay ${url}`);
|
|
||||||
const ws = new WebSocket(url);
|
|
||||||
const relay = {
|
|
||||||
ws,
|
|
||||||
connected: false,
|
|
||||||
subscriptions: new Map()
|
|
||||||
};
|
|
||||||
|
|
||||||
this.relays.set(url, relay);
|
|
||||||
|
|
||||||
// Wait for connection with proper event handlers
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
console.error(`NIP-46: Connection timeout for ${url}`);
|
|
||||||
reject(new Error(`Connection timeout to ${url}`));
|
|
||||||
}, 10000); // 10 second timeout
|
|
||||||
|
|
||||||
ws.onopen = () => {
|
|
||||||
console.log(`NIP-46: Successfully connected to relay ${url}, WebSocket state: ${ws.readyState}`);
|
|
||||||
relay.connected = true;
|
|
||||||
clearTimeout(timeout);
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onerror = (error) => {
|
|
||||||
console.error(`NIP-46: Failed to connect to ${url}:`, error);
|
|
||||||
clearTimeout(timeout);
|
|
||||||
reject(new Error(`Failed to connect to ${url}: ${error.message || 'Connection failed'}`));
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onclose = (event) => {
|
|
||||||
console.log(`NIP-46: Disconnected from relay ${url}:`, event.code, event.reason);
|
|
||||||
relay.connected = false;
|
|
||||||
if (this.relays.has(url)) {
|
|
||||||
this.relays.delete(url);
|
|
||||||
}
|
|
||||||
clearTimeout(timeout);
|
|
||||||
reject(new Error(`Connection closed during setup: ${event.reason || 'Unknown reason'}`));
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const relay = this.relays.get(url);
|
|
||||||
// Verify the existing connection is still open
|
|
||||||
if (!relay.connected || relay.ws.readyState !== WebSocket.OPEN) {
|
|
||||||
console.log(`NIP-46: Reconnecting to relay ${url}`);
|
|
||||||
this.relays.delete(url);
|
|
||||||
return await this.ensureRelay(url); // Recursively reconnect
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const relay = this.relays.get(url);
|
|
||||||
console.log(`NIP-46: Relay ${url} ready, WebSocket state: ${relay.ws.readyState}`);
|
|
||||||
return relay;
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe(relays, filters, params = {}) {
|
|
||||||
const subId = Math.random().toString(36).substring(7);
|
|
||||||
|
|
||||||
relays.forEach(async (url) => {
|
|
||||||
try {
|
|
||||||
const relay = await this.ensureRelay(url);
|
|
||||||
|
|
||||||
relay.ws.onmessage = (event) => {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(event.data);
|
|
||||||
if (data[0] === 'EVENT' && data[1] === subId) {
|
|
||||||
params.onevent?.(data[2]);
|
|
||||||
} else if (data[0] === 'EOSE' && data[1] === subId) {
|
|
||||||
params.oneose?.();
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('Failed to parse message:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ensure filters is an array
|
|
||||||
const filtersArray = Array.isArray(filters) ? filters : [filters];
|
|
||||||
const reqMsg = JSON.stringify(['REQ', subId, ...filtersArray]);
|
|
||||||
relay.ws.send(reqMsg);
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('Failed to connect to relay:', url, err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
close: () => {
|
|
||||||
relays.forEach(async (url) => {
|
|
||||||
const relay = this.relays.get(url);
|
|
||||||
if (relay?.connected) {
|
|
||||||
relay.ws.send(JSON.stringify(['CLOSE', subId]));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async publish(relays, event) {
|
|
||||||
console.log(`NIP-46: Publishing event to ${relays.length} relays:`, event);
|
|
||||||
|
|
||||||
const promises = relays.map(async (url) => {
|
|
||||||
try {
|
|
||||||
console.log(`NIP-46: Attempting to publish to ${url}`);
|
|
||||||
const relay = await this.ensureRelay(url);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
console.error(`NIP-46: Publish timeout to ${url}`);
|
|
||||||
reject(new Error(`Publish timeout to ${url}`));
|
|
||||||
}, 10000); // Increased timeout to 10 seconds
|
|
||||||
|
|
||||||
// Set up message handler for this specific event
|
|
||||||
const messageHandler = (msg) => {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(msg.data);
|
|
||||||
if (data[0] === 'OK' && data[1] === event.id) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
relay.ws.removeEventListener('message', messageHandler);
|
|
||||||
if (data[2]) {
|
|
||||||
console.log(`NIP-46: Publish success to ${url}:`, data[3]);
|
|
||||||
resolve(data[3]);
|
|
||||||
} else {
|
|
||||||
console.error(`NIP-46: Publish rejected by ${url}:`, data[3]);
|
|
||||||
reject(new Error(`Publish rejected: ${data[3]}`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`NIP-46: Error parsing message from ${url}:`, err);
|
|
||||||
clearTimeout(timeout);
|
|
||||||
relay.ws.removeEventListener('message', messageHandler);
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
relay.ws.addEventListener('message', messageHandler);
|
|
||||||
|
|
||||||
// Double-check WebSocket state before sending
|
|
||||||
console.log(`NIP-46: About to publish to ${url}, WebSocket state: ${relay.ws.readyState} (0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED)`);
|
|
||||||
if (relay.ws.readyState === WebSocket.OPEN) {
|
|
||||||
console.log(`NIP-46: Sending event to ${url}`);
|
|
||||||
relay.ws.send(JSON.stringify(['EVENT', event]));
|
|
||||||
} else {
|
|
||||||
console.error(`NIP-46: WebSocket not ready for ${url}, state: ${relay.ws.readyState}`);
|
|
||||||
clearTimeout(timeout);
|
|
||||||
relay.ws.removeEventListener('message', messageHandler);
|
|
||||||
reject(new Error(`WebSocket not ready for ${url}, state: ${relay.ws.readyState}`));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`NIP-46: Failed to publish to ${url}:`, err);
|
|
||||||
return Promise.reject(new Error(`Failed to publish to ${url}: ${err.message}`));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const results = await Promise.allSettled(promises);
|
|
||||||
console.log(`NIP-46: Publish results:`, results);
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
async querySync(relays, filter, params = {}) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const events = [];
|
|
||||||
this.subscribe(relays, [filter], {
|
|
||||||
...params,
|
|
||||||
onevent: (event) => events.push(event),
|
|
||||||
oneose: () => resolve(events)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bunker URL utilities
|
|
||||||
function toBunkerURL(bunkerPointer) {
|
|
||||||
let bunkerURL = new URL(`bunker://${bunkerPointer.pubkey}`);
|
|
||||||
bunkerPointer.relays.forEach((relay) => {
|
|
||||||
bunkerURL.searchParams.append('relay', relay);
|
|
||||||
});
|
|
||||||
if (bunkerPointer.secret) {
|
|
||||||
bunkerURL.searchParams.set('secret', bunkerPointer.secret);
|
|
||||||
}
|
|
||||||
return bunkerURL.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function parseBunkerInput(input) {
|
|
||||||
let match = input.match(BUNKER_REGEX);
|
|
||||||
if (match) {
|
|
||||||
try {
|
|
||||||
const pubkey = match[1];
|
|
||||||
const qs = new URLSearchParams(match[2]);
|
|
||||||
return {
|
|
||||||
pubkey,
|
|
||||||
relays: qs.getAll('relay'),
|
|
||||||
secret: qs.get('secret')
|
|
||||||
};
|
|
||||||
} catch (_err) {
|
|
||||||
// Continue to NIP-05 parsing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return queryBunkerProfile(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function queryBunkerProfile(nip05) {
|
|
||||||
if (!_fetch) {
|
|
||||||
throw new Error('Fetch implementation not available');
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = nip05.match(NIP05_REGEX);
|
|
||||||
if (!match) return null;
|
|
||||||
|
|
||||||
const [_, name = '_', domain] = match;
|
|
||||||
try {
|
|
||||||
const url = `https://${domain}/.well-known/nostr.json?name=${name}`;
|
|
||||||
const res = await (await _fetch(url, { redirect: 'error' })).json();
|
|
||||||
let pubkey = res.names[name];
|
|
||||||
let relays = res.nip46[pubkey] || [];
|
|
||||||
return { pubkey, relays, secret: null };
|
|
||||||
} catch (_err) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BunkerSigner class
|
|
||||||
class BunkerSigner {
|
|
||||||
constructor(clientSecretKey, bp, params = {}) {
|
|
||||||
if (bp.relays.length === 0) {
|
|
||||||
throw new Error('no relays are specified for this bunker');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.params = params;
|
|
||||||
this.pool = params.pool || new SimplePool();
|
|
||||||
this.secretKey = clientSecretKey;
|
|
||||||
this.conversationKey = nip44.getConversationKey(clientSecretKey, bp.pubkey);
|
|
||||||
this.bp = bp;
|
|
||||||
this.isOpen = false;
|
|
||||||
this.idPrefix = Math.random().toString(36).substring(7);
|
|
||||||
this.serial = 0;
|
|
||||||
this.listeners = {};
|
|
||||||
this.waitingForAuth = {};
|
|
||||||
this.ready = false;
|
|
||||||
this.readyPromise = this.setupSubscription(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setupSubscription(params) {
|
|
||||||
console.log('NIP-46: Setting up subscription to relays:', this.bp.relays);
|
|
||||||
const listeners = this.listeners;
|
|
||||||
const waitingForAuth = this.waitingForAuth;
|
|
||||||
const convKey = this.conversationKey;
|
|
||||||
|
|
||||||
// Ensure all relays are connected first
|
|
||||||
await Promise.all(this.bp.relays.map(url => this.pool.ensureRelay(url)));
|
|
||||||
console.log('NIP-46: All relays connected, setting up subscription');
|
|
||||||
|
|
||||||
this.subCloser = this.pool.subscribe(
|
|
||||||
this.bp.relays,
|
|
||||||
[{ kinds: [NostrConnect], authors: [this.bp.pubkey], '#p': [getPublicKey(this.secretKey)] }],
|
|
||||||
{
|
|
||||||
onevent: async (event) => {
|
|
||||||
const o = JSON.parse(nip44.decrypt(event.content, convKey));
|
|
||||||
const { id, result, error } = o;
|
|
||||||
|
|
||||||
if (result === 'auth_url' && waitingForAuth[id]) {
|
|
||||||
delete waitingForAuth[id];
|
|
||||||
if (params.onauth) {
|
|
||||||
params.onauth(error);
|
|
||||||
} else {
|
|
||||||
console.warn(
|
|
||||||
`NIP-46: remote signer ${this.bp.pubkey} tried to send an "auth_url"='${error}' but there was no onauth() callback configured.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let handler = listeners[id];
|
|
||||||
if (handler) {
|
|
||||||
if (error) handler.reject(error);
|
|
||||||
else if (result) handler.resolve(result);
|
|
||||||
delete listeners[id];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onclose: () => {
|
|
||||||
this.subCloser = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.isOpen = true;
|
|
||||||
this.ready = true;
|
|
||||||
console.log('NIP-46: BunkerSigner setup complete and ready');
|
|
||||||
}
|
|
||||||
|
|
||||||
async ensureReady() {
|
|
||||||
if (!this.ready) {
|
|
||||||
console.log('NIP-46: Waiting for BunkerSigner to be ready...');
|
|
||||||
await this.readyPromise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async close() {
|
|
||||||
this.isOpen = false;
|
|
||||||
this.subCloser?.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendRequest(method, params) {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
try {
|
|
||||||
await this.ensureReady(); // Wait for BunkerSigner to be ready
|
|
||||||
|
|
||||||
if (!this.isOpen) {
|
|
||||||
throw new Error('this signer is not open anymore, create a new one');
|
|
||||||
}
|
|
||||||
if (!this.subCloser) {
|
|
||||||
await this.setupSubscription(this.params);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.serial++;
|
|
||||||
const id = `${this.idPrefix}-${this.serial}`;
|
|
||||||
const encryptedContent = nip44.encrypt(JSON.stringify({ id, method, params }), this.conversationKey);
|
|
||||||
|
|
||||||
const verifiedEvent = finalizeEvent(
|
|
||||||
{
|
|
||||||
kind: NostrConnect,
|
|
||||||
tags: [['p', this.bp.pubkey]],
|
|
||||||
content: encryptedContent,
|
|
||||||
created_at: Math.floor(Date.now() / 1000)
|
|
||||||
},
|
|
||||||
this.secretKey
|
|
||||||
);
|
|
||||||
|
|
||||||
this.listeners[id] = { resolve, reject };
|
|
||||||
this.waitingForAuth[id] = true;
|
|
||||||
|
|
||||||
console.log(`NIP-46: Sending ${method} request with id ${id}`);
|
|
||||||
const publishResults = await this.pool.publish(this.bp.relays, verifiedEvent);
|
|
||||||
// Check if at least one publish succeeded
|
|
||||||
const hasSuccess = publishResults.some(result => result.status === 'fulfilled');
|
|
||||||
if (!hasSuccess) {
|
|
||||||
throw new Error('Failed to publish to any relay');
|
|
||||||
}
|
|
||||||
console.log(`NIP-46: ${method} request sent successfully`);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`NIP-46: sendRequest ${method} failed:`, err);
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async ping() {
|
|
||||||
let resp = await this.sendRequest('ping', []);
|
|
||||||
if (resp !== 'pong') {
|
|
||||||
throw new Error(`result is not pong: ${resp}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async connect() {
|
|
||||||
await this.sendRequest('connect', [this.bp.pubkey, this.bp.secret || '']);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPublicKey() {
|
|
||||||
if (!this.cachedPubKey) {
|
|
||||||
this.cachedPubKey = await this.sendRequest('get_public_key', []);
|
|
||||||
}
|
|
||||||
return this.cachedPubKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
async signEvent(event) {
|
|
||||||
let resp = await this.sendRequest('sign_event', [JSON.stringify(event)]);
|
|
||||||
let signed = JSON.parse(resp);
|
|
||||||
if (verifyEvent(signed)) {
|
|
||||||
return signed;
|
|
||||||
} else {
|
|
||||||
throw new Error(`event returned from bunker is improperly signed: ${JSON.stringify(signed)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async nip04Encrypt(thirdPartyPubkey, plaintext) {
|
|
||||||
return await this.sendRequest('nip04_encrypt', [thirdPartyPubkey, plaintext]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async nip04Decrypt(thirdPartyPubkey, ciphertext) {
|
|
||||||
return await this.sendRequest('nip04_decrypt', [thirdPartyPubkey, ciphertext]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async nip44Encrypt(thirdPartyPubkey, plaintext) {
|
|
||||||
return await this.sendRequest('nip44_encrypt', [thirdPartyPubkey, plaintext]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async nip44Decrypt(thirdPartyPubkey, ciphertext) {
|
|
||||||
return await this.sendRequest('nip44_decrypt', [thirdPartyPubkey, ciphertext]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createAccount(bunker, params, username, domain, email, localSecretKey = generateSecretKey()) {
|
|
||||||
if (email && !EMAIL_REGEX.test(email)) {
|
|
||||||
throw new Error('Invalid email');
|
|
||||||
}
|
|
||||||
|
|
||||||
let rpc = new BunkerSigner(localSecretKey, bunker.bunkerPointer, params);
|
|
||||||
let pubkey = await rpc.sendRequest('create_account', [username, domain, email || '']);
|
|
||||||
rpc.bp.pubkey = pubkey;
|
|
||||||
await rpc.connect();
|
|
||||||
return rpc;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchBunkerProviders(pool, relays) {
|
|
||||||
const events = await pool.querySync(relays, {
|
|
||||||
kinds: [Handlerinformation],
|
|
||||||
'#k': [NostrConnect.toString()]
|
|
||||||
});
|
|
||||||
|
|
||||||
events.sort((a, b) => b.created_at - a.created_at);
|
|
||||||
|
|
||||||
const validatedBunkers = await Promise.all(
|
|
||||||
events.map(async (event, i) => {
|
|
||||||
try {
|
|
||||||
const content = JSON.parse(event.content);
|
|
||||||
try {
|
|
||||||
if (events.findIndex((ev) => JSON.parse(ev.content).nip05 === content.nip05) !== i) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// Continue processing
|
|
||||||
}
|
|
||||||
|
|
||||||
const bp = await queryBunkerProfile(content.nip05);
|
|
||||||
if (bp && bp.pubkey === event.pubkey && bp.relays.length) {
|
|
||||||
return {
|
|
||||||
bunkerPointer: bp,
|
|
||||||
nip05: content.nip05,
|
|
||||||
domain: content.nip05.split('@')[1],
|
|
||||||
name: content.name || content.display_name,
|
|
||||||
picture: content.picture,
|
|
||||||
about: content.about,
|
|
||||||
website: content.website,
|
|
||||||
local: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return validatedBunkers.filter((b) => b !== undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extend NostrTools with NIP-46 functionality
|
|
||||||
window.NostrTools.nip46 = {
|
|
||||||
BunkerSigner,
|
|
||||||
parseBunkerInput,
|
|
||||||
toBunkerURL,
|
|
||||||
queryBunkerProfile,
|
|
||||||
createAccount,
|
|
||||||
fetchBunkerProviders,
|
|
||||||
useFetchImplementation,
|
|
||||||
BUNKER_REGEX,
|
|
||||||
SimplePool
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('NIP-46 extension loaded successfully');
|
|
||||||
console.log('Available: NostrTools.nip46');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================================
|
// ======================================
|
||||||
// NOSTR_LOGIN_LITE Components
|
// NOSTR_LOGIN_LITE Components
|
||||||
// ======================================
|
// ======================================
|
||||||
@@ -854,7 +355,7 @@ class Modal {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
// Modal content: centered with margin
|
// Modal content: centered with margin, no fixed height
|
||||||
modalContent.style.cssText = `
|
modalContent.style.cssText = `
|
||||||
position: relative;
|
position: relative;
|
||||||
background: var(--nl-secondary-color);
|
background: var(--nl-secondary-color);
|
||||||
@@ -864,7 +365,6 @@ class Modal {
|
|||||||
margin: 50px auto;
|
margin: 50px auto;
|
||||||
border-radius: var(--nl-border-radius, 15px);
|
border-radius: var(--nl-border-radius, 15px);
|
||||||
border: var(--nl-border-width) solid var(--nl-primary-color);
|
border: var(--nl-border-width) solid var(--nl-primary-color);
|
||||||
max-height: 600px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -929,8 +429,6 @@ class Modal {
|
|||||||
this.modalBody = document.createElement('div');
|
this.modalBody = document.createElement('div');
|
||||||
this.modalBody.style.cssText = `
|
this.modalBody.style.cssText = `
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
overflow-y: auto;
|
|
||||||
max-height: 500px;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
font-family: var(--nl-font-family, 'Courier New', monospace);
|
font-family: var(--nl-font-family, 'Courier New', monospace);
|
||||||
`;
|
`;
|
||||||
@@ -1019,6 +517,16 @@ class Modal {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Seed Phrase option - only show if explicitly enabled
|
||||||
|
if (this.options?.methods?.seedphrase === true) {
|
||||||
|
options.push({
|
||||||
|
type: 'seedphrase',
|
||||||
|
title: 'Seed Phrase',
|
||||||
|
description: 'Import from mnemonic seed phrase',
|
||||||
|
icon: '🌱'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Nostr Connect option (check both 'connect' and 'remote' for compatibility)
|
// Nostr Connect option (check both 'connect' and 'remote' for compatibility)
|
||||||
if (this.options?.methods?.connect !== false && this.options?.methods?.remote !== false) {
|
if (this.options?.methods?.connect !== false && this.options?.methods?.remote !== false) {
|
||||||
options.push({
|
options.push({
|
||||||
@@ -1076,6 +584,27 @@ class Modal {
|
|||||||
button.style.background = 'var(--nl-secondary-color)';
|
button.style.background = 'var(--nl-secondary-color)';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const iconDiv = document.createElement('div');
|
||||||
|
// Replace emoji icons with text-based ones
|
||||||
|
const iconMap = {
|
||||||
|
'🔌': '[EXT]',
|
||||||
|
'🔑': '[KEY]',
|
||||||
|
'🌱': '[SEED]',
|
||||||
|
'🌐': '[NET]',
|
||||||
|
'👁️': '[VIEW]',
|
||||||
|
'📱': '[SMS]'
|
||||||
|
};
|
||||||
|
iconDiv.textContent = iconMap[option.icon] || option.icon;
|
||||||
|
iconDiv.style.cssText = `
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 16px;
|
||||||
|
width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--nl-primary-color);
|
||||||
|
font-family: var(--nl-font-family, 'Courier New', monospace);
|
||||||
|
`;
|
||||||
|
|
||||||
const contentDiv = document.createElement('div');
|
const contentDiv = document.createElement('div');
|
||||||
contentDiv.style.cssText = 'flex: 1; text-align: left;';
|
contentDiv.style.cssText = 'flex: 1; text-align: left;';
|
||||||
|
|
||||||
@@ -1099,6 +628,7 @@ class Modal {
|
|||||||
contentDiv.appendChild(titleDiv);
|
contentDiv.appendChild(titleDiv);
|
||||||
contentDiv.appendChild(descDiv);
|
contentDiv.appendChild(descDiv);
|
||||||
|
|
||||||
|
button.appendChild(iconDiv);
|
||||||
button.appendChild(contentDiv);
|
button.appendChild(contentDiv);
|
||||||
this.modalBody.appendChild(button);
|
this.modalBody.appendChild(button);
|
||||||
});
|
});
|
||||||
@@ -1115,6 +645,9 @@ class Modal {
|
|||||||
case 'local':
|
case 'local':
|
||||||
this._showLocalKeyScreen();
|
this._showLocalKeyScreen();
|
||||||
break;
|
break;
|
||||||
|
case 'seedphrase':
|
||||||
|
this._showSeedPhraseScreen();
|
||||||
|
break;
|
||||||
case 'connect':
|
case 'connect':
|
||||||
this._showConnectScreen();
|
this._showConnectScreen();
|
||||||
break;
|
break;
|
||||||
@@ -2159,6 +1692,287 @@ class Modal {
|
|||||||
this._setAuthMethod('readonly');
|
this._setAuthMethod('readonly');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_showSeedPhraseScreen() {
|
||||||
|
this.modalBody.innerHTML = '';
|
||||||
|
|
||||||
|
const title = document.createElement('h3');
|
||||||
|
title.textContent = 'Import from Seed Phrase';
|
||||||
|
title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
|
||||||
|
|
||||||
|
const description = document.createElement('p');
|
||||||
|
description.textContent = 'Enter your 12 or 24-word mnemonic seed phrase to derive Nostr accounts:';
|
||||||
|
description.style.cssText = 'margin-bottom: 12px; color: #6b7280; font-size: 14px;';
|
||||||
|
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
// Remove default placeholder text as requested
|
||||||
|
textarea.placeholder = '';
|
||||||
|
textarea.style.cssText = `
|
||||||
|
width: 100%;
|
||||||
|
height: 100px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
resize: none;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add real-time mnemonic validation
|
||||||
|
const formatHint = document.createElement('div');
|
||||||
|
formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;';
|
||||||
|
|
||||||
|
textarea.oninput = () => {
|
||||||
|
const value = textarea.value.trim();
|
||||||
|
if (!value) {
|
||||||
|
formatHint.textContent = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValid = this._validateMnemonic(value);
|
||||||
|
if (isValid) {
|
||||||
|
const wordCount = value.split(/\s+/).length;
|
||||||
|
formatHint.textContent = `✅ Valid ${wordCount}-word mnemonic detected`;
|
||||||
|
formatHint.style.color = '#059669';
|
||||||
|
} else {
|
||||||
|
formatHint.textContent = '❌ Invalid mnemonic - must be 12 or 24 valid BIP-39 words';
|
||||||
|
formatHint.style.color = '#dc2626';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate new seed phrase button
|
||||||
|
const generateButton = document.createElement('button');
|
||||||
|
generateButton.textContent = 'Generate New Seed Phrase';
|
||||||
|
generateButton.onclick = () => this._generateNewSeedPhrase(textarea, formatHint);
|
||||||
|
generateButton.style.cssText = this._getButtonStyle() + 'margin-bottom: 12px;';
|
||||||
|
|
||||||
|
const importButton = document.createElement('button');
|
||||||
|
importButton.textContent = 'Import Accounts';
|
||||||
|
importButton.onclick = () => this._importFromSeedPhrase(textarea.value);
|
||||||
|
importButton.style.cssText = this._getButtonStyle();
|
||||||
|
|
||||||
|
const backButton = document.createElement('button');
|
||||||
|
backButton.textContent = 'Back';
|
||||||
|
backButton.onclick = () => this._renderLoginOptions();
|
||||||
|
backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;';
|
||||||
|
|
||||||
|
this.modalBody.appendChild(title);
|
||||||
|
this.modalBody.appendChild(description);
|
||||||
|
this.modalBody.appendChild(textarea);
|
||||||
|
this.modalBody.appendChild(formatHint);
|
||||||
|
this.modalBody.appendChild(generateButton);
|
||||||
|
this.modalBody.appendChild(importButton);
|
||||||
|
this.modalBody.appendChild(backButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
_generateNewSeedPhrase(textarea, formatHint) {
|
||||||
|
try {
|
||||||
|
// Check if NIP-06 is available
|
||||||
|
if (!window.NostrTools?.nip06) {
|
||||||
|
throw new Error('NIP-06 not available in bundle');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a random 12-word mnemonic using NostrTools
|
||||||
|
const mnemonic = window.NostrTools.nip06.generateSeedWords();
|
||||||
|
|
||||||
|
// Set the generated mnemonic in the textarea
|
||||||
|
textarea.value = mnemonic;
|
||||||
|
|
||||||
|
// Trigger validation to show it's valid
|
||||||
|
const wordCount = mnemonic.split(/\s+/).length;
|
||||||
|
formatHint.textContent = `✅ Generated valid ${wordCount}-word mnemonic`;
|
||||||
|
formatHint.style.color = '#059669';
|
||||||
|
|
||||||
|
console.log('Generated new seed phrase:', wordCount, 'words');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to generate seed phrase:', error);
|
||||||
|
formatHint.textContent = '❌ Failed to generate seed phrase - NIP-06 not available';
|
||||||
|
formatHint.style.color = '#dc2626';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_validateMnemonic(mnemonic) {
|
||||||
|
try {
|
||||||
|
// Check if NIP-06 is available
|
||||||
|
if (!window.NostrTools?.nip06) {
|
||||||
|
console.error('NIP-06 not available in bundle');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const words = mnemonic.trim().split(/\s+/);
|
||||||
|
|
||||||
|
// Must be 12 or 24 words
|
||||||
|
if (words.length !== 12 && words.length !== 24) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to validate using NostrTools nip06 - this will throw if invalid
|
||||||
|
window.NostrTools.nip06.privateKeyFromSeedWords(mnemonic, '', 0);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Mnemonic validation failed:', error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_importFromSeedPhrase(mnemonic) {
|
||||||
|
try {
|
||||||
|
const trimmed = mnemonic.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
throw new Error('Please enter a mnemonic seed phrase');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the mnemonic
|
||||||
|
if (!this._validateMnemonic(trimmed)) {
|
||||||
|
throw new Error('Invalid mnemonic. Please enter a valid 12 or 24-word BIP-39 seed phrase');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate accounts 0-5 using NIP-06
|
||||||
|
const accounts = [];
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
try {
|
||||||
|
const privateKey = window.NostrTools.nip06.privateKeyFromSeedWords(trimmed, '', i);
|
||||||
|
const publicKey = window.NostrTools.getPublicKey(privateKey);
|
||||||
|
const nsec = window.NostrTools.nip19.nsecEncode(privateKey);
|
||||||
|
const npub = window.NostrTools.nip19.npubEncode(publicKey);
|
||||||
|
|
||||||
|
accounts.push({
|
||||||
|
index: i,
|
||||||
|
privateKey,
|
||||||
|
publicKey,
|
||||||
|
nsec,
|
||||||
|
npub
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to derive account ${i}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accounts.length === 0) {
|
||||||
|
throw new Error('Failed to derive any accounts from seed phrase');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Successfully derived ${accounts.length} accounts from seed phrase`);
|
||||||
|
this._showAccountSelection(accounts);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Seed phrase import failed:', error);
|
||||||
|
this._showError('Seed phrase import failed: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_showAccountSelection(accounts) {
|
||||||
|
this.modalBody.innerHTML = '';
|
||||||
|
|
||||||
|
const title = document.createElement('h3');
|
||||||
|
title.textContent = 'Select Account';
|
||||||
|
title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
|
||||||
|
|
||||||
|
const description = document.createElement('p');
|
||||||
|
description.textContent = `Select which account to use (${accounts.length} accounts derived from seed phrase):`;
|
||||||
|
description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
|
||||||
|
|
||||||
|
this.modalBody.appendChild(title);
|
||||||
|
this.modalBody.appendChild(description);
|
||||||
|
|
||||||
|
// Create table for account selection
|
||||||
|
const table = document.createElement('table');
|
||||||
|
table.style.cssText = `
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-family: var(--nl-font-family, 'Courier New', monospace);
|
||||||
|
font-size: 12px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Table header
|
||||||
|
const thead = document.createElement('thead');
|
||||||
|
thead.innerHTML = `
|
||||||
|
<tr style="background: #f3f4f6;">
|
||||||
|
<th style="padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;">#</th>
|
||||||
|
<th style="padding: 8px; text-align: left; border: 1px solid #d1d5db; font-weight: bold;">Public Key (npub)</th>
|
||||||
|
<th style="padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;">Action</th>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
table.appendChild(thead);
|
||||||
|
|
||||||
|
// Table body
|
||||||
|
const tbody = document.createElement('tbody');
|
||||||
|
accounts.forEach(account => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.style.cssText = 'border: 1px solid #d1d5db;';
|
||||||
|
|
||||||
|
const indexCell = document.createElement('td');
|
||||||
|
indexCell.textContent = account.index;
|
||||||
|
indexCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;';
|
||||||
|
|
||||||
|
const pubkeyCell = document.createElement('td');
|
||||||
|
pubkeyCell.style.cssText = 'padding: 8px; border: 1px solid #d1d5db; font-family: monospace; word-break: break-all;';
|
||||||
|
|
||||||
|
// Show truncated npub for readability
|
||||||
|
const truncatedNpub = `${account.npub.slice(0, 12)}...${account.npub.slice(-8)}`;
|
||||||
|
pubkeyCell.innerHTML = `
|
||||||
|
<code style="background: #f3f4f6; padding: 2px 4px; border-radius: 2px;">${truncatedNpub}</code><br>
|
||||||
|
<small style="color: #6b7280;">Full: ${account.npub}</small>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const actionCell = document.createElement('td');
|
||||||
|
actionCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db;';
|
||||||
|
|
||||||
|
const selectButton = document.createElement('button');
|
||||||
|
selectButton.textContent = 'Use';
|
||||||
|
selectButton.onclick = () => this._selectAccount(account);
|
||||||
|
selectButton.style.cssText = `
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 11px;
|
||||||
|
background: var(--nl-secondary-color);
|
||||||
|
color: var(--nl-primary-color);
|
||||||
|
border: 1px solid var(--nl-primary-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: var(--nl-font-family, 'Courier New', monospace);
|
||||||
|
`;
|
||||||
|
selectButton.onmouseover = () => {
|
||||||
|
selectButton.style.borderColor = 'var(--nl-accent-color)';
|
||||||
|
};
|
||||||
|
selectButton.onmouseout = () => {
|
||||||
|
selectButton.style.borderColor = 'var(--nl-primary-color)';
|
||||||
|
};
|
||||||
|
|
||||||
|
actionCell.appendChild(selectButton);
|
||||||
|
|
||||||
|
row.appendChild(indexCell);
|
||||||
|
row.appendChild(pubkeyCell);
|
||||||
|
row.appendChild(actionCell);
|
||||||
|
tbody.appendChild(row);
|
||||||
|
});
|
||||||
|
table.appendChild(tbody);
|
||||||
|
|
||||||
|
this.modalBody.appendChild(table);
|
||||||
|
|
||||||
|
// Back button
|
||||||
|
const backButton = document.createElement('button');
|
||||||
|
backButton.textContent = 'Back to Seed Phrase';
|
||||||
|
backButton.onclick = () => this._showSeedPhraseScreen();
|
||||||
|
backButton.style.cssText = this._getButtonStyle('secondary');
|
||||||
|
|
||||||
|
this.modalBody.appendChild(backButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectAccount(account) {
|
||||||
|
console.log('Selected account:', account.index, account.npub);
|
||||||
|
|
||||||
|
// Use the same auth method as local keys, but with seedphrase identifier
|
||||||
|
this._setAuthMethod('local', {
|
||||||
|
secret: account.nsec,
|
||||||
|
pubkey: account.publicKey,
|
||||||
|
source: 'seedphrase',
|
||||||
|
accountIndex: account.index
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_showOtpScreen() {
|
_showOtpScreen() {
|
||||||
// Placeholder for OTP functionality
|
// Placeholder for OTP functionality
|
||||||
this._showError('OTP/DM not yet implemented - coming soon!');
|
this._showError('OTP/DM not yet implemented - coming soon!');
|
||||||
@@ -2503,13 +2317,13 @@ class FloatingTab {
|
|||||||
// Determine which relays to use
|
// Determine which relays to use
|
||||||
const relays = this.options.getUserRelay.length > 0
|
const relays = this.options.getUserRelay.length > 0
|
||||||
? this.options.getUserRelay
|
? this.options.getUserRelay
|
||||||
: (this.modal?.options?.relays || ['wss://relay.damus.io', 'wss://nos.lol']);
|
: ['wss://relay.damus.io', 'wss://nos.lol'];
|
||||||
|
|
||||||
console.log('FloatingTab: Fetching profile from relays:', relays);
|
console.log('FloatingTab: Fetching profile from relays:', relays);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create a SimplePool instance for querying
|
// Create a SimplePool instance for querying
|
||||||
const pool = new window.NostrTools.nip46.SimplePool();
|
const pool = new window.NostrTools.SimplePool();
|
||||||
|
|
||||||
// Query for kind 0 (user metadata) events
|
// Query for kind 0 (user metadata) events
|
||||||
const events = await pool.querySync(relays, {
|
const events = await pool.querySync(relays, {
|
||||||
@@ -2532,9 +2346,27 @@ class FloatingTab {
|
|||||||
const profile = JSON.parse(latestEvent.content);
|
const profile = JSON.parse(latestEvent.content);
|
||||||
console.log('FloatingTab: Parsed profile:', profile);
|
console.log('FloatingTab: Parsed profile:', profile);
|
||||||
|
|
||||||
// Return relevant profile fields
|
// Find the best name from any key containing "name" (case-insensitive)
|
||||||
|
let bestName = null;
|
||||||
|
const nameKeys = Object.keys(profile).filter(key =>
|
||||||
|
key.toLowerCase().includes('name') &&
|
||||||
|
typeof profile[key] === 'string' &&
|
||||||
|
profile[key].trim().length > 0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nameKeys.length > 0) {
|
||||||
|
// Find the shortest name value
|
||||||
|
bestName = nameKeys
|
||||||
|
.map(key => profile[key].trim())
|
||||||
|
.reduce((shortest, current) =>
|
||||||
|
current.length < shortest.length ? current : shortest
|
||||||
|
);
|
||||||
|
console.log('FloatingTab: Found name keys:', nameKeys, 'selected:', bestName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return relevant profile fields with the best name
|
||||||
return {
|
return {
|
||||||
name: profile.name || null,
|
name: bestName,
|
||||||
display_name: profile.display_name || null,
|
display_name: profile.display_name || null,
|
||||||
about: profile.about || null,
|
about: profile.about || null,
|
||||||
picture: profile.picture || null,
|
picture: profile.picture || null,
|
||||||
@@ -2695,10 +2527,10 @@ class NostrLite {
|
|||||||
|
|
||||||
this.options = {
|
this.options = {
|
||||||
theme: 'default',
|
theme: 'default',
|
||||||
relays: ['wss://relay.damus.io', 'wss://nos.lol'],
|
|
||||||
methods: {
|
methods: {
|
||||||
extension: true,
|
extension: true,
|
||||||
local: true,
|
local: true,
|
||||||
|
seedphrase: false,
|
||||||
readonly: true,
|
readonly: true,
|
||||||
connect: false,
|
connect: false,
|
||||||
otp: false
|
otp: false
|
||||||
@@ -3127,8 +2959,8 @@ class WindowNostr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getRelays() {
|
async getRelays() {
|
||||||
// Return configured relays from nostr-lite options
|
// Return default relays since we removed the relays configuration
|
||||||
return this.nostrLite.options?.relays || ['wss://relay.damus.io'];
|
return ['wss://relay.damus.io', 'wss://nos.lol'];
|
||||||
}
|
}
|
||||||
|
|
||||||
get nip04() {
|
get nip04() {
|
||||||
|
|||||||
5472
api/nostr.bundle.js
5472
api/nostr.bundle.js
File diff suppressed because it is too large
Load Diff
568
src/config.c
568
src/config.c
@@ -14,8 +14,12 @@
|
|||||||
// External database connection (from main.c)
|
// External database connection (from main.c)
|
||||||
extern sqlite3* g_db;
|
extern sqlite3* g_db;
|
||||||
|
|
||||||
// Global configuration manager instance
|
// Global unified configuration cache instance
|
||||||
config_manager_t g_config_manager = {0};
|
unified_config_cache_t g_unified_cache = {
|
||||||
|
.cache_lock = PTHREAD_MUTEX_INITIALIZER,
|
||||||
|
.cache_valid = 0,
|
||||||
|
.cache_expires = 0
|
||||||
|
};
|
||||||
char g_database_path[512] = {0};
|
char g_database_path[512] = {0};
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
@@ -78,6 +82,126 @@ static cJSON* g_pending_config_event = NULL;
|
|||||||
// Temporary storage for relay private key during first-time setup
|
// Temporary storage for relay private key during first-time setup
|
||||||
static char g_temp_relay_privkey[65] = {0};
|
static char g_temp_relay_privkey[65] = {0};
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// UNIFIED CACHE MANAGEMENT FUNCTIONS
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
// Get cache timeout from environment variable or default (similar to request_validator)
|
||||||
|
static int get_cache_timeout(void) {
|
||||||
|
char *no_cache = getenv("GINX_NO_CACHE");
|
||||||
|
char *cache_timeout = getenv("GINX_CACHE_TIMEOUT");
|
||||||
|
|
||||||
|
if (no_cache && strcmp(no_cache, "1") == 0) {
|
||||||
|
return 0; // No caching
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cache_timeout) {
|
||||||
|
int timeout = atoi(cache_timeout);
|
||||||
|
return (timeout >= 0) ? timeout : 300; // Use provided value or default
|
||||||
|
}
|
||||||
|
|
||||||
|
return 300; // Default 5 minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force cache refresh - invalidates current cache
|
||||||
|
void force_config_cache_refresh(void) {
|
||||||
|
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||||
|
g_unified_cache.cache_valid = 0;
|
||||||
|
g_unified_cache.cache_expires = 0;
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
|
log_info("Configuration cache forcibly invalidated");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh unified cache from database
|
||||||
|
static int refresh_unified_cache_from_table(void) {
|
||||||
|
if (!g_db) {
|
||||||
|
log_error("Database not available for cache refresh");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear cache
|
||||||
|
memset(&g_unified_cache, 0, sizeof(g_unified_cache));
|
||||||
|
g_unified_cache.cache_lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
// Load critical config values from table
|
||||||
|
const char* admin_pubkey = get_config_value_from_table("admin_pubkey");
|
||||||
|
if (admin_pubkey) {
|
||||||
|
strncpy(g_unified_cache.admin_pubkey, admin_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1);
|
||||||
|
g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* relay_pubkey = get_config_value_from_table("relay_pubkey");
|
||||||
|
if (relay_pubkey) {
|
||||||
|
strncpy(g_unified_cache.relay_pubkey, relay_pubkey, sizeof(g_unified_cache.relay_pubkey) - 1);
|
||||||
|
g_unified_cache.relay_pubkey[sizeof(g_unified_cache.relay_pubkey) - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load auth-related config
|
||||||
|
const char* auth_required = get_config_value_from_table("auth_required");
|
||||||
|
g_unified_cache.auth_required = (auth_required && strcmp(auth_required, "true") == 0) ? 1 : 0;
|
||||||
|
|
||||||
|
const char* admin_enabled = get_config_value_from_table("admin_enabled");
|
||||||
|
g_unified_cache.admin_enabled = (admin_enabled && strcmp(admin_enabled, "true") == 0) ? 1 : 0;
|
||||||
|
|
||||||
|
const char* max_file_size = get_config_value_from_table("max_file_size");
|
||||||
|
g_unified_cache.max_file_size = max_file_size ? atol(max_file_size) : 104857600; // 100MB default
|
||||||
|
|
||||||
|
const char* nip42_mode = get_config_value_from_table("nip42_mode");
|
||||||
|
if (nip42_mode) {
|
||||||
|
if (strcmp(nip42_mode, "disabled") == 0) {
|
||||||
|
g_unified_cache.nip42_mode = 0;
|
||||||
|
} else if (strcmp(nip42_mode, "required") == 0) {
|
||||||
|
g_unified_cache.nip42_mode = 2;
|
||||||
|
} else {
|
||||||
|
g_unified_cache.nip42_mode = 1; // Optional/enabled
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g_unified_cache.nip42_mode = 1; // Default to optional/enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* challenge_timeout = get_config_value_from_table("nip42_challenge_timeout");
|
||||||
|
g_unified_cache.nip42_challenge_timeout = challenge_timeout ? atoi(challenge_timeout) : 600;
|
||||||
|
|
||||||
|
const char* time_tolerance = get_config_value_from_table("nip42_time_tolerance");
|
||||||
|
g_unified_cache.nip42_time_tolerance = time_tolerance ? atoi(time_tolerance) : 300;
|
||||||
|
|
||||||
|
// Set cache expiration
|
||||||
|
int cache_timeout = get_cache_timeout();
|
||||||
|
g_unified_cache.cache_expires = time(NULL) + cache_timeout;
|
||||||
|
g_unified_cache.cache_valid = 1;
|
||||||
|
|
||||||
|
log_info("Unified configuration cache refreshed from database");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get admin pubkey from cache (with automatic refresh)
|
||||||
|
const char* get_admin_pubkey_cached(void) {
|
||||||
|
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||||
|
|
||||||
|
// Check cache validity
|
||||||
|
if (!g_unified_cache.cache_valid || time(NULL) > g_unified_cache.cache_expires) {
|
||||||
|
refresh_unified_cache_from_table();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* result = g_unified_cache.admin_pubkey[0] ? g_unified_cache.admin_pubkey : NULL;
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get relay pubkey from cache (with automatic refresh)
|
||||||
|
const char* get_relay_pubkey_cached(void) {
|
||||||
|
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||||
|
|
||||||
|
// Check cache validity
|
||||||
|
if (!g_unified_cache.cache_valid || time(NULL) > g_unified_cache.cache_expires) {
|
||||||
|
refresh_unified_cache_from_table();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* result = g_unified_cache.relay_pubkey[0] ? g_unified_cache.relay_pubkey : NULL;
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
// UTILITY FUNCTIONS
|
// UTILITY FUNCTIONS
|
||||||
// ================================
|
// ================================
|
||||||
@@ -254,15 +378,16 @@ cJSON* load_config_event_from_database(const char* relay_pubkey) {
|
|||||||
sqlite3_stmt* stmt;
|
sqlite3_stmt* stmt;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
// If we have admin pubkey, query by it; otherwise find the most recent kind 33334 event
|
// Try to get admin pubkey from cache, otherwise find the most recent kind 33334 event
|
||||||
if (strlen(g_config_manager.admin_pubkey) > 0) {
|
const char* admin_pubkey = get_admin_pubkey_cached();
|
||||||
|
if (admin_pubkey && strlen(admin_pubkey) > 0) {
|
||||||
sql = "SELECT id, pubkey, created_at, kind, content, sig, tags FROM events WHERE kind = 33334 AND pubkey = ? ORDER BY created_at DESC LIMIT 1";
|
sql = "SELECT id, pubkey, created_at, kind, content, sig, tags FROM events WHERE kind = 33334 AND pubkey = ? ORDER BY created_at DESC LIMIT 1";
|
||||||
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||||
if (rc != SQLITE_OK) {
|
if (rc != SQLITE_OK) {
|
||||||
log_error("Failed to prepare configuration event query");
|
log_error("Failed to prepare configuration event query");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
sqlite3_bind_text(stmt, 1, g_config_manager.admin_pubkey, -1, SQLITE_STATIC);
|
sqlite3_bind_text(stmt, 1, admin_pubkey, -1, SQLITE_STATIC);
|
||||||
} else {
|
} else {
|
||||||
// During existing relay startup, we don't know the admin pubkey yet
|
// During existing relay startup, we don't know the admin pubkey yet
|
||||||
// Look for any kind 33334 configuration event (should only be one per relay)
|
// Look for any kind 33334 configuration event (should only be one per relay)
|
||||||
@@ -288,11 +413,8 @@ cJSON* load_config_event_from_database(const char* relay_pubkey) {
|
|||||||
cJSON_AddStringToObject(event, "content", (const char*)sqlite3_column_text(stmt, 4));
|
cJSON_AddStringToObject(event, "content", (const char*)sqlite3_column_text(stmt, 4));
|
||||||
cJSON_AddStringToObject(event, "sig", (const char*)sqlite3_column_text(stmt, 5));
|
cJSON_AddStringToObject(event, "sig", (const char*)sqlite3_column_text(stmt, 5));
|
||||||
|
|
||||||
// If we didn't have admin pubkey, store it now
|
// If we didn't have admin pubkey in cache, we should update the cache
|
||||||
if (strlen(g_config_manager.admin_pubkey) == 0) {
|
// Note: This will be handled by the cache refresh mechanism automatically
|
||||||
strncpy(g_config_manager.admin_pubkey, event_pubkey, sizeof(g_config_manager.admin_pubkey) - 1);
|
|
||||||
g_config_manager.admin_pubkey[sizeof(g_config_manager.admin_pubkey) - 1] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse tags JSON
|
// Parse tags JSON
|
||||||
const char* tags_str = (const char*)sqlite3_column_text(stmt, 6);
|
const char* tags_str = (const char*)sqlite3_column_text(stmt, 6);
|
||||||
@@ -318,9 +440,28 @@ cJSON* load_config_event_from_database(const char* relay_pubkey) {
|
|||||||
// ================================
|
// ================================
|
||||||
|
|
||||||
const char* get_config_value(const char* key) {
|
const char* get_config_value(const char* key) {
|
||||||
static char buffer[CONFIG_VALUE_MAX_LENGTH];
|
if (!key) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (!key || !g_current_config) {
|
// Special fast path for frequently accessed keys via unified cache
|
||||||
|
if (strcmp(key, "admin_pubkey") == 0) {
|
||||||
|
return get_admin_pubkey_cached();
|
||||||
|
}
|
||||||
|
if (strcmp(key, "relay_pubkey") == 0) {
|
||||||
|
return get_relay_pubkey_cached();
|
||||||
|
}
|
||||||
|
|
||||||
|
// For other keys, try config table first
|
||||||
|
const char* table_value = get_config_value_from_table(key);
|
||||||
|
if (table_value) {
|
||||||
|
return table_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to legacy event-based config for backward compatibility
|
||||||
|
// Use unified cache buffer instead of static buffer
|
||||||
|
|
||||||
|
if (!g_current_config) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,24 +471,30 @@ const char* get_config_value(const char* key) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||||
|
|
||||||
cJSON* tag = NULL;
|
cJSON* tag = NULL;
|
||||||
cJSON_ArrayForEach(tag, tags) {
|
cJSON_ArrayForEach(tag, tags) {
|
||||||
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
|
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
|
||||||
cJSON* tag_key = cJSON_GetArrayItem(tag, 0);
|
cJSON* tag_key = cJSON_GetArrayItem(tag, 0);
|
||||||
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
|
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
|
||||||
|
|
||||||
if (tag_key && tag_value &&
|
if (tag_key && tag_value &&
|
||||||
cJSON_IsString(tag_key) && cJSON_IsString(tag_value)) {
|
cJSON_IsString(tag_key) && cJSON_IsString(tag_value)) {
|
||||||
|
|
||||||
if (strcmp(cJSON_GetStringValue(tag_key), key) == 0) {
|
if (strcmp(cJSON_GetStringValue(tag_key), key) == 0) {
|
||||||
strncpy(buffer, cJSON_GetStringValue(tag_value), sizeof(buffer) - 1);
|
strncpy(g_unified_cache.temp_buffer, cJSON_GetStringValue(tag_value),
|
||||||
buffer[sizeof(buffer) - 1] = '\0';
|
sizeof(g_unified_cache.temp_buffer) - 1);
|
||||||
return buffer;
|
g_unified_cache.temp_buffer[sizeof(g_unified_cache.temp_buffer) - 1] = '\0';
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
|
return g_unified_cache.temp_buffer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,14 +638,44 @@ int init_configuration_system(const char* config_dir_override, const char* confi
|
|||||||
|
|
||||||
log_info("Initializing event-based configuration system...");
|
log_info("Initializing event-based configuration system...");
|
||||||
|
|
||||||
// Clear configuration manager state
|
// Initialize unified cache with proper structure initialization
|
||||||
memset(&g_config_manager, 0, sizeof(config_manager_t));
|
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||||
g_config_manager.db = g_db;
|
|
||||||
|
|
||||||
// For now, set empty paths for compatibility
|
// Clear the entire cache structure
|
||||||
g_config_manager.config_file_path[0] = '\0';
|
memset(&g_unified_cache, 0, sizeof(g_unified_cache));
|
||||||
|
|
||||||
log_success("Event-based configuration system initialized");
|
// Reinitialize the mutex after memset
|
||||||
|
g_unified_cache.cache_lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
// Initialize basic cache state
|
||||||
|
g_unified_cache.cache_valid = 0;
|
||||||
|
g_unified_cache.cache_expires = 0;
|
||||||
|
|
||||||
|
// Initialize relay_info structure with default values
|
||||||
|
strncpy(g_unified_cache.relay_info.software, "https://git.laantungir.net/laantungir/c-relay.git",
|
||||||
|
sizeof(g_unified_cache.relay_info.software) - 1);
|
||||||
|
strncpy(g_unified_cache.relay_info.version, "0.2.0",
|
||||||
|
sizeof(g_unified_cache.relay_info.version) - 1);
|
||||||
|
|
||||||
|
// Initialize pow_config structure with defaults
|
||||||
|
g_unified_cache.pow_config.enabled = 1;
|
||||||
|
g_unified_cache.pow_config.min_pow_difficulty = 0;
|
||||||
|
g_unified_cache.pow_config.validation_flags = 1; // NOSTR_POW_VALIDATE_BASIC
|
||||||
|
g_unified_cache.pow_config.require_nonce_tag = 0;
|
||||||
|
g_unified_cache.pow_config.reject_lower_targets = 0;
|
||||||
|
g_unified_cache.pow_config.strict_format = 0;
|
||||||
|
g_unified_cache.pow_config.anti_spam_mode = 0;
|
||||||
|
|
||||||
|
// Initialize expiration_config structure with defaults
|
||||||
|
g_unified_cache.expiration_config.enabled = 1;
|
||||||
|
g_unified_cache.expiration_config.strict_mode = 1;
|
||||||
|
g_unified_cache.expiration_config.filter_responses = 1;
|
||||||
|
g_unified_cache.expiration_config.delete_expired = 0;
|
||||||
|
g_unified_cache.expiration_config.grace_period = 1;
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
|
|
||||||
|
log_success("Event-based configuration system initialized with unified cache structures");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -515,8 +692,38 @@ void cleanup_configuration_system(void) {
|
|||||||
g_pending_config_event = NULL;
|
g_pending_config_event = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(&g_config_manager, 0, sizeof(config_manager_t));
|
// Clear unified cache with proper cleanup of JSON objects
|
||||||
log_success("Configuration system cleaned up");
|
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||||
|
|
||||||
|
// Clean up relay_info JSON objects if they exist
|
||||||
|
if (g_unified_cache.relay_info.supported_nips) {
|
||||||
|
cJSON_Delete(g_unified_cache.relay_info.supported_nips);
|
||||||
|
}
|
||||||
|
if (g_unified_cache.relay_info.limitation) {
|
||||||
|
cJSON_Delete(g_unified_cache.relay_info.limitation);
|
||||||
|
}
|
||||||
|
if (g_unified_cache.relay_info.retention) {
|
||||||
|
cJSON_Delete(g_unified_cache.relay_info.retention);
|
||||||
|
}
|
||||||
|
if (g_unified_cache.relay_info.relay_countries) {
|
||||||
|
cJSON_Delete(g_unified_cache.relay_info.relay_countries);
|
||||||
|
}
|
||||||
|
if (g_unified_cache.relay_info.language_tags) {
|
||||||
|
cJSON_Delete(g_unified_cache.relay_info.language_tags);
|
||||||
|
}
|
||||||
|
if (g_unified_cache.relay_info.tags) {
|
||||||
|
cJSON_Delete(g_unified_cache.relay_info.tags);
|
||||||
|
}
|
||||||
|
if (g_unified_cache.relay_info.fees) {
|
||||||
|
cJSON_Delete(g_unified_cache.relay_info.fees);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the entire cache structure
|
||||||
|
memset(&g_unified_cache, 0, sizeof(g_unified_cache));
|
||||||
|
g_unified_cache.cache_lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
|
log_success("Configuration system cleaned up with proper JSON cleanup");
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_database_config(const char* key, const char* value, const char* changed_by) {
|
int set_database_config(const char* key, const char* value, const char* changed_by) {
|
||||||
@@ -832,11 +1039,13 @@ int first_time_startup_sequence(const cli_options_t* cli_options) {
|
|||||||
}
|
}
|
||||||
nostr_bytes_to_hex(relay_pubkey_bytes, 32, relay_pubkey);
|
nostr_bytes_to_hex(relay_pubkey_bytes, 32, relay_pubkey);
|
||||||
|
|
||||||
// 3. Store keys in global config manager
|
// 3. Store keys in unified cache (will be added to database after init)
|
||||||
strncpy(g_config_manager.admin_pubkey, admin_pubkey, sizeof(g_config_manager.admin_pubkey) - 1);
|
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||||
g_config_manager.admin_pubkey[sizeof(g_config_manager.admin_pubkey) - 1] = '\0';
|
strncpy(g_unified_cache.admin_pubkey, admin_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1);
|
||||||
strncpy(g_config_manager.relay_pubkey, relay_pubkey, sizeof(g_config_manager.relay_pubkey) - 1);
|
g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0';
|
||||||
g_config_manager.relay_pubkey[sizeof(g_config_manager.relay_pubkey) - 1] = '\0';
|
strncpy(g_unified_cache.relay_pubkey, relay_pubkey, sizeof(g_unified_cache.relay_pubkey) - 1);
|
||||||
|
g_unified_cache.relay_pubkey[sizeof(g_unified_cache.relay_pubkey) - 1] = '\0';
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
|
|
||||||
// 4. Create database with relay pubkey name
|
// 4. Create database with relay pubkey name
|
||||||
if (create_database_with_relay_pubkey(relay_pubkey) != 0) {
|
if (create_database_with_relay_pubkey(relay_pubkey) != 0) {
|
||||||
@@ -904,8 +1113,11 @@ int startup_existing_relay(const char* relay_pubkey) {
|
|||||||
log_info("Starting existing relay...");
|
log_info("Starting existing relay...");
|
||||||
printf(" Relay pubkey: %s\n", relay_pubkey);
|
printf(" Relay pubkey: %s\n", relay_pubkey);
|
||||||
|
|
||||||
// Store relay pubkey in global config manager
|
// Store relay pubkey in unified cache
|
||||||
strncpy(g_config_manager.relay_pubkey, relay_pubkey, sizeof(g_config_manager.relay_pubkey) - 1);
|
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||||
|
strncpy(g_unified_cache.relay_pubkey, relay_pubkey, sizeof(g_unified_cache.relay_pubkey) - 1);
|
||||||
|
g_unified_cache.relay_pubkey[sizeof(g_unified_cache.relay_pubkey) - 1] = '\0';
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
|
|
||||||
// Set database path
|
// Set database path
|
||||||
char* db_name = get_database_name_from_relay_pubkey(relay_pubkey);
|
char* db_name = get_database_name_from_relay_pubkey(relay_pubkey);
|
||||||
@@ -1408,8 +1620,9 @@ int process_configuration_event(const cJSON* event) {
|
|||||||
|
|
||||||
// Verify it's from the admin
|
// Verify it's from the admin
|
||||||
const char* event_pubkey = cJSON_GetStringValue(pubkey_obj);
|
const char* event_pubkey = cJSON_GetStringValue(pubkey_obj);
|
||||||
if (strlen(g_config_manager.admin_pubkey) > 0) {
|
const char* admin_pubkey = get_admin_pubkey_cached();
|
||||||
if (strcmp(event_pubkey, g_config_manager.admin_pubkey) != 0) {
|
if (admin_pubkey && strlen(admin_pubkey) > 0) {
|
||||||
|
if (strcmp(event_pubkey, admin_pubkey) != 0) {
|
||||||
log_error("Configuration event not from authorized admin");
|
log_error("Configuration event not from authorized admin");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -1580,11 +1793,18 @@ int apply_configuration_from_event(const cJSON* event) {
|
|||||||
// Update cached configuration
|
// Update cached configuration
|
||||||
g_current_config = cJSON_Duplicate(event, 1);
|
g_current_config = cJSON_Duplicate(event, 1);
|
||||||
|
|
||||||
// Extract admin pubkey if not already set
|
// Extract admin pubkey if not already in cache
|
||||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||||
if (pubkey_obj && strlen(g_config_manager.admin_pubkey) == 0) {
|
if (pubkey_obj) {
|
||||||
strncpy(g_config_manager.admin_pubkey, cJSON_GetStringValue(pubkey_obj),
|
const char* event_pubkey = cJSON_GetStringValue(pubkey_obj);
|
||||||
sizeof(g_config_manager.admin_pubkey) - 1);
|
const char* cached_admin_pubkey = get_admin_pubkey_cached();
|
||||||
|
if (!cached_admin_pubkey || strlen(cached_admin_pubkey) == 0) {
|
||||||
|
// Update cache with admin pubkey from event
|
||||||
|
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||||
|
strncpy(g_unified_cache.admin_pubkey, event_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1);
|
||||||
|
g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0';
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply runtime configuration changes
|
// Apply runtime configuration changes
|
||||||
@@ -1651,15 +1871,17 @@ const char* get_config_value_from_table(const char* key) {
|
|||||||
|
|
||||||
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
|
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
|
||||||
|
|
||||||
static char config_value_buffer[CONFIG_VALUE_MAX_LENGTH];
|
|
||||||
const char* result = NULL;
|
const char* result = NULL;
|
||||||
|
|
||||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
const char* value = (char*)sqlite3_column_text(stmt, 0);
|
const char* value = (char*)sqlite3_column_text(stmt, 0);
|
||||||
if (value) {
|
if (value) {
|
||||||
strncpy(config_value_buffer, value, sizeof(config_value_buffer) - 1);
|
// Use unified cache buffer with thread safety
|
||||||
config_value_buffer[sizeof(config_value_buffer) - 1] = '\0';
|
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||||
result = config_value_buffer;
|
strncpy(g_unified_cache.temp_buffer, value, sizeof(g_unified_cache.temp_buffer) - 1);
|
||||||
|
g_unified_cache.temp_buffer[sizeof(g_unified_cache.temp_buffer) - 1] = '\0';
|
||||||
|
result = g_unified_cache.temp_buffer;
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1783,6 +2005,52 @@ int populate_default_config_values(void) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add dynamically generated pubkeys to config table
|
||||||
|
int add_pubkeys_to_config_table(void) {
|
||||||
|
if (!g_db) {
|
||||||
|
log_error("Database not available for pubkey storage");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info("Adding dynamically generated pubkeys to config table...");
|
||||||
|
|
||||||
|
// Get the pubkeys directly from unified cache (not through cached accessors to avoid circular dependency)
|
||||||
|
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||||
|
const char* admin_pubkey = g_unified_cache.admin_pubkey[0] ? g_unified_cache.admin_pubkey : NULL;
|
||||||
|
const char* relay_pubkey = g_unified_cache.relay_pubkey[0] ? g_unified_cache.relay_pubkey : NULL;
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
|
|
||||||
|
if (!admin_pubkey || strlen(admin_pubkey) != 64) {
|
||||||
|
log_error("Admin pubkey not available or invalid for config table storage");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!relay_pubkey || strlen(relay_pubkey) != 64) {
|
||||||
|
log_error("Relay pubkey not available or invalid for config table storage");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store admin pubkey in config table
|
||||||
|
if (set_config_value_in_table("admin_pubkey", admin_pubkey, "string",
|
||||||
|
"Administrator public key", "authentication", 0) != 0) {
|
||||||
|
log_error("Failed to store admin_pubkey in config table");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store relay pubkey in config table
|
||||||
|
if (set_config_value_in_table("relay_pubkey", relay_pubkey, "string",
|
||||||
|
"Relay public key", "relay", 0) != 0) {
|
||||||
|
log_error("Failed to store relay_pubkey in config table");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success("Dynamically generated pubkeys added to config table");
|
||||||
|
printf(" Admin pubkey: %s\n", admin_pubkey);
|
||||||
|
printf(" Relay pubkey: %s\n", relay_pubkey);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
// ADMIN EVENT PROCESSING FUNCTIONS
|
// ADMIN EVENT PROCESSING FUNCTIONS
|
||||||
// ================================
|
// ================================
|
||||||
@@ -2028,17 +2296,24 @@ int remove_auth_rule_from_config(const char* rule_type, const char* pattern_type
|
|||||||
|
|
||||||
// Invalidate configuration cache
|
// Invalidate configuration cache
|
||||||
void invalidate_config_cache(void) {
|
void invalidate_config_cache(void) {
|
||||||
// For now, just log that cache was invalidated
|
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||||
// In a full implementation, this would clear any cached config values
|
g_unified_cache.cache_valid = 0;
|
||||||
log_info("Configuration cache invalidated");
|
g_unified_cache.cache_expires = 0;
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
|
log_info("Unified configuration cache invalidated");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload configuration from table
|
// Reload configuration from table
|
||||||
int reload_config_from_table(void) {
|
int reload_config_from_table(void) {
|
||||||
// For now, just log that config was reloaded
|
// Trigger a cache refresh by calling the refresh function directly
|
||||||
// In a full implementation, this would reload all cached values from the table
|
int result = refresh_unified_cache_from_table();
|
||||||
log_info("Configuration reloaded from table");
|
|
||||||
return 0;
|
if (result == 0) {
|
||||||
|
log_info("Configuration reloaded from table");
|
||||||
|
} else {
|
||||||
|
log_error("Failed to reload configuration from table");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
@@ -2101,8 +2376,11 @@ int is_config_table_ready(void) {
|
|||||||
int initialize_config_system_with_migration(void) {
|
int initialize_config_system_with_migration(void) {
|
||||||
log_info("Initializing configuration system with migration support...");
|
log_info("Initializing configuration system with migration support...");
|
||||||
|
|
||||||
// Initialize config manager
|
// Initialize unified cache and migration status
|
||||||
memset(&g_config_manager, 0, sizeof(g_config_manager));
|
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||||
|
g_unified_cache.cache_valid = 0;
|
||||||
|
g_unified_cache.cache_expires = 0;
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
memset(&g_migration_status, 0, sizeof(g_migration_status));
|
memset(&g_migration_status, 0, sizeof(g_migration_status));
|
||||||
|
|
||||||
// For new installations, config table should already exist from embedded schema
|
// For new installations, config table should already exist from embedded schema
|
||||||
@@ -2254,7 +2532,8 @@ int migrate_config_from_events_to_table(void) {
|
|||||||
log_info("Migrating configuration from events to config table...");
|
log_info("Migrating configuration from events to config table...");
|
||||||
|
|
||||||
// Load the most recent configuration event from database
|
// Load the most recent configuration event from database
|
||||||
cJSON* config_event = load_config_event_from_database(g_config_manager.relay_pubkey);
|
const char* relay_pubkey = get_relay_pubkey_cached();
|
||||||
|
cJSON* config_event = load_config_event_from_database(relay_pubkey);
|
||||||
if (!config_event) {
|
if (!config_event) {
|
||||||
log_info("No existing configuration event found - migration not needed");
|
log_info("No existing configuration event found - migration not needed");
|
||||||
return 0;
|
return 0;
|
||||||
@@ -2382,4 +2661,191 @@ int process_startup_config_event_with_fallback(const cJSON* event) {
|
|||||||
|
|
||||||
log_error("Startup configuration processing failed even after populating defaults");
|
log_error("Startup configuration processing failed even after populating defaults");
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// DYNAMIC EVENT GENERATION FROM CONFIG TABLE
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
// Generate synthetic kind 33334 configuration event from current config table data
|
||||||
|
cJSON* generate_config_event_from_table(void) {
|
||||||
|
if (!g_db) {
|
||||||
|
log_error("Database not available for config event generation");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info("Generating synthetic kind 33334 event from config table...");
|
||||||
|
|
||||||
|
// Get relay pubkey for event generation
|
||||||
|
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||||
|
if (!relay_pubkey || strlen(relay_pubkey) != 64) {
|
||||||
|
// Try to get from unified cache
|
||||||
|
relay_pubkey = get_relay_pubkey_cached();
|
||||||
|
if (!relay_pubkey || strlen(relay_pubkey) != 64) {
|
||||||
|
log_error("Relay pubkey not available for config event generation");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the event structure
|
||||||
|
cJSON* event = cJSON_CreateObject();
|
||||||
|
if (!event) {
|
||||||
|
log_error("Failed to create config event object");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set basic event fields - we'll generate a synthetic event
|
||||||
|
cJSON_AddStringToObject(event, "id", "synthetic_config_event_id");
|
||||||
|
cJSON_AddStringToObject(event, "pubkey", relay_pubkey); // Use relay pubkey as event author
|
||||||
|
cJSON_AddNumberToObject(event, "created_at", (double)time(NULL));
|
||||||
|
cJSON_AddNumberToObject(event, "kind", 33334);
|
||||||
|
cJSON_AddStringToObject(event, "content", "C Nostr Relay Configuration");
|
||||||
|
cJSON_AddStringToObject(event, "sig", "synthetic_signature");
|
||||||
|
|
||||||
|
// Create tags array from config table
|
||||||
|
cJSON* tags = cJSON_CreateArray();
|
||||||
|
if (!tags) {
|
||||||
|
log_error("Failed to create tags array for config event");
|
||||||
|
cJSON_Delete(event);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add d tag with relay pubkey (addressable event identifier)
|
||||||
|
cJSON* d_tag = cJSON_CreateArray();
|
||||||
|
cJSON_AddItemToArray(d_tag, cJSON_CreateString("d"));
|
||||||
|
cJSON_AddItemToArray(d_tag, cJSON_CreateString(relay_pubkey));
|
||||||
|
cJSON_AddItemToArray(tags, d_tag);
|
||||||
|
|
||||||
|
// Query all configuration values from the config table
|
||||||
|
const char* sql = "SELECT key, value FROM config ORDER BY key";
|
||||||
|
sqlite3_stmt* stmt;
|
||||||
|
|
||||||
|
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
log_error("Failed to prepare config query for event generation");
|
||||||
|
cJSON_Delete(tags);
|
||||||
|
cJSON_Delete(event);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int config_items_added = 0;
|
||||||
|
|
||||||
|
// Add each config item as a tag
|
||||||
|
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) {
|
||||||
|
cJSON* config_tag = cJSON_CreateArray();
|
||||||
|
cJSON_AddItemToArray(config_tag, cJSON_CreateString(key));
|
||||||
|
cJSON_AddItemToArray(config_tag, cJSON_CreateString(value));
|
||||||
|
cJSON_AddItemToArray(tags, config_tag);
|
||||||
|
config_items_added++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
|
if (config_items_added == 0) {
|
||||||
|
log_warning("No configuration items found in config table for event generation");
|
||||||
|
cJSON_Delete(tags);
|
||||||
|
cJSON_Delete(event);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add tags to event
|
||||||
|
cJSON_AddItemToObject(event, "tags", tags);
|
||||||
|
|
||||||
|
char success_msg[256];
|
||||||
|
snprintf(success_msg, sizeof(success_msg),
|
||||||
|
"Generated synthetic kind 33334 event with %d configuration items", config_items_added);
|
||||||
|
log_success(success_msg);
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a REQ filter requests kind 33334 events
|
||||||
|
int req_filter_requests_config_events(const cJSON* filter) {
|
||||||
|
if (!filter || !cJSON_IsObject(filter)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* kinds = cJSON_GetObjectItem(filter, "kinds");
|
||||||
|
if (!kinds || !cJSON_IsArray(kinds)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if kinds array contains 33334
|
||||||
|
cJSON* kind_item = NULL;
|
||||||
|
cJSON_ArrayForEach(kind_item, kinds) {
|
||||||
|
if (cJSON_IsNumber(kind_item) && (int)cJSON_GetNumberValue(kind_item) == 33334) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate synthetic config event data for subscription (callback approach)
|
||||||
|
cJSON* generate_synthetic_config_event_for_subscription(const char* sub_id, const cJSON* filters) {
|
||||||
|
if (!sub_id || !filters) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any filter requests kind 33334
|
||||||
|
int requests_config = 0;
|
||||||
|
|
||||||
|
if (cJSON_IsArray(filters)) {
|
||||||
|
cJSON* filter = NULL;
|
||||||
|
cJSON_ArrayForEach(filter, filters) {
|
||||||
|
if (req_filter_requests_config_events(filter)) {
|
||||||
|
requests_config = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (cJSON_IsObject(filters)) {
|
||||||
|
requests_config = req_filter_requests_config_events(filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requests_config) {
|
||||||
|
// No config events requested
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info("Generating synthetic kind 33334 event for subscription");
|
||||||
|
|
||||||
|
// Generate synthetic config event from table
|
||||||
|
cJSON* config_event = generate_config_event_from_table();
|
||||||
|
if (!config_event) {
|
||||||
|
log_error("Failed to generate synthetic config event");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create EVENT message for the subscription
|
||||||
|
cJSON* event_msg = cJSON_CreateArray();
|
||||||
|
cJSON_AddItemToArray(event_msg, cJSON_CreateString("EVENT"));
|
||||||
|
cJSON_AddItemToArray(event_msg, cJSON_CreateString(sub_id));
|
||||||
|
cJSON_AddItemToArray(event_msg, config_event);
|
||||||
|
|
||||||
|
log_success("Generated synthetic kind 33334 configuration event message");
|
||||||
|
return event_msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a synthetic kind 33334 configuration event from config table data
|
||||||
|
* This allows WebSocket clients to fetch configuration via REQ messages
|
||||||
|
* Returns JSON string that must be freed by caller
|
||||||
|
*/
|
||||||
|
char* generate_config_event_json(void) {
|
||||||
|
// Use the existing cJSON function and convert to string
|
||||||
|
cJSON* event = generate_config_event_from_table();
|
||||||
|
if (!event) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to JSON string
|
||||||
|
char* json_string = cJSON_Print(event);
|
||||||
|
cJSON_Delete(event);
|
||||||
|
|
||||||
|
return json_string;
|
||||||
}
|
}
|
||||||
86
src/config.h
86
src/config.h
@@ -4,6 +4,7 @@
|
|||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
#include <cjson/cJSON.h>
|
#include <cjson/cJSON.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
// Configuration constants
|
// Configuration constants
|
||||||
#define CONFIG_VALUE_MAX_LENGTH 1024
|
#define CONFIG_VALUE_MAX_LENGTH 1024
|
||||||
@@ -23,14 +24,71 @@
|
|||||||
// Database path for event-based config
|
// Database path for event-based config
|
||||||
extern char g_database_path[512];
|
extern char g_database_path[512];
|
||||||
|
|
||||||
// Configuration manager structure
|
// Unified configuration cache structure (consolidates all caching systems)
|
||||||
typedef struct {
|
typedef struct {
|
||||||
sqlite3* db;
|
// Critical keys (frequently accessed)
|
||||||
char relay_pubkey[65];
|
|
||||||
char admin_pubkey[65];
|
char admin_pubkey[65];
|
||||||
time_t last_config_check;
|
char relay_pubkey[65];
|
||||||
char config_file_path[512]; // Temporary for compatibility
|
|
||||||
} config_manager_t;
|
// 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
|
// Command line options structure for first-time startup
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -39,8 +97,8 @@ typedef struct {
|
|||||||
char relay_privkey_override[65]; // Empty string = not set, 64-char hex = override
|
char relay_privkey_override[65]; // Empty string = not set, 64-char hex = override
|
||||||
} cli_options_t;
|
} cli_options_t;
|
||||||
|
|
||||||
// Global configuration manager
|
// Global unified configuration cache
|
||||||
extern config_manager_t g_config_manager;
|
extern unified_config_cache_t g_unified_cache;
|
||||||
|
|
||||||
// Core configuration functions (temporary compatibility)
|
// Core configuration functions (temporary compatibility)
|
||||||
int init_configuration_system(const char* config_dir_override, const char* config_file_override);
|
int init_configuration_system(const char* config_dir_override, const char* config_file_override);
|
||||||
@@ -100,6 +158,7 @@ int set_config_value_in_table(const char* key, const char* value, const char* da
|
|||||||
const char* description, const char* category, int requires_restart);
|
const char* description, const char* category, int requires_restart);
|
||||||
int update_config_in_table(const char* key, const char* value);
|
int update_config_in_table(const char* key, const char* value);
|
||||||
int populate_default_config_values(void);
|
int populate_default_config_values(void);
|
||||||
|
int add_pubkeys_to_config_table(void);
|
||||||
|
|
||||||
// Admin event processing functions
|
// Admin event processing functions
|
||||||
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size);
|
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size);
|
||||||
@@ -112,7 +171,10 @@ int add_auth_rule_from_config(const char* rule_type, const char* pattern_type,
|
|||||||
int remove_auth_rule_from_config(const char* rule_type, const char* pattern_type,
|
int remove_auth_rule_from_config(const char* rule_type, const char* pattern_type,
|
||||||
const char* pattern_value);
|
const char* pattern_value);
|
||||||
|
|
||||||
// Configuration cache management
|
// 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);
|
void invalidate_config_cache(void);
|
||||||
int reload_config_from_table(void);
|
int reload_config_from_table(void);
|
||||||
|
|
||||||
@@ -129,4 +191,10 @@ int populate_config_table_from_event(const cJSON* event);
|
|||||||
int process_startup_config_event(const cJSON* event);
|
int process_startup_config_event(const cJSON* event);
|
||||||
int process_startup_config_event_with_fallback(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 */
|
#endif /* CONFIG_H */
|
||||||
509
src/main.c
509
src/main.c
@@ -68,8 +68,8 @@ struct relay_info {
|
|||||||
char payments_url[RELAY_URL_MAX_LENGTH];
|
char payments_url[RELAY_URL_MAX_LENGTH];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Global relay information instance
|
// Global relay information instance moved to unified cache
|
||||||
static struct relay_info g_relay_info = {0};
|
// static struct relay_info g_relay_info = {0}; // REMOVED - now in g_unified_cache.relay_info
|
||||||
|
|
||||||
// NIP-13 PoW configuration structure
|
// NIP-13 PoW configuration structure
|
||||||
struct pow_config {
|
struct pow_config {
|
||||||
@@ -698,7 +698,12 @@ int broadcast_event_to_subscriptions(cJSON* event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if event is expired and should not be broadcast (NIP-40)
|
// 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);
|
time_t current_time = time(NULL);
|
||||||
if (is_event_expired(event, current_time)) {
|
if (is_event_expired(event, current_time)) {
|
||||||
char debug_msg[256];
|
char debug_msg[256];
|
||||||
@@ -1480,131 +1485,147 @@ int mark_event_as_deleted(const char* event_id, const char* deletion_event_id, c
|
|||||||
|
|
||||||
// Initialize relay information using configuration system
|
// Initialize relay information using configuration system
|
||||||
void init_relay_info() {
|
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");
|
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");
|
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");
|
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");
|
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");
|
const char* relay_contact = get_config_value("relay_contact");
|
||||||
if (relay_contact) {
|
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||||
strncpy(g_relay_info.contact, relay_contact, sizeof(g_relay_info.contact) - 1);
|
|
||||||
|
// 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) {
|
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
|
// Initialize supported NIPs array
|
||||||
g_relay_info.supported_nips = cJSON_CreateArray();
|
g_unified_cache.relay_info.supported_nips = cJSON_CreateArray();
|
||||||
if (g_relay_info.supported_nips) {
|
if (g_unified_cache.relay_info.supported_nips) {
|
||||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(1)); // NIP-01: Basic protocol
|
cJSON_AddItemToArray(g_unified_cache.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_unified_cache.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_unified_cache.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_unified_cache.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_unified_cache.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_unified_cache.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_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(40)); // NIP-40: Expiration Timestamp
|
||||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(42)); // NIP-42: Authentication
|
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(42)); // NIP-42: Authentication
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize server limitations using configuration
|
// Initialize server limitations using configuration
|
||||||
g_relay_info.limitation = cJSON_CreateObject();
|
g_unified_cache.relay_info.limitation = cJSON_CreateObject();
|
||||||
if (g_relay_info.limitation) {
|
if (g_unified_cache.relay_info.limitation) {
|
||||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_message_length", get_config_int("max_message_length", 16384));
|
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_message_length", max_message_length);
|
||||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_subscriptions", get_config_int("max_subscriptions_per_client", 20));
|
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_subscriptions", max_subscriptions_per_client);
|
||||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_limit", get_config_int("max_limit", 5000));
|
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_limit", max_limit);
|
||||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH);
|
cJSON_AddNumberToObject(g_unified_cache.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_unified_cache.relay_info.limitation, "max_event_tags", max_event_tags);
|
||||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_content_length", get_config_int("max_content_length", 8196));
|
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_content_length", max_content_length);
|
||||||
cJSON_AddNumberToObject(g_relay_info.limitation, "min_pow_difficulty", g_pow_config.min_pow_difficulty);
|
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "min_pow_difficulty", g_unified_cache.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_unified_cache.relay_info.limitation, "auth_required", admin_enabled ? cJSON_True : cJSON_False);
|
||||||
cJSON_AddBoolToObject(g_relay_info.limitation, "payment_required", cJSON_False);
|
cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "payment_required", cJSON_False);
|
||||||
cJSON_AddBoolToObject(g_relay_info.limitation, "restricted_writes", cJSON_False);
|
cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "restricted_writes", cJSON_False);
|
||||||
cJSON_AddNumberToObject(g_relay_info.limitation, "created_at_lower_limit", 0);
|
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "created_at_lower_limit", 0);
|
||||||
cJSON_AddNumberToObject(g_relay_info.limitation, "created_at_upper_limit", 2147483647);
|
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "created_at_upper_limit", 2147483647);
|
||||||
cJSON_AddNumberToObject(g_relay_info.limitation, "default_limit", get_config_int("default_limit", 500));
|
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "default_limit", default_limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize empty retention policies (can be configured later)
|
// 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
|
// Initialize language tags - set to global for now
|
||||||
g_relay_info.language_tags = cJSON_CreateArray();
|
g_unified_cache.relay_info.language_tags = cJSON_CreateArray();
|
||||||
if (g_relay_info.language_tags) {
|
if (g_unified_cache.relay_info.language_tags) {
|
||||||
cJSON_AddItemToArray(g_relay_info.language_tags, cJSON_CreateString("*"));
|
cJSON_AddItemToArray(g_unified_cache.relay_info.language_tags, cJSON_CreateString("*"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize relay countries - set to global for now
|
// Initialize relay countries - set to global for now
|
||||||
g_relay_info.relay_countries = cJSON_CreateArray();
|
g_unified_cache.relay_info.relay_countries = cJSON_CreateArray();
|
||||||
if (g_relay_info.relay_countries) {
|
if (g_unified_cache.relay_info.relay_countries) {
|
||||||
cJSON_AddItemToArray(g_relay_info.relay_countries, cJSON_CreateString("*"));
|
cJSON_AddItemToArray(g_unified_cache.relay_info.relay_countries, cJSON_CreateString("*"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize content tags as empty array
|
// 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)
|
// 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");
|
log_success("Relay information initialized with default values");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up relay information JSON objects
|
// Clean up relay information JSON objects
|
||||||
void cleanup_relay_info() {
|
void cleanup_relay_info() {
|
||||||
if (g_relay_info.supported_nips) {
|
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||||
cJSON_Delete(g_relay_info.supported_nips);
|
if (g_unified_cache.relay_info.supported_nips) {
|
||||||
g_relay_info.supported_nips = NULL;
|
cJSON_Delete(g_unified_cache.relay_info.supported_nips);
|
||||||
|
g_unified_cache.relay_info.supported_nips = NULL;
|
||||||
}
|
}
|
||||||
if (g_relay_info.limitation) {
|
if (g_unified_cache.relay_info.limitation) {
|
||||||
cJSON_Delete(g_relay_info.limitation);
|
cJSON_Delete(g_unified_cache.relay_info.limitation);
|
||||||
g_relay_info.limitation = NULL;
|
g_unified_cache.relay_info.limitation = NULL;
|
||||||
}
|
}
|
||||||
if (g_relay_info.retention) {
|
if (g_unified_cache.relay_info.retention) {
|
||||||
cJSON_Delete(g_relay_info.retention);
|
cJSON_Delete(g_unified_cache.relay_info.retention);
|
||||||
g_relay_info.retention = NULL;
|
g_unified_cache.relay_info.retention = NULL;
|
||||||
}
|
}
|
||||||
if (g_relay_info.language_tags) {
|
if (g_unified_cache.relay_info.language_tags) {
|
||||||
cJSON_Delete(g_relay_info.language_tags);
|
cJSON_Delete(g_unified_cache.relay_info.language_tags);
|
||||||
g_relay_info.language_tags = NULL;
|
g_unified_cache.relay_info.language_tags = NULL;
|
||||||
}
|
}
|
||||||
if (g_relay_info.relay_countries) {
|
if (g_unified_cache.relay_info.relay_countries) {
|
||||||
cJSON_Delete(g_relay_info.relay_countries);
|
cJSON_Delete(g_unified_cache.relay_info.relay_countries);
|
||||||
g_relay_info.relay_countries = NULL;
|
g_unified_cache.relay_info.relay_countries = NULL;
|
||||||
}
|
}
|
||||||
if (g_relay_info.tags) {
|
if (g_unified_cache.relay_info.tags) {
|
||||||
cJSON_Delete(g_relay_info.tags);
|
cJSON_Delete(g_unified_cache.relay_info.tags);
|
||||||
g_relay_info.tags = NULL;
|
g_unified_cache.relay_info.tags = NULL;
|
||||||
}
|
}
|
||||||
if (g_relay_info.fees) {
|
if (g_unified_cache.relay_info.fees) {
|
||||||
cJSON_Delete(g_relay_info.fees);
|
cJSON_Delete(g_unified_cache.relay_info.fees);
|
||||||
g_relay_info.fees = NULL;
|
g_unified_cache.relay_info.fees = NULL;
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate NIP-11 compliant JSON document
|
// Generate NIP-11 compliant JSON document
|
||||||
@@ -1615,79 +1636,83 @@ cJSON* generate_relay_info_json() {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||||
|
|
||||||
// Add basic relay information
|
// Add basic relay information
|
||||||
if (strlen(g_relay_info.name) > 0) {
|
if (strlen(g_unified_cache.relay_info.name) > 0) {
|
||||||
cJSON_AddStringToObject(info, "name", g_relay_info.name);
|
cJSON_AddStringToObject(info, "name", g_unified_cache.relay_info.name);
|
||||||
}
|
}
|
||||||
if (strlen(g_relay_info.description) > 0) {
|
if (strlen(g_unified_cache.relay_info.description) > 0) {
|
||||||
cJSON_AddStringToObject(info, "description", g_relay_info.description);
|
cJSON_AddStringToObject(info, "description", g_unified_cache.relay_info.description);
|
||||||
}
|
}
|
||||||
if (strlen(g_relay_info.banner) > 0) {
|
if (strlen(g_unified_cache.relay_info.banner) > 0) {
|
||||||
cJSON_AddStringToObject(info, "banner", g_relay_info.banner);
|
cJSON_AddStringToObject(info, "banner", g_unified_cache.relay_info.banner);
|
||||||
}
|
}
|
||||||
if (strlen(g_relay_info.icon) > 0) {
|
if (strlen(g_unified_cache.relay_info.icon) > 0) {
|
||||||
cJSON_AddStringToObject(info, "icon", g_relay_info.icon);
|
cJSON_AddStringToObject(info, "icon", g_unified_cache.relay_info.icon);
|
||||||
}
|
}
|
||||||
if (strlen(g_relay_info.pubkey) > 0) {
|
if (strlen(g_unified_cache.relay_info.pubkey) > 0) {
|
||||||
cJSON_AddStringToObject(info, "pubkey", g_relay_info.pubkey);
|
cJSON_AddStringToObject(info, "pubkey", g_unified_cache.relay_info.pubkey);
|
||||||
}
|
}
|
||||||
if (strlen(g_relay_info.contact) > 0) {
|
if (strlen(g_unified_cache.relay_info.contact) > 0) {
|
||||||
cJSON_AddStringToObject(info, "contact", g_relay_info.contact);
|
cJSON_AddStringToObject(info, "contact", g_unified_cache.relay_info.contact);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add supported NIPs
|
// Add supported NIPs
|
||||||
if (g_relay_info.supported_nips) {
|
if (g_unified_cache.relay_info.supported_nips) {
|
||||||
cJSON_AddItemToObject(info, "supported_nips", cJSON_Duplicate(g_relay_info.supported_nips, 1));
|
cJSON_AddItemToObject(info, "supported_nips", cJSON_Duplicate(g_unified_cache.relay_info.supported_nips, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add software information
|
// Add software information
|
||||||
if (strlen(g_relay_info.software) > 0) {
|
if (strlen(g_unified_cache.relay_info.software) > 0) {
|
||||||
cJSON_AddStringToObject(info, "software", g_relay_info.software);
|
cJSON_AddStringToObject(info, "software", g_unified_cache.relay_info.software);
|
||||||
}
|
}
|
||||||
if (strlen(g_relay_info.version) > 0) {
|
if (strlen(g_unified_cache.relay_info.version) > 0) {
|
||||||
cJSON_AddStringToObject(info, "version", g_relay_info.version);
|
cJSON_AddStringToObject(info, "version", g_unified_cache.relay_info.version);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add policies
|
// Add policies
|
||||||
if (strlen(g_relay_info.privacy_policy) > 0) {
|
if (strlen(g_unified_cache.relay_info.privacy_policy) > 0) {
|
||||||
cJSON_AddStringToObject(info, "privacy_policy", g_relay_info.privacy_policy);
|
cJSON_AddStringToObject(info, "privacy_policy", g_unified_cache.relay_info.privacy_policy);
|
||||||
}
|
}
|
||||||
if (strlen(g_relay_info.terms_of_service) > 0) {
|
if (strlen(g_unified_cache.relay_info.terms_of_service) > 0) {
|
||||||
cJSON_AddStringToObject(info, "terms_of_service", g_relay_info.terms_of_service);
|
cJSON_AddStringToObject(info, "terms_of_service", g_unified_cache.relay_info.terms_of_service);
|
||||||
}
|
}
|
||||||
if (strlen(g_relay_info.posting_policy) > 0) {
|
if (strlen(g_unified_cache.relay_info.posting_policy) > 0) {
|
||||||
cJSON_AddStringToObject(info, "posting_policy", g_relay_info.posting_policy);
|
cJSON_AddStringToObject(info, "posting_policy", g_unified_cache.relay_info.posting_policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add server limitations
|
// Add server limitations
|
||||||
if (g_relay_info.limitation) {
|
if (g_unified_cache.relay_info.limitation) {
|
||||||
cJSON_AddItemToObject(info, "limitation", cJSON_Duplicate(g_relay_info.limitation, 1));
|
cJSON_AddItemToObject(info, "limitation", cJSON_Duplicate(g_unified_cache.relay_info.limitation, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add retention policies if configured
|
// Add retention policies if configured
|
||||||
if (g_relay_info.retention && cJSON_GetArraySize(g_relay_info.retention) > 0) {
|
if (g_unified_cache.relay_info.retention && cJSON_GetArraySize(g_unified_cache.relay_info.retention) > 0) {
|
||||||
cJSON_AddItemToObject(info, "retention", cJSON_Duplicate(g_relay_info.retention, 1));
|
cJSON_AddItemToObject(info, "retention", cJSON_Duplicate(g_unified_cache.relay_info.retention, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add geographical and language information
|
// Add geographical and language information
|
||||||
if (g_relay_info.relay_countries) {
|
if (g_unified_cache.relay_info.relay_countries) {
|
||||||
cJSON_AddItemToObject(info, "relay_countries", cJSON_Duplicate(g_relay_info.relay_countries, 1));
|
cJSON_AddItemToObject(info, "relay_countries", cJSON_Duplicate(g_unified_cache.relay_info.relay_countries, 1));
|
||||||
}
|
}
|
||||||
if (g_relay_info.language_tags) {
|
if (g_unified_cache.relay_info.language_tags) {
|
||||||
cJSON_AddItemToObject(info, "language_tags", cJSON_Duplicate(g_relay_info.language_tags, 1));
|
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) {
|
if (g_unified_cache.relay_info.tags && cJSON_GetArraySize(g_unified_cache.relay_info.tags) > 0) {
|
||||||
cJSON_AddItemToObject(info, "tags", cJSON_Duplicate(g_relay_info.tags, 1));
|
cJSON_AddItemToObject(info, "tags", cJSON_Duplicate(g_unified_cache.relay_info.tags, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add payment information if configured
|
// Add payment information if configured
|
||||||
if (strlen(g_relay_info.payments_url) > 0) {
|
if (strlen(g_unified_cache.relay_info.payments_url) > 0) {
|
||||||
cJSON_AddStringToObject(info, "payments_url", g_relay_info.payments_url);
|
cJSON_AddStringToObject(info, "payments_url", g_unified_cache.relay_info.payments_url);
|
||||||
}
|
}
|
||||||
if (g_relay_info.fees && cJSON_GetObjectItem(g_relay_info.fees, "admission")) {
|
if (g_unified_cache.relay_info.fees && cJSON_GetObjectItem(g_unified_cache.relay_info.fees, "admission")) {
|
||||||
cJSON_AddItemToObject(info, "fees", cJSON_Duplicate(g_relay_info.fees, 1));
|
cJSON_AddItemToObject(info, "fees", cJSON_Duplicate(g_unified_cache.relay_info.fees, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1865,34 +1890,40 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
|
|||||||
void init_pow_config() {
|
void init_pow_config() {
|
||||||
log_info("Initializing NIP-13 Proof of Work configuration");
|
log_info("Initializing NIP-13 Proof of Work configuration");
|
||||||
|
|
||||||
// Load PoW settings from configuration system
|
// Get all config values first (without holding mutex to avoid deadlock)
|
||||||
g_pow_config.enabled = get_config_bool("pow_enabled", 1);
|
int pow_enabled = get_config_bool("pow_enabled", 1);
|
||||||
g_pow_config.min_pow_difficulty = get_config_int("pow_min_difficulty", 0);
|
int pow_min_difficulty = get_config_int("pow_min_difficulty", 0);
|
||||||
|
|
||||||
// Get PoW mode from configuration
|
|
||||||
const char* pow_mode = get_config_value("pow_mode");
|
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 (pow_mode) {
|
||||||
if (strcmp(pow_mode, "strict") == 0) {
|
if (strcmp(pow_mode, "strict") == 0) {
|
||||||
g_pow_config.validation_flags = NOSTR_POW_VALIDATE_ANTI_SPAM | NOSTR_POW_STRICT_FORMAT;
|
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_ANTI_SPAM | NOSTR_POW_STRICT_FORMAT;
|
||||||
g_pow_config.require_nonce_tag = 1;
|
g_unified_cache.pow_config.require_nonce_tag = 1;
|
||||||
g_pow_config.reject_lower_targets = 1;
|
g_unified_cache.pow_config.reject_lower_targets = 1;
|
||||||
g_pow_config.strict_format = 1;
|
g_unified_cache.pow_config.strict_format = 1;
|
||||||
g_pow_config.anti_spam_mode = 1;
|
g_unified_cache.pow_config.anti_spam_mode = 1;
|
||||||
log_info("PoW configured in strict anti-spam mode");
|
log_info("PoW configured in strict anti-spam mode");
|
||||||
} else if (strcmp(pow_mode, "full") == 0) {
|
} else if (strcmp(pow_mode, "full") == 0) {
|
||||||
g_pow_config.validation_flags = NOSTR_POW_VALIDATE_FULL;
|
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_FULL;
|
||||||
g_pow_config.require_nonce_tag = 1;
|
g_unified_cache.pow_config.require_nonce_tag = 1;
|
||||||
log_info("PoW configured in full validation mode");
|
log_info("PoW configured in full validation mode");
|
||||||
} else if (strcmp(pow_mode, "basic") == 0) {
|
} 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");
|
log_info("PoW configured in basic validation mode");
|
||||||
} else if (strcmp(pow_mode, "disabled") == 0) {
|
} 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");
|
log_info("PoW validation disabled via configuration");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Default to basic mode
|
// 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)");
|
log_info("PoW configured in basic validation mode (default)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1900,17 +1931,25 @@ void init_pow_config() {
|
|||||||
char config_msg[512];
|
char config_msg[512];
|
||||||
snprintf(config_msg, sizeof(config_msg),
|
snprintf(config_msg, sizeof(config_msg),
|
||||||
"PoW Configuration: enabled=%s, min_difficulty=%d, validation_flags=0x%x, mode=%s",
|
"PoW Configuration: enabled=%s, min_difficulty=%d, validation_flags=0x%x, mode=%s",
|
||||||
g_pow_config.enabled ? "true" : "false",
|
g_unified_cache.pow_config.enabled ? "true" : "false",
|
||||||
g_pow_config.min_pow_difficulty,
|
g_unified_cache.pow_config.min_pow_difficulty,
|
||||||
g_pow_config.validation_flags,
|
g_unified_cache.pow_config.validation_flags,
|
||||||
g_pow_config.anti_spam_mode ? "anti-spam" :
|
g_unified_cache.pow_config.anti_spam_mode ? "anti-spam" :
|
||||||
(g_pow_config.validation_flags & NOSTR_POW_VALIDATE_FULL) ? "full" : "basic");
|
(g_unified_cache.pow_config.validation_flags & NOSTR_POW_VALIDATE_FULL) ? "full" : "basic");
|
||||||
log_info(config_msg);
|
log_info(config_msg);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate event Proof of Work according to NIP-13
|
// Validate event Proof of Work according to NIP-13
|
||||||
int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
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
|
return 0; // PoW validation disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1921,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
|
// If min_pow_difficulty is 0, only validate events that have nonce tags
|
||||||
// This allows events without PoW when difficulty requirement is 0
|
// 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");
|
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||||
int has_nonce_tag = 0;
|
int has_nonce_tag = 0;
|
||||||
|
|
||||||
@@ -1949,8 +1988,8 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
|||||||
|
|
||||||
// Perform PoW validation using nostr_core_lib
|
// Perform PoW validation using nostr_core_lib
|
||||||
nostr_pow_result_t pow_result;
|
nostr_pow_result_t pow_result;
|
||||||
int validation_result = nostr_validate_pow(event, g_pow_config.min_pow_difficulty,
|
int validation_result = nostr_validate_pow(event, min_pow_difficulty,
|
||||||
g_pow_config.validation_flags, &pow_result);
|
validation_flags, &pow_result);
|
||||||
|
|
||||||
if (validation_result != NOSTR_SUCCESS) {
|
if (validation_result != NOSTR_SUCCESS) {
|
||||||
// Handle specific error cases with appropriate messages
|
// Handle specific error cases with appropriate messages
|
||||||
@@ -1958,12 +1997,12 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
|||||||
case NOSTR_ERROR_NIP13_INSUFFICIENT:
|
case NOSTR_ERROR_NIP13_INSUFFICIENT:
|
||||||
snprintf(error_message, error_size,
|
snprintf(error_message, error_size,
|
||||||
"pow: insufficient difficulty: %d < %d",
|
"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");
|
log_warning("Event rejected: insufficient PoW difficulty");
|
||||||
break;
|
break;
|
||||||
case NOSTR_ERROR_NIP13_NO_NONCE_TAG:
|
case NOSTR_ERROR_NIP13_NO_NONCE_TAG:
|
||||||
// This should not happen with min_difficulty=0 after our check above
|
// 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");
|
snprintf(error_message, error_size, "pow: missing required nonce tag");
|
||||||
log_warning("Event rejected: missing nonce tag");
|
log_warning("Event rejected: missing nonce tag");
|
||||||
} else {
|
} else {
|
||||||
@@ -1977,7 +2016,7 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
|||||||
case NOSTR_ERROR_NIP13_TARGET_MISMATCH:
|
case NOSTR_ERROR_NIP13_TARGET_MISMATCH:
|
||||||
snprintf(error_message, error_size,
|
snprintf(error_message, error_size,
|
||||||
"pow: committed target (%d) lower than minimum (%d)",
|
"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)");
|
log_warning("Event rejected: committed target too low (anti-spam protection)");
|
||||||
break;
|
break;
|
||||||
case NOSTR_ERROR_NIP13_CALCULATION:
|
case NOSTR_ERROR_NIP13_CALCULATION:
|
||||||
@@ -1997,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)
|
// 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];
|
char debug_msg[256];
|
||||||
snprintf(debug_msg, sizeof(debug_msg),
|
snprintf(debug_msg, sizeof(debug_msg),
|
||||||
"PoW validated: difficulty=%d, target=%d, nonce=%llu%s",
|
"PoW validated: difficulty=%d, target=%d, nonce=%llu%s",
|
||||||
@@ -2021,28 +2060,39 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
|||||||
void init_expiration_config() {
|
void init_expiration_config() {
|
||||||
log_info("Initializing NIP-40 Expiration Timestamp configuration");
|
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
|
// Load expiration settings from configuration system
|
||||||
g_expiration_config.enabled = get_config_bool("expiration_enabled", 1);
|
g_unified_cache.expiration_config.enabled = expiration_enabled;
|
||||||
g_expiration_config.strict_mode = get_config_bool("expiration_strict", 1);
|
g_unified_cache.expiration_config.strict_mode = expiration_strict;
|
||||||
g_expiration_config.filter_responses = get_config_bool("expiration_filter", 1);
|
g_unified_cache.expiration_config.filter_responses = expiration_filter;
|
||||||
g_expiration_config.delete_expired = get_config_bool("expiration_delete", 0);
|
g_unified_cache.expiration_config.delete_expired = expiration_delete;
|
||||||
g_expiration_config.grace_period = get_config_int("expiration_grace_period", 1);
|
g_unified_cache.expiration_config.grace_period = expiration_grace_period;
|
||||||
|
|
||||||
// Validate grace period bounds
|
// 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");
|
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
|
// Log final configuration
|
||||||
char config_msg[512];
|
char config_msg[512];
|
||||||
snprintf(config_msg, sizeof(config_msg),
|
snprintf(config_msg, sizeof(config_msg),
|
||||||
"Expiration Configuration: enabled=%s, strict_mode=%s, filter_responses=%s, grace_period=%ld seconds",
|
"Expiration Configuration: enabled=%s, strict_mode=%s, filter_responses=%s, grace_period=%ld seconds",
|
||||||
g_expiration_config.enabled ? "true" : "false",
|
g_unified_cache.expiration_config.enabled ? "true" : "false",
|
||||||
g_expiration_config.strict_mode ? "true" : "false",
|
g_unified_cache.expiration_config.strict_mode ? "true" : "false",
|
||||||
g_expiration_config.filter_responses ? "true" : "false",
|
g_unified_cache.expiration_config.filter_responses ? "true" : "false",
|
||||||
g_expiration_config.grace_period);
|
g_unified_cache.expiration_config.grace_period);
|
||||||
log_info(config_msg);
|
log_info(config_msg);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract expiration timestamp from event tags
|
// Extract expiration timestamp from event tags
|
||||||
@@ -2112,12 +2162,22 @@ int is_event_expired(cJSON* event, time_t current_time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if current time exceeds expiration + grace period
|
// 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
|
// Validate event expiration according to NIP-40
|
||||||
int validate_event_expiration(cJSON* event, char* error_message, size_t error_size) {
|
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
|
return 0; // Expiration validation disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2129,13 +2189,13 @@ int validate_event_expiration(cJSON* event, char* error_message, size_t error_si
|
|||||||
// Check if event is expired
|
// Check if event is expired
|
||||||
time_t current_time = time(NULL);
|
time_t current_time = time(NULL);
|
||||||
if (is_event_expired(event, current_time)) {
|
if (is_event_expired(event, current_time)) {
|
||||||
if (g_expiration_config.strict_mode) {
|
if (strict_mode) {
|
||||||
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||||
long expiration_ts = extract_expiration_timestamp(tags);
|
long expiration_ts = extract_expiration_timestamp(tags);
|
||||||
|
|
||||||
snprintf(error_message, error_size,
|
snprintf(error_message, error_size,
|
||||||
"invalid: event expired (expiration=%ld, current=%ld, grace=%ld)",
|
"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");
|
log_warning("Event rejected: expired timestamp");
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
@@ -2631,6 +2691,54 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
|||||||
return 0;
|
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
|
// Check session subscription limits
|
||||||
if (pss && pss->subscription_count >= g_subscription_manager.max_subscriptions_per_client) {
|
if (pss && pss->subscription_count >= g_subscription_manager.max_subscriptions_per_client) {
|
||||||
log_error("Maximum subscriptions per client exceeded");
|
log_error("Maximum subscriptions per client exceeded");
|
||||||
@@ -2654,14 +2762,14 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
|||||||
}
|
}
|
||||||
cJSON_Delete(closed_msg);
|
cJSON_Delete(closed_msg);
|
||||||
|
|
||||||
return 0;
|
return has_config_request ? config_events_sent : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create persistent subscription
|
// Create persistent subscription
|
||||||
subscription_t* subscription = create_subscription(sub_id, wsi, filters, pss ? pss->client_ip : "unknown");
|
subscription_t* subscription = create_subscription(sub_id, wsi, filters, pss ? pss->client_ip : "unknown");
|
||||||
if (!subscription) {
|
if (!subscription) {
|
||||||
log_error("Failed to create subscription");
|
log_error("Failed to create subscription");
|
||||||
return 0;
|
return has_config_request ? config_events_sent : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to global manager
|
// Add to global manager
|
||||||
@@ -2688,7 +2796,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
|||||||
}
|
}
|
||||||
cJSON_Delete(closed_msg);
|
cJSON_Delete(closed_msg);
|
||||||
|
|
||||||
return 0;
|
return has_config_request ? config_events_sent : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to session's subscription list (if session data available)
|
// Add to session's subscription list (if session data available)
|
||||||
@@ -2700,7 +2808,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
|||||||
pthread_mutex_unlock(&pss->session_lock);
|
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
|
// Process each filter in the array
|
||||||
for (int i = 0; i < cJSON_GetArraySize(filters); i++) {
|
for (int i = 0; i < cJSON_GetArraySize(filters); i++) {
|
||||||
@@ -2847,7 +2955,12 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
|||||||
cJSON_AddItemToObject(event, "tags", tags);
|
cJSON_AddItemToObject(event, "tags", tags);
|
||||||
|
|
||||||
// Check expiration filtering (NIP-40) at application level
|
// 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);
|
time_t current_time = time(NULL);
|
||||||
if (is_event_expired(event, current_time)) {
|
if (is_event_expired(event, current_time)) {
|
||||||
// Skip this expired event
|
// Skip this expired event
|
||||||
@@ -2958,8 +3071,13 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
// Get real client IP address
|
// Get real client IP address
|
||||||
char client_ip[CLIENT_IP_MAX_LENGTH];
|
char client_ip[CLIENT_IP_MAX_LENGTH];
|
||||||
lws_get_peer_simple(wsi, client_ip, sizeof(client_ip));
|
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
|
// Initialize NIP-42 authentication state
|
||||||
pss->authenticated = 0;
|
pss->authenticated = 0;
|
||||||
@@ -3109,7 +3227,10 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
if (process_admin_event_in_config(event, admin_error, sizeof(admin_error)) != 0) {
|
if (process_admin_event_in_config(event, admin_error, sizeof(admin_error)) != 0) {
|
||||||
log_error("Failed to process admin event through admin API");
|
log_error("Failed to process admin event through admin API");
|
||||||
result = -1;
|
result = -1;
|
||||||
strncpy(error_message, admin_error, sizeof(error_message) - 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 {
|
} else {
|
||||||
log_success("Admin event processed successfully through admin API");
|
log_success("Admin event processed successfully through admin API");
|
||||||
// Admin events are processed by the admin API, not broadcast to subscriptions
|
// Admin events are processed by the admin API, not broadcast to subscriptions
|
||||||
@@ -3684,10 +3805,29 @@ int main(int argc, char* argv[]) {
|
|||||||
return 1;
|
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
|
// Retry storing the configuration event now that database is initialized
|
||||||
if (retry_store_initial_config_event() != 0) {
|
if (retry_store_initial_config_event() != 0) {
|
||||||
log_warning("Failed to store initial configuration event after database init");
|
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 {
|
} else {
|
||||||
log_info("Existing relay detected");
|
log_info("Existing relay detected");
|
||||||
|
|
||||||
@@ -3757,6 +3897,21 @@ int main(int argc, char* argv[]) {
|
|||||||
log_warning("Failed to apply configuration from database");
|
log_warning("Failed to apply configuration from database");
|
||||||
} else {
|
} else {
|
||||||
log_success("Configuration loaded from database");
|
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);
|
cJSON_Delete(config_event);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -132,24 +132,11 @@ typedef struct {
|
|||||||
int time_tolerance_seconds;
|
int time_tolerance_seconds;
|
||||||
} nip42_challenge_manager_t;
|
} 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
|
// 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 nip42_challenge_manager_t g_challenge_manager = {0};
|
||||||
static int g_validator_initialized = 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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize NIP-42 challenge manager
|
// Initialize NIP-42 challenge manager using unified config
|
||||||
memset(&g_challenge_manager, 0, sizeof(g_challenge_manager));
|
memset(&g_challenge_manager, 0, sizeof(g_challenge_manager));
|
||||||
g_challenge_manager.timeout_seconds =
|
|
||||||
g_auth_cache.nip42_challenge_timeout > 0
|
const char* nip42_timeout = get_config_value("nip42_challenge_timeout");
|
||||||
? g_auth_cache.nip42_challenge_timeout
|
g_challenge_manager.timeout_seconds = nip42_timeout ? atoi(nip42_timeout) : 600;
|
||||||
: 600;
|
|
||||||
g_challenge_manager.time_tolerance_seconds =
|
const char* nip42_tolerance = get_config_value("nip42_time_tolerance");
|
||||||
g_auth_cache.nip42_time_tolerance > 0 ? g_auth_cache.nip42_time_tolerance
|
g_challenge_manager.time_tolerance_seconds = nip42_tolerance ? atoi(nip42_tolerance) : 300;
|
||||||
: 300;
|
|
||||||
g_challenge_manager.last_cleanup = time(NULL);
|
g_challenge_manager.last_cleanup = time(NULL);
|
||||||
|
|
||||||
g_validator_initialized = 1;
|
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
|
* Check if authentication rules are enabled
|
||||||
*/
|
*/
|
||||||
int nostr_auth_rules_enabled(void) {
|
int nostr_auth_rules_enabled(void) {
|
||||||
// Reload config if cache expired
|
// Use unified cache from config.c
|
||||||
if (!g_auth_cache.cache_valid || time(NULL) > g_auth_cache.cache_expires) {
|
const char* auth_enabled = get_config_value("auth_enabled");
|
||||||
reload_auth_config();
|
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);
|
int event_kind = (int)cJSON_GetNumberValue(kind);
|
||||||
|
|
||||||
// 5. Reload config if needed
|
// 5. Check configuration using unified cache
|
||||||
if (!g_auth_cache.cache_valid || time(NULL) > g_auth_cache.cache_expires) {
|
int auth_required = nostr_auth_rules_enabled();
|
||||||
reload_auth_config();
|
|
||||||
}
|
|
||||||
|
|
||||||
char config_msg[256];
|
char config_msg[256];
|
||||||
sprintf(config_msg, "VALIDATOR_DEBUG: STEP 5 PASSED - Event kind: %d, auth_required: %d\n",
|
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);
|
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) {
|
if (event_kind == 22242) {
|
||||||
validator_debug_log("VALIDATOR_DEBUG: STEP 8 - Processing NIP-42 challenge response\n");
|
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");
|
validator_debug_log("VALIDATOR_DEBUG: STEP 8 FAILED - NIP-42 is disabled\n");
|
||||||
cJSON_Delete(event);
|
cJSON_Delete(event);
|
||||||
return NOSTR_ERROR_NIP42_DISABLED;
|
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
|
// 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");
|
validator_debug_log("VALIDATOR_DEBUG: STEP 9 - Authentication disabled, skipping database auth rules\n");
|
||||||
} else {
|
} else {
|
||||||
// 10. Check database authentication rules (only if auth enabled)
|
// 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
|
// 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");
|
validator_debug_log("VALIDATOR_DEBUG: STEP 11 - Validating NIP-13 Proof of Work\n");
|
||||||
|
|
||||||
nostr_pow_result_t pow_result;
|
nostr_pow_result_t pow_result;
|
||||||
int pow_validation_result = nostr_validate_pow(event, g_pow_config.min_pow_difficulty,
|
int pow_validation_result = nostr_validate_pow(event, pow_min_difficulty,
|
||||||
g_pow_config.validation_flags, &pow_result);
|
pow_validation_flags, &pow_result);
|
||||||
|
|
||||||
if (pow_validation_result != NOSTR_SUCCESS) {
|
if (pow_validation_result != NOSTR_SUCCESS) {
|
||||||
char pow_msg[256];
|
char pow_msg[256];
|
||||||
sprintf(pow_msg, "VALIDATOR_DEBUG: STEP 11 FAILED - PoW validation failed (error=%d, difficulty=%d/%d)\n",
|
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);
|
validator_debug_log(pow_msg);
|
||||||
cJSON_Delete(event);
|
cJSON_Delete(event);
|
||||||
return pow_validation_result;
|
return pow_validation_result;
|
||||||
@@ -553,7 +549,6 @@ void nostr_request_validator_clear_violation(void) {
|
|||||||
*/
|
*/
|
||||||
void ginxsom_request_validator_cleanup(void) {
|
void ginxsom_request_validator_cleanup(void) {
|
||||||
g_validator_initialized = 0;
|
g_validator_initialized = 0;
|
||||||
memset(&g_auth_cache, 0, sizeof(g_auth_cache));
|
|
||||||
nostr_request_validator_clear_violation();
|
nostr_request_validator_clear_violation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,145 +568,22 @@ void nostr_request_result_free_file_data(nostr_request_result_t *result) {
|
|||||||
// HELPER FUNCTIONS
|
// 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) {
|
void nostr_request_validator_force_cache_refresh(void) {
|
||||||
g_auth_cache.cache_valid = 0;
|
// Use unified cache refresh from config.c
|
||||||
g_auth_cache.cache_expires = 0;
|
force_config_cache_refresh();
|
||||||
validator_debug_log("VALIDATOR: Cache forcibly invalidated\n");
|
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) {
|
static int reload_auth_config(void) {
|
||||||
sqlite3 *db = NULL;
|
// Configuration is now handled by the unified cache in config.c
|
||||||
sqlite3_stmt *stmt = NULL;
|
validator_debug_log("VALIDATOR: Using unified cache system for configuration\n");
|
||||||
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);
|
|
||||||
|
|
||||||
return NOSTR_SUCCESS;
|
return NOSTR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user