Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fd3e531c3 | ||
|
|
c1c05991cf |
@@ -27,7 +27,7 @@
|
||||
## Critical Integration Issues
|
||||
|
||||
### Event-Based Configuration System
|
||||
- **No traditional config files** - all configuration stored as kind 33334 Nostr events
|
||||
- **No traditional config files** - all configuration stored in config table
|
||||
- Admin private key shown **only once** on first startup
|
||||
- Configuration changes require cryptographically signed events
|
||||
- Database path determined by generated relay pubkey
|
||||
@@ -35,7 +35,7 @@
|
||||
### First-Time Startup Sequence
|
||||
1. Relay generates admin keypair and relay keypair
|
||||
2. Creates database file with relay pubkey as filename
|
||||
3. Stores default configuration as kind 33334 event
|
||||
3. Stores default configuration in config table
|
||||
4. **CRITICAL**: Admin private key displayed once and never stored on disk
|
||||
|
||||
### Port Management
|
||||
@@ -51,7 +51,7 @@
|
||||
### Configuration Event Structure
|
||||
```json
|
||||
{
|
||||
"kind": 33334,
|
||||
"kind": 23455,
|
||||
"content": "C Nostr Relay Configuration",
|
||||
"tags": [
|
||||
["d", "<relay_pubkey>"],
|
||||
|
||||
@@ -76,6 +76,7 @@ All commands are sent as nip44 encrypted content. The following table lists all
|
||||
| **Auth Rules Management** |
|
||||
| `auth_add_blacklist` | `["blacklist", "pubkey", "abc123..."]` | Add pubkey to blacklist |
|
||||
| `auth_add_whitelist` | `["whitelist", "pubkey", "def456..."]` | Add pubkey to whitelist |
|
||||
| `auth_delete_rule` | `["delete_auth_rule", "blacklist", "pubkey", "abc123..."]` | Delete specific auth rule |
|
||||
| `auth_query_all` | `["auth_query", "all"]` | Query all auth rules |
|
||||
| `auth_query_type` | `["auth_query", "whitelist"]` | Query specific rule type |
|
||||
| `auth_query_pattern` | `["auth_query", "pattern", "abc123..."]` | Query specific pattern |
|
||||
|
||||
341
api/index.html
341
api/index.html
@@ -33,7 +33,6 @@
|
||||
h2 {
|
||||
margin: 30px 0 15px 0;
|
||||
font-weight: normal;
|
||||
border-left: 4px solid black;
|
||||
padding-left: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
@@ -438,50 +437,27 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Streamlined Auth Rule Input Sections -->
|
||||
<!-- Simplified Auth Rule Input Section -->
|
||||
<div id="authRuleInputSections" style="display: block;">
|
||||
|
||||
<!-- Blacklist Section -->
|
||||
<!-- Combined Pubkey Auth Rule Section -->
|
||||
<div class="auth-rule-section">
|
||||
<h3>BLACKLIST PUBKEY</h3>
|
||||
<p>Block a specific user from all operations</p>
|
||||
<h3>MANAGE PUBKEY ACCESS</h3>
|
||||
<p>Add pubkeys to whitelist (allow) or blacklist (deny) access</p>
|
||||
<div class="input-group">
|
||||
<label for="blacklistPubkey">Pubkey (nsec or hex):</label>
|
||||
<input type="text" id="blacklistPubkey" placeholder="nsec1... or 64-character hex pubkey">
|
||||
<small id="blacklistHelp">Enter nsec (will auto-convert) or 64-character hex pubkey</small>
|
||||
</div>
|
||||
<button type="button" id="addBlacklistBtn" onclick="addBlacklistRule()">ADD TO BLACKLIST</button>
|
||||
<div id="blacklistStatus" class="rule-status"></div>
|
||||
</div>
|
||||
|
||||
<!-- Whitelist Section -->
|
||||
<div class="auth-rule-section">
|
||||
<h3>WHITELIST PUBKEY</h3>
|
||||
<p>Allow only specific users (converts relay to whitelist-only mode)</p>
|
||||
<div class="input-group">
|
||||
<label for="whitelistPubkey">Pubkey (nsec or hex):</label>
|
||||
<input type="text" id="whitelistPubkey" placeholder="nsec1... or 64-character hex pubkey">
|
||||
<small id="whitelistHelp">Enter nsec (will auto-convert) or 64-character hex pubkey</small>
|
||||
<label for="authRulePubkey">Pubkey (nsec or hex):</label>
|
||||
<input type="text" id="authRulePubkey" placeholder="nsec1... or 64-character hex pubkey">
|
||||
<small id="authRuleHelp">Enter nsec (will auto-convert) or 64-character hex pubkey</small>
|
||||
</div>
|
||||
<div id="whitelistWarning" class="warning-box" style="display: none;">
|
||||
<strong>⚠️ WARNING:</strong> Adding whitelist rules changes relay behavior to whitelist-only mode.
|
||||
Only whitelisted users will be able to interact with the relay.
|
||||
</div>
|
||||
<button type="button" id="addWhitelistBtn" onclick="addWhitelistRule()">ADD TO WHITELIST</button>
|
||||
<div id="whitelistStatus" class="rule-status"></div>
|
||||
</div>
|
||||
|
||||
<!-- Hash Blacklist Section -->
|
||||
<div class="auth-rule-section">
|
||||
<h3>BLACKLIST CONTENT HASH</h3>
|
||||
<p>Block specific content by SHA-256 hash</p>
|
||||
<div class="input-group">
|
||||
<label for="hashBlacklist">Content Hash (SHA-256):</label>
|
||||
<input type="text" id="hashBlacklist" placeholder="64-character hex SHA-256 hash">
|
||||
<small id="hashHelp">Enter 64-character hex SHA-256 hash of content to block</small>
|
||||
<div class="inline-buttons">
|
||||
<button type="button" id="addWhitelistBtn" onclick="addWhitelistRule()">ADD TO WHITELIST</button>
|
||||
<button type="button" id="addBlacklistBtn" onclick="addBlacklistRule()">ADD TO BLACKLIST</button>
|
||||
</div>
|
||||
<button type="button" id="addHashBlacklistBtn" onclick="addHashBlacklistRule()">BLOCK CONTENT HASH</button>
|
||||
<div id="hashStatus" class="rule-status"></div>
|
||||
<div id="authRuleStatus" class="rule-status"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -978,9 +954,9 @@
|
||||
|
||||
// Subscribe to kind 23457 events (admin response events)
|
||||
const subscription = relayPool.subscribeMany([url], [{
|
||||
// kinds: [23457],
|
||||
// authors: [getRelayPubkey()], // Only listen to responses from the relay
|
||||
// "#p": [userPubkey], // Only responses directed to this user
|
||||
kinds: [23457],
|
||||
authors: [getRelayPubkey()], // Only listen to responses from the relay
|
||||
"#p": [userPubkey], // Only responses directed to this user
|
||||
limit: 50
|
||||
}], {
|
||||
onevent(event) {
|
||||
@@ -1133,12 +1109,25 @@
|
||||
console.log('Total results:', responseData.total_results);
|
||||
console.log('Data:', responseData.data);
|
||||
|
||||
// Update the current auth rules with the response data
|
||||
if (responseData.data && Array.isArray(responseData.data)) {
|
||||
currentAuthRules = responseData.data;
|
||||
displayAuthRules(currentAuthRules);
|
||||
updateAuthRulesStatus('loaded');
|
||||
log(`Loaded ${responseData.total_results} auth rules from relay`, 'INFO');
|
||||
} else {
|
||||
currentAuthRules = [];
|
||||
displayAuthRules(currentAuthRules);
|
||||
updateAuthRulesStatus('loaded');
|
||||
log('No auth rules found on relay', 'INFO');
|
||||
}
|
||||
|
||||
if (typeof logTestEvent === 'function') {
|
||||
logTestEvent('RECV', `Auth query response: ${responseData.query_type}, ${responseData.total_results} results`, 'AUTH_QUERY');
|
||||
|
||||
if (responseData.data && responseData.data.length > 0) {
|
||||
responseData.data.forEach((rule, index) => {
|
||||
logTestEvent('RECV', `Rule ${index + 1}: ${rule.rule_type} - ${rule.rule_target}`, 'AUTH_RULE');
|
||||
logTestEvent('RECV', `Rule ${index + 1}: ${rule.rule_type} - ${rule.pattern_value || rule.rule_target}`, 'AUTH_RULE');
|
||||
});
|
||||
} else {
|
||||
logTestEvent('RECV', 'No auth rules found', 'AUTH_QUERY');
|
||||
@@ -1152,6 +1141,30 @@
|
||||
console.log('Command:', responseData.command);
|
||||
console.log('Status:', responseData.status);
|
||||
|
||||
// Handle delete auth rule responses
|
||||
if (responseData.command === 'delete_auth_rule') {
|
||||
if (responseData.status === 'success') {
|
||||
log('Auth rule deleted successfully', 'INFO');
|
||||
// Refresh the auth rules display
|
||||
loadAuthRules();
|
||||
} else {
|
||||
log(`Failed to delete auth rule: ${responseData.message || 'Unknown error'}`, 'ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
// Handle clear all auth rules responses
|
||||
if (responseData.command === 'clear_all_auth_rules') {
|
||||
if (responseData.status === 'success') {
|
||||
const rulesCleared = responseData.rules_cleared || 0;
|
||||
log(`Successfully cleared ${rulesCleared} auth rules`, 'INFO');
|
||||
// Clear local auth rules and refresh display
|
||||
currentAuthRules = [];
|
||||
displayAuthRules(currentAuthRules);
|
||||
} else {
|
||||
log(`Failed to clear auth rules: ${responseData.message || 'Unknown error'}`, 'ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof logTestEvent === 'function') {
|
||||
logTestEvent('RECV', `System command response: ${responseData.command} - ${responseData.status}`, 'SYSTEM_CMD');
|
||||
}
|
||||
@@ -1163,12 +1176,25 @@
|
||||
console.log('Operation:', responseData.operation);
|
||||
console.log('Status:', responseData.status);
|
||||
|
||||
// Handle auth rule addition/modification responses
|
||||
if (responseData.status === 'success') {
|
||||
const rulesProcessed = responseData.rules_processed || 0;
|
||||
log(`Successfully processed ${rulesProcessed} auth rule modifications`, 'INFO');
|
||||
|
||||
// Refresh the auth rules display to show the new rules
|
||||
if (authRulesTableContainer && authRulesTableContainer.style.display !== 'none') {
|
||||
loadAuthRules();
|
||||
}
|
||||
} else {
|
||||
log(`Failed to process auth rule modifications: ${responseData.message || 'Unknown error'}`, 'ERROR');
|
||||
}
|
||||
|
||||
if (typeof logTestEvent === 'function') {
|
||||
logTestEvent('RECV', `Auth rule response: ${responseData.operation} - ${responseData.status}`, 'AUTH_RULE');
|
||||
|
||||
if (responseData.processed_rules) {
|
||||
responseData.processed_rules.forEach((rule, index) => {
|
||||
logTestEvent('RECV', `Processed rule ${index + 1}: ${rule.rule_type} - ${rule.rule_target}`, 'AUTH_RULE');
|
||||
logTestEvent('RECV', `Processed rule ${index + 1}: ${rule.rule_type} - ${rule.pattern_value || rule.rule_target}`, 'AUTH_RULE');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1646,23 +1672,61 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Load auth rules from relay (placeholder - will be implemented with WebSocket)
|
||||
// Load auth rules from relay using admin API
|
||||
async function loadAuthRules() {
|
||||
try {
|
||||
log('Loading auth rules...', 'INFO');
|
||||
log('Loading auth rules via admin API...', 'INFO');
|
||||
updateAuthRulesStatus('loading');
|
||||
|
||||
// TODO: Implement actual auth rules loading via WebSocket/HTTP
|
||||
// For now, show empty state
|
||||
currentAuthRules = [];
|
||||
displayAuthRules(currentAuthRules);
|
||||
updateAuthRulesStatus('loaded');
|
||||
if (!isLoggedIn || !userPubkey) {
|
||||
throw new Error('Must be logged in to load auth rules');
|
||||
}
|
||||
|
||||
if (!relayPool) {
|
||||
throw new Error('SimplePool connection not available');
|
||||
}
|
||||
|
||||
// Create command array for getting all auth rules
|
||||
const command_array = '["auth_query", "all"]';
|
||||
|
||||
log('Auth rules loaded (placeholder implementation)', 'INFO');
|
||||
// Encrypt the command content using NIP-44
|
||||
const encrypted_content = await encryptForRelay(command_array);
|
||||
if (!encrypted_content) {
|
||||
throw new Error('Failed to encrypt auth query command');
|
||||
}
|
||||
|
||||
// Create kind 23456 admin event
|
||||
const authEvent = {
|
||||
kind: 23456,
|
||||
pubkey: userPubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [
|
||||
["p", getRelayPubkey()]
|
||||
],
|
||||
content: encrypted_content
|
||||
};
|
||||
|
||||
// Sign the event
|
||||
const signedEvent = await window.nostr.signEvent(authEvent);
|
||||
if (!signedEvent || !signedEvent.sig) {
|
||||
throw new Error('Event signing failed');
|
||||
}
|
||||
|
||||
log('Sending auth rules query to relay...', 'INFO');
|
||||
|
||||
// Publish via SimplePool
|
||||
const url = relayUrl.value.trim();
|
||||
const publishPromises = relayPool.publish([url], signedEvent);
|
||||
await Promise.any(publishPromises);
|
||||
|
||||
log('Auth rules query sent successfully - waiting for response...', 'INFO');
|
||||
updateAuthRulesStatus('loaded');
|
||||
|
||||
} catch (error) {
|
||||
log(`Failed to load auth rules: ${error.message}`, 'ERROR');
|
||||
updateAuthRulesStatus('error');
|
||||
currentAuthRules = [];
|
||||
displayAuthRules(currentAuthRules);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1747,7 +1811,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Delete auth rule
|
||||
// Delete auth rule using admin API
|
||||
async function deleteAuthRule(index) {
|
||||
if (index < 0 || index >= currentAuthRules.length) return;
|
||||
|
||||
@@ -1759,12 +1823,57 @@
|
||||
try {
|
||||
log(`Deleting auth rule: ${rule.rule_type} - ${rule.pattern_value || rule.rule_target}`, 'INFO');
|
||||
|
||||
// TODO: Implement actual rule deletion via WebSocket kind 23456 event
|
||||
// For now, just remove from local array
|
||||
if (!isLoggedIn || !userPubkey) {
|
||||
throw new Error('Must be logged in to delete auth rules');
|
||||
}
|
||||
|
||||
if (!relayPool) {
|
||||
throw new Error('SimplePool connection not available');
|
||||
}
|
||||
|
||||
// Create command array for deleting auth rule
|
||||
// Format: ["system_command", "delete_auth_rule", rule_type, pattern_type, pattern_value]
|
||||
const rule_type = rule.rule_type;
|
||||
const pattern_type = rule.pattern_type || 'pubkey';
|
||||
const pattern_value = rule.pattern_value || rule.rule_target;
|
||||
|
||||
const command_array = `["system_command", "delete_auth_rule", "${rule_type}", "${pattern_type}", "${pattern_value}"]`;
|
||||
|
||||
// Encrypt the command content using NIP-44
|
||||
const encrypted_content = await encryptForRelay(command_array);
|
||||
if (!encrypted_content) {
|
||||
throw new Error('Failed to encrypt delete auth rule command');
|
||||
}
|
||||
|
||||
// Create kind 23456 admin event
|
||||
const authEvent = {
|
||||
kind: 23456,
|
||||
pubkey: userPubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [
|
||||
["p", getRelayPubkey()]
|
||||
],
|
||||
content: encrypted_content
|
||||
};
|
||||
|
||||
// Sign the event
|
||||
const signedEvent = await window.nostr.signEvent(authEvent);
|
||||
if (!signedEvent || !signedEvent.sig) {
|
||||
throw new Error('Event signing failed');
|
||||
}
|
||||
|
||||
log('Sending delete auth rule command to relay...', 'INFO');
|
||||
|
||||
// Publish via SimplePool
|
||||
const url = relayUrl.value.trim();
|
||||
const publishPromises = relayPool.publish([url], signedEvent);
|
||||
await Promise.any(publishPromises);
|
||||
|
||||
log('Delete auth rule command sent successfully - waiting for response...', 'INFO');
|
||||
|
||||
// Remove from local array immediately for UI responsiveness
|
||||
currentAuthRules.splice(index, 1);
|
||||
displayAuthRules(currentAuthRules);
|
||||
|
||||
log('Auth rule deleted (placeholder implementation)', 'INFO');
|
||||
|
||||
} catch (error) {
|
||||
log(`Failed to delete auth rule: ${error.message}`, 'ERROR');
|
||||
@@ -1953,10 +2062,10 @@
|
||||
return null; // Invalid format
|
||||
}
|
||||
|
||||
// Add blacklist rule
|
||||
// Add blacklist rule (updated to use combined input)
|
||||
function addBlacklistRule() {
|
||||
const input = document.getElementById('blacklistPubkey');
|
||||
const statusDiv = document.getElementById('blacklistStatus');
|
||||
const input = document.getElementById('authRulePubkey');
|
||||
const statusDiv = document.getElementById('authRuleStatus');
|
||||
|
||||
if (!input || !statusDiv) return;
|
||||
|
||||
@@ -2011,10 +2120,10 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Add whitelist rule
|
||||
// Add whitelist rule (updated to use combined input)
|
||||
function addWhitelistRule() {
|
||||
const input = document.getElementById('whitelistPubkey');
|
||||
const statusDiv = document.getElementById('whitelistStatus');
|
||||
const input = document.getElementById('authRulePubkey');
|
||||
const statusDiv = document.getElementById('authRuleStatus');
|
||||
const warningDiv = document.getElementById('whitelistWarning');
|
||||
|
||||
if (!input || !statusDiv) return;
|
||||
@@ -2041,6 +2150,11 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Show whitelist warning
|
||||
if (warningDiv) {
|
||||
warningDiv.style.display = 'block';
|
||||
}
|
||||
|
||||
statusDiv.className = 'rule-status';
|
||||
statusDiv.textContent = 'Adding to whitelist...';
|
||||
|
||||
@@ -2070,57 +2184,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Add hash blacklist rule
|
||||
function addHashBlacklistRule() {
|
||||
const input = document.getElementById('hashBlacklist');
|
||||
const statusDiv = document.getElementById('hashStatus');
|
||||
|
||||
if (!input || !statusDiv) return;
|
||||
|
||||
const inputValue = input.value.trim();
|
||||
if (!inputValue) {
|
||||
statusDiv.className = 'rule-status error';
|
||||
statusDiv.textContent = 'Please enter a SHA-256 hash';
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate hash format (64-char hex)
|
||||
if (!/^[0-9a-fA-F]{64}$/.test(inputValue)) {
|
||||
statusDiv.className = 'rule-status error';
|
||||
statusDiv.textContent = 'Invalid hash format. Must be 64-character hex SHA-256 hash';
|
||||
return;
|
||||
}
|
||||
|
||||
statusDiv.className = 'rule-status';
|
||||
statusDiv.textContent = 'Adding content hash to blacklist...';
|
||||
|
||||
// Create auth rule data
|
||||
const ruleData = {
|
||||
rule_type: 'hash_blacklist',
|
||||
pattern_type: 'Global',
|
||||
pattern_value: inputValue.toLowerCase(), // Normalize to lowercase
|
||||
action: 'deny'
|
||||
};
|
||||
|
||||
// Add to WebSocket queue for processing
|
||||
addAuthRuleViaWebSocket(ruleData)
|
||||
.then(() => {
|
||||
statusDiv.className = 'rule-status success';
|
||||
statusDiv.textContent = `Content hash ${inputValue.substring(0, 16)}... added to blacklist`;
|
||||
input.value = '';
|
||||
|
||||
// Refresh auth rules display if visible
|
||||
if (authRulesTableContainer && authRulesTableContainer.style.display !== 'none') {
|
||||
loadAuthRules();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
statusDiv.className = 'rule-status error';
|
||||
statusDiv.textContent = `Failed to add rule: ${error.message}`;
|
||||
});
|
||||
}
|
||||
|
||||
// Add auth rule via SimplePool (kind 23456 event)
|
||||
// Add auth rule via SimplePool (kind 23456 event) - FIXED to match working test pattern
|
||||
async function addAuthRuleViaWebSocket(ruleData) {
|
||||
if (!isLoggedIn || !userPubkey) {
|
||||
throw new Error('Must be logged in to add auth rules');
|
||||
@@ -2133,60 +2197,53 @@
|
||||
try {
|
||||
log(`Adding auth rule: ${ruleData.rule_type} - ${ruleData.pattern_value.substring(0, 16)}...`, 'INFO');
|
||||
|
||||
// Map client-side rule types to database schema values
|
||||
let dbRuleType, dbPatternType, dbAction;
|
||||
// Map client-side rule types to command array format (matching working tests)
|
||||
let commandRuleType, commandPatternType;
|
||||
|
||||
switch (ruleData.rule_type) {
|
||||
case 'pubkey_blacklist':
|
||||
dbRuleType = 'blacklist';
|
||||
dbPatternType = 'pubkey';
|
||||
dbAction = 'deny';
|
||||
commandRuleType = 'blacklist';
|
||||
commandPatternType = 'pubkey';
|
||||
break;
|
||||
case 'pubkey_whitelist':
|
||||
dbRuleType = 'whitelist';
|
||||
dbPatternType = 'pubkey';
|
||||
dbAction = 'allow';
|
||||
commandRuleType = 'whitelist';
|
||||
commandPatternType = 'pubkey';
|
||||
break;
|
||||
case 'hash_blacklist':
|
||||
dbRuleType = 'blacklist';
|
||||
dbPatternType = 'pubkey'; // Schema supports: pubkey, kind, ip, global - using pubkey for hash for now
|
||||
dbAction = 'deny';
|
||||
commandRuleType = 'blacklist';
|
||||
commandPatternType = 'hash';
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown rule type: ${ruleData.rule_type}`);
|
||||
}
|
||||
|
||||
// Map pattern type to database schema values
|
||||
if (ruleData.pattern_type === 'Global') {
|
||||
dbPatternType = 'global';
|
||||
} else if (ruleData.pattern_type === 'pubkey') {
|
||||
dbPatternType = 'pubkey';
|
||||
// Create command array in the same format as working tests
|
||||
// Format: ["blacklist", "pubkey", "abc123..."] or ["whitelist", "pubkey", "def456..."]
|
||||
const command_array = `["${commandRuleType}", "${commandPatternType}", "${ruleData.pattern_value}"]`;
|
||||
|
||||
// Encrypt the command content using NIP-44 (same as working tests)
|
||||
const encrypted_content = await encryptForRelay(command_array);
|
||||
if (!encrypted_content) {
|
||||
throw new Error('Failed to encrypt auth rule command');
|
||||
}
|
||||
|
||||
// Create kind 23456 auth rule event (ephemeral auth management)
|
||||
// Create kind 23456 admin event with encrypted content (same as working tests)
|
||||
const authEvent = {
|
||||
kind: 23456,
|
||||
pubkey: userPubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [
|
||||
[dbRuleType, dbPatternType, ruleData.pattern_value]
|
||||
["p", getRelayPubkey()]
|
||||
],
|
||||
content: JSON.stringify({
|
||||
action: 'add',
|
||||
rule_type: dbRuleType,
|
||||
pattern_type: dbPatternType,
|
||||
pattern_value: ruleData.pattern_value,
|
||||
rule_action: dbAction
|
||||
})
|
||||
content: encrypted_content
|
||||
};
|
||||
|
||||
// DEBUG: Log the complete event structure being sent
|
||||
console.log('=== AUTH RULE EVENT DEBUG ===');
|
||||
console.log('=== AUTH RULE EVENT DEBUG (FIXED FORMAT) ===');
|
||||
console.log('Original Rule Data:', ruleData);
|
||||
console.log('Mapped DB Values:', { dbRuleType, dbPatternType, dbAction });
|
||||
console.log('Command Array:', command_array);
|
||||
console.log('Encrypted Content:', encrypted_content.substring(0, 50) + '...');
|
||||
console.log('Auth Event (before signing):', JSON.stringify(authEvent, null, 2));
|
||||
console.log('Auth Event Tags:', authEvent.tags);
|
||||
console.log('Auth Event Content:', authEvent.content);
|
||||
console.log('=== END AUTH RULE EVENT DEBUG ===');
|
||||
|
||||
// Sign the event using the standard NIP-07 interface
|
||||
@@ -2384,8 +2441,7 @@
|
||||
pubkey: userPubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [
|
||||
["p", getRelayPubkey()],
|
||||
["blacklist", "pubkey", testPubkey]
|
||||
["p", getRelayPubkey()]
|
||||
],
|
||||
content: encrypted_content
|
||||
};
|
||||
@@ -2449,8 +2505,7 @@
|
||||
pubkey: userPubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [
|
||||
["p", getRelayPubkey()],
|
||||
["whitelist", "pubkey", testPubkey]
|
||||
["p", getRelayPubkey()]
|
||||
],
|
||||
content: encrypted_content
|
||||
};
|
||||
|
||||
@@ -282,14 +282,14 @@ cd build
|
||||
# Start relay in background and capture its PID
|
||||
if [ "$USE_TEST_KEYS" = true ]; then
|
||||
echo "Using deterministic test keys for development..."
|
||||
./$(basename $BINARY_PATH) -a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -r 1111111111111111111111111111111111111111111111111111111111111111 > ../relay.log 2>&1 &
|
||||
./$(basename $BINARY_PATH) -a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -r 1111111111111111111111111111111111111111111111111111111111111111 --strict-port > ../relay.log 2>&1 &
|
||||
elif [ -n "$RELAY_ARGS" ]; then
|
||||
echo "Starting relay with custom configuration..."
|
||||
./$(basename $BINARY_PATH) $RELAY_ARGS > ../relay.log 2>&1 &
|
||||
./$(basename $BINARY_PATH) $RELAY_ARGS --strict-port > ../relay.log 2>&1 &
|
||||
else
|
||||
# No command line arguments needed for random key generation
|
||||
echo "Starting relay with random key generation..."
|
||||
./$(basename $BINARY_PATH) > ../relay.log 2>&1 &
|
||||
./$(basename $BINARY_PATH) --strict-port > ../relay.log 2>&1 &
|
||||
fi
|
||||
RELAY_PID=$!
|
||||
# Change back to original directory
|
||||
|
||||
200
src/config.c
200
src/config.c
@@ -342,7 +342,7 @@ int store_config_event_in_database(const cJSON* event) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Insert or replace the configuration event (kind 33334 is replaceable)
|
||||
// Insert or replace the configuration event
|
||||
const char* sql = "INSERT OR REPLACE INTO events (id, pubkey, created_at, kind, event_type, content, sig, tags) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
@@ -357,7 +357,7 @@ int store_config_event_in_database(const cJSON* event) {
|
||||
sqlite3_bind_text(stmt, 2, cJSON_GetStringValue(pubkey_obj), -1, SQLITE_STATIC);
|
||||
sqlite3_bind_int64(stmt, 3, (sqlite3_int64)cJSON_GetNumberValue(created_at_obj));
|
||||
sqlite3_bind_int(stmt, 4, (int)cJSON_GetNumberValue(kind_obj));
|
||||
sqlite3_bind_text(stmt, 5, "addressable", -1, SQLITE_STATIC); // kind 33334 is addressable
|
||||
sqlite3_bind_text(stmt, 5, "regular", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 6, cJSON_GetStringValue(content_obj), -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 7, cJSON_GetStringValue(sig_obj), -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 8, tags_str, -1, SQLITE_TRANSIENT);
|
||||
@@ -384,26 +384,9 @@ cJSON* load_config_event_from_database(const char* relay_pubkey) {
|
||||
sqlite3_stmt* stmt;
|
||||
int rc;
|
||||
|
||||
// Try to get admin pubkey from cache, otherwise find the most recent kind 33334 event
|
||||
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";
|
||||
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
log_error("Failed to prepare configuration event query");
|
||||
return NULL;
|
||||
}
|
||||
sqlite3_bind_text(stmt, 1, admin_pubkey, -1, SQLITE_STATIC);
|
||||
} else {
|
||||
// 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)
|
||||
sql = "SELECT id, pubkey, created_at, kind, content, sig, tags FROM events WHERE kind = 33334 ORDER BY created_at DESC LIMIT 1";
|
||||
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
log_error("Failed to prepare configuration event query");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
// Configuration is now managed through config table, not events
|
||||
log_info("Configuration events are no longer stored in events table");
|
||||
return NULL;
|
||||
|
||||
cJSON* event = NULL;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
@@ -937,7 +920,7 @@ cJSON* create_default_config_event(const unsigned char* admin_privkey_bytes,
|
||||
|
||||
// Create and sign event using nostr_core_lib
|
||||
cJSON* event = nostr_create_and_sign_event(
|
||||
33334, // kind
|
||||
23455, // kind
|
||||
"C Nostr Relay Configuration", // content
|
||||
tags, // tags
|
||||
admin_privkey_bytes, // private key bytes for signing
|
||||
@@ -1614,7 +1597,7 @@ int process_configuration_event(const cJSON* event) {
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
|
||||
if (!kind_obj || cJSON_GetNumberValue(kind_obj) != 33334) {
|
||||
if (!kind_obj || (cJSON_GetNumberValue(kind_obj) != 23455 && cJSON_GetNumberValue(kind_obj) != 23456)) {
|
||||
log_error("Invalid event kind for configuration");
|
||||
return -1;
|
||||
}
|
||||
@@ -1775,7 +1758,7 @@ int apply_runtime_config_handlers(const cJSON* old_config, const cJSON* new_conf
|
||||
if (handlers_applied > 0) {
|
||||
char audit_msg[512];
|
||||
snprintf(audit_msg, sizeof(audit_msg),
|
||||
"Configuration updated via kind 33334 event - %d system components reinitialized",
|
||||
"Configuration updated via admin event - %d system components reinitialized",
|
||||
handlers_applied);
|
||||
log_success(audit_msg);
|
||||
} else {
|
||||
@@ -1832,7 +1815,7 @@ int apply_configuration_from_event(const cJSON* event) {
|
||||
// REAL-TIME EVENT HANDLER (called from main.c)
|
||||
// ================================
|
||||
|
||||
// Handle kind 33334 configuration events received via WebSocket
|
||||
// Handle configuration events received via WebSocket
|
||||
int handle_configuration_event(cJSON* event, char* error_message, size_t error_size) {
|
||||
if (!event) {
|
||||
snprintf(error_message, error_size, "invalid: null configuration event");
|
||||
@@ -2121,12 +2104,6 @@ int process_admin_event_in_config(cJSON* event, char* error_message, size_t erro
|
||||
case 23456: // New ephemeral auth rules management
|
||||
log_info("DEBUG: Routing to process_admin_auth_event (kind 23456)");
|
||||
return process_admin_auth_event(event, error_message, error_size, wsi);
|
||||
case 33334: // Legacy addressable config events (backward compatibility)
|
||||
log_info("DEBUG: Routing to process_admin_config_event (legacy kind 33334)");
|
||||
return process_admin_config_event(event, error_message, error_size);
|
||||
case 33335: // Legacy addressable auth events (backward compatibility)
|
||||
log_info("DEBUG: Routing to process_admin_auth_event (legacy kind 33335)");
|
||||
return process_admin_auth_event(event, error_message, error_size, wsi);
|
||||
default:
|
||||
log_error("DEBUG: Unsupported admin event kind");
|
||||
printf(" Unsupported kind: %d\n", kind);
|
||||
@@ -2135,7 +2112,7 @@ int process_admin_event_in_config(cJSON* event, char* error_message, size_t erro
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Kind 23455 configuration management events and legacy Kind 33334
|
||||
// Handle Kind 23455 configuration management events
|
||||
int process_admin_config_event(cJSON* event, char* error_message, size_t error_size) {
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
int kind = kind_obj ? (int)cJSON_GetNumberValue(kind_obj) : 0;
|
||||
@@ -2211,10 +2188,6 @@ int process_admin_config_event(cJSON* event, char* error_message, size_t error_s
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip relay identifier tag (only for legacy addressable events)
|
||||
if (kind == 33334 && strcmp(key, "d") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update configuration in table
|
||||
if (update_config_in_table(key, value) == 0) {
|
||||
@@ -2238,7 +2211,7 @@ int process_admin_config_event(cJSON* event, char* error_message, size_t error_s
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle Kind 23456 auth rules management and legacy Kind 33335
|
||||
// Handle Kind 23456 auth rules management
|
||||
int process_admin_auth_event(cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
log_info("DEBUG: Entering process_admin_auth_event()");
|
||||
|
||||
@@ -2267,13 +2240,6 @@ int process_admin_auth_event(cJSON* event, char* error_message, size_t error_siz
|
||||
return handle_kind_23456_unified(event, error_message, error_size, wsi);
|
||||
}
|
||||
|
||||
// Legacy Kind 33335 events use the unified handler as well
|
||||
if (kind == 33335) {
|
||||
log_info("DEBUG: Routing legacy Kind 33335 to unified handler");
|
||||
// For legacy events, we still use the unified handler but may need special processing
|
||||
// The unified handler already supports all the functionality
|
||||
return handle_kind_23456_unified(event, error_message, error_size, wsi);
|
||||
}
|
||||
|
||||
log_error("DEBUG: Unsupported auth event kind in process_admin_auth_event");
|
||||
printf(" Unsupported kind: %d\n", kind);
|
||||
@@ -2561,7 +2527,9 @@ char* encrypt_admin_response_content(const cJSON* response_data, const char* rec
|
||||
}
|
||||
|
||||
// Send admin response event using relay's standard event distribution system
|
||||
int send_admin_response_event(const cJSON* response_data, const char* recipient_pubkey) {
|
||||
int send_admin_response_event(const cJSON* response_data, const char* recipient_pubkey, struct lws* wsi) {
|
||||
// Suppress unused parameter warning
|
||||
(void)wsi;
|
||||
if (!response_data || !recipient_pubkey) {
|
||||
log_error("Invalid parameters for admin response event transmission");
|
||||
return -1;
|
||||
@@ -2645,6 +2613,8 @@ cJSON* build_query_response(const char* query_type, cJSON* results_array, int to
|
||||
|
||||
// Single unified handler for all Kind 23456 requests
|
||||
int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
// Suppress unused parameter warning
|
||||
(void)wsi;
|
||||
if (!event) {
|
||||
log_error("DEBUG: Null event passed to handle_kind_23456_unified");
|
||||
snprintf(error_message, error_size, "invalid: null event");
|
||||
@@ -2854,7 +2824,7 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
|
||||
return -1;
|
||||
}
|
||||
printf(" Query type: %s\n", query_type);
|
||||
return handle_auth_query_unified(event, query_type, error_message, error_size);
|
||||
return handle_auth_query_unified(event, query_type, error_message, error_size, wsi);
|
||||
}
|
||||
else if (strcmp(action_type, "system_command") == 0) {
|
||||
log_info("DEBUG: Routing to system_command handler");
|
||||
@@ -2865,13 +2835,13 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
|
||||
return -1;
|
||||
}
|
||||
printf(" Command: %s\n", command);
|
||||
return handle_system_command_unified(event, command, error_message, error_size);
|
||||
return handle_system_command_unified(event, command, error_message, error_size, wsi);
|
||||
}
|
||||
else if (strcmp(action_type, "whitelist") == 0 || strcmp(action_type, "blacklist") == 0) {
|
||||
log_info("DEBUG: Routing to auth rule modification handler");
|
||||
printf(" Rule type: %s\n", action_type);
|
||||
// Handle auth rule modifications (existing logic from process_admin_auth_event)
|
||||
return handle_auth_rule_modification_unified(event, error_message, error_size);
|
||||
return handle_auth_rule_modification_unified(event, error_message, error_size, wsi);
|
||||
}
|
||||
else {
|
||||
log_error("DEBUG: Unknown Kind 23456 action type");
|
||||
@@ -2882,7 +2852,9 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
|
||||
}
|
||||
|
||||
// Unified auth query handler
|
||||
int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size) {
|
||||
int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
// Suppress unused parameter warning
|
||||
(void)wsi;
|
||||
if (!g_db) {
|
||||
snprintf(error_message, error_size, "database not available");
|
||||
return -1;
|
||||
@@ -2983,7 +2955,7 @@ int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_
|
||||
}
|
||||
|
||||
// Send response as signed kind 23457 event
|
||||
if (send_admin_response_event(response, admin_pubkey) == 0) {
|
||||
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||
printf("Total results: %d\n", rule_count);
|
||||
log_success("Auth query completed successfully with signed response");
|
||||
cJSON_Delete(response);
|
||||
@@ -2999,7 +2971,9 @@ int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_
|
||||
}
|
||||
|
||||
// Unified system command handler
|
||||
int handle_system_command_unified(cJSON* event, const char* command, char* error_message, size_t error_size) {
|
||||
int handle_system_command_unified(cJSON* event, const char* command, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
// Suppress unused parameter warning
|
||||
(void)wsi;
|
||||
if (!g_db) {
|
||||
snprintf(error_message, error_size, "database not available");
|
||||
return -1;
|
||||
@@ -3054,7 +3028,7 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
|
||||
}
|
||||
|
||||
// Send response as signed kind 23457 event
|
||||
if (send_admin_response_event(response, admin_pubkey) == 0) {
|
||||
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||
log_success("Clear auth rules command completed successfully with signed response");
|
||||
cJSON_Delete(response);
|
||||
return 0;
|
||||
@@ -3064,6 +3038,85 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
|
||||
snprintf(error_message, error_size, "failed to send clear auth rules response");
|
||||
return -1;
|
||||
}
|
||||
else if (strcmp(command, "delete_auth_rule") == 0) {
|
||||
// Get rule parameters from tags
|
||||
const char* rule_type = get_tag_value(event, "system_command", 2);
|
||||
const char* pattern_type = get_tag_value(event, "system_command", 3);
|
||||
const char* pattern_value = get_tag_value(event, "system_command", 4);
|
||||
|
||||
if (!rule_type || !pattern_type || !pattern_value) {
|
||||
snprintf(error_message, error_size, "invalid: delete_auth_rule requires rule_type, pattern_type, and pattern_value");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing delete auth rule command");
|
||||
printf(" Rule type: %s\n", rule_type);
|
||||
printf(" Pattern type: %s\n", pattern_type);
|
||||
printf(" Pattern value: %s\n", pattern_value);
|
||||
|
||||
// Check if rule exists before deletion
|
||||
const char* check_sql = "SELECT COUNT(*) FROM auth_rules WHERE rule_type = ? AND pattern_type = ? AND pattern_value = ?";
|
||||
sqlite3_stmt* check_stmt;
|
||||
|
||||
int check_rc = sqlite3_prepare_v2(g_db, check_sql, -1, &check_stmt, NULL);
|
||||
if (check_rc != SQLITE_OK) {
|
||||
snprintf(error_message, error_size, "failed to prepare rule existence check");
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(check_stmt, 1, rule_type, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(check_stmt, 2, pattern_type, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(check_stmt, 3, pattern_value, -1, SQLITE_STATIC);
|
||||
|
||||
int rule_exists = 0;
|
||||
if (sqlite3_step(check_stmt) == SQLITE_ROW) {
|
||||
rule_exists = sqlite3_column_int(check_stmt, 0) > 0;
|
||||
}
|
||||
sqlite3_finalize(check_stmt);
|
||||
|
||||
if (!rule_exists) {
|
||||
snprintf(error_message, error_size, "error: auth rule not found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Delete the specific auth rule
|
||||
if (remove_auth_rule_from_config(rule_type, pattern_type, pattern_value) != 0) {
|
||||
snprintf(error_message, error_size, "failed to delete auth rule from database");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Build response
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(response, "command", "delete_auth_rule");
|
||||
cJSON_AddStringToObject(response, "rule_type", rule_type);
|
||||
cJSON_AddStringToObject(response, "pattern_type", pattern_type);
|
||||
cJSON_AddStringToObject(response, "pattern_value", pattern_value);
|
||||
cJSON_AddStringToObject(response, "status", "success");
|
||||
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
||||
|
||||
printf("Deleted auth rule: %s %s:%s\n", rule_type, pattern_type, pattern_value);
|
||||
|
||||
// Get admin pubkey from event for response
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
|
||||
|
||||
if (!admin_pubkey) {
|
||||
cJSON_Delete(response);
|
||||
snprintf(error_message, error_size, "missing admin pubkey for response");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Send response as signed kind 23457 event
|
||||
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||
log_success("Delete auth rule command completed successfully with signed response");
|
||||
cJSON_Delete(response);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON_Delete(response);
|
||||
snprintf(error_message, error_size, "failed to send delete auth rule response");
|
||||
return -1;
|
||||
}
|
||||
else if (strcmp(command, "system_status") == 0) {
|
||||
// Build system status response
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
@@ -3116,7 +3169,7 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
|
||||
}
|
||||
|
||||
// Send response as signed kind 23457 event
|
||||
if (send_admin_response_event(response, admin_pubkey) == 0) {
|
||||
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||
log_success("System status query completed successfully with signed response");
|
||||
cJSON_Delete(response);
|
||||
return 0;
|
||||
@@ -3133,7 +3186,9 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
|
||||
}
|
||||
|
||||
// Handle auth rule modifications (extracted from process_admin_auth_event)
|
||||
int handle_auth_rule_modification_unified(cJSON* event, char* error_message, size_t error_size) {
|
||||
int handle_auth_rule_modification_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
// Suppress unused parameter warning
|
||||
(void)wsi;
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
if (!tags_obj || !cJSON_IsArray(tags_obj)) {
|
||||
snprintf(error_message, error_size, "invalid: auth rule event must have tags");
|
||||
@@ -3155,7 +3210,8 @@ int handle_auth_rule_modification_unified(cJSON* event, char* error_message, siz
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Process each tag as an auth rule specification
|
||||
// For Kind 23456 events, only process synthetic tags created from decrypted content
|
||||
// Skip original unencrypted tags (except p tag validation which is done elsewhere)
|
||||
cJSON* auth_tag = NULL;
|
||||
cJSON_ArrayForEach(auth_tag, tags_obj) {
|
||||
if (!cJSON_IsArray(auth_tag) || cJSON_GetArraySize(auth_tag) < 3) {
|
||||
@@ -3176,6 +3232,11 @@ int handle_auth_rule_modification_unified(cJSON* event, char* error_message, siz
|
||||
const char* pattern_type = cJSON_GetStringValue(pattern_type_obj);
|
||||
const char* pattern_value = cJSON_GetStringValue(pattern_value_obj);
|
||||
|
||||
// Skip p tags - they are for routing, not auth rules
|
||||
if (strcmp(rule_type, "p") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process auth rule: ["blacklist"|"whitelist", "pubkey"|"hash", "value"]
|
||||
if (strcmp(rule_type, "blacklist") == 0 || strcmp(rule_type, "whitelist") == 0) {
|
||||
if (add_auth_rule_from_config(rule_type, pattern_type, pattern_value, "allow") == 0) {
|
||||
@@ -3221,7 +3282,7 @@ int handle_auth_rule_modification_unified(cJSON* event, char* error_message, siz
|
||||
}
|
||||
|
||||
// Send response as signed kind 23457 event
|
||||
if (send_admin_response_event(response, admin_pubkey) == 0) {
|
||||
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||
log_success("Auth rule modification completed successfully with signed response");
|
||||
cJSON_Delete(response);
|
||||
return 0;
|
||||
@@ -3519,7 +3580,7 @@ int process_startup_config_event(const cJSON* event) {
|
||||
|
||||
// Validate event structure first
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
if (!kind_obj || cJSON_GetNumberValue(kind_obj) != 33334) {
|
||||
if (!kind_obj || cJSON_GetNumberValue(kind_obj) != 23455) {
|
||||
log_error("Invalid event kind for startup configuration");
|
||||
return -1;
|
||||
}
|
||||
@@ -3617,14 +3678,14 @@ int process_startup_config_event_with_fallback(const cJSON* event) {
|
||||
// DYNAMIC EVENT GENERATION FROM CONFIG TABLE
|
||||
// ================================
|
||||
|
||||
// Generate synthetic kind 33334 configuration event from current config table data
|
||||
// Generate synthetic 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...");
|
||||
log_info("Generating synthetic configuration event from config table...");
|
||||
|
||||
// Get relay pubkey for event generation
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
@@ -3648,7 +3709,7 @@ cJSON* generate_config_event_from_table(void) {
|
||||
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_AddNumberToObject(event, "kind", 23455);
|
||||
cJSON_AddStringToObject(event, "content", "C Nostr Relay Configuration");
|
||||
cJSON_AddStringToObject(event, "sig", "synthetic_signature");
|
||||
|
||||
@@ -3708,13 +3769,13 @@ cJSON* generate_config_event_from_table(void) {
|
||||
|
||||
char success_msg[256];
|
||||
snprintf(success_msg, sizeof(success_msg),
|
||||
"Generated synthetic kind 33334 event with %d configuration items", config_items_added);
|
||||
"Generated synthetic configuration event with %d configuration items", config_items_added);
|
||||
log_success(success_msg);
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
// Check if a REQ filter requests kind 33334 events
|
||||
// Check if a REQ filter requests configuration events
|
||||
int req_filter_requests_config_events(const cJSON* filter) {
|
||||
if (!filter || !cJSON_IsObject(filter)) {
|
||||
return 0;
|
||||
@@ -3725,10 +3786,11 @@ int req_filter_requests_config_events(const cJSON* filter) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if kinds array contains 33334
|
||||
// Check if kinds array contains configuration event kinds
|
||||
cJSON* kind_item = NULL;
|
||||
cJSON_ArrayForEach(kind_item, kinds) {
|
||||
if (cJSON_IsNumber(kind_item) && (int)cJSON_GetNumberValue(kind_item) == 33334) {
|
||||
int kind_val = (int)cJSON_GetNumberValue(kind_item);
|
||||
if (cJSON_IsNumber(kind_item) && (kind_val == 23455 || kind_val == 23456)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -3742,7 +3804,7 @@ cJSON* generate_synthetic_config_event_for_subscription(const char* sub_id, cons
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Check if any filter requests kind 33334
|
||||
// Check if any filter requests configuration events
|
||||
int requests_config = 0;
|
||||
|
||||
if (cJSON_IsArray(filters)) {
|
||||
@@ -3762,7 +3824,7 @@ cJSON* generate_synthetic_config_event_for_subscription(const char* sub_id, cons
|
||||
return NULL;
|
||||
}
|
||||
|
||||
log_info("Generating synthetic kind 33334 event for subscription");
|
||||
log_info("Generating synthetic configuration event for subscription");
|
||||
|
||||
// Generate synthetic config event from table
|
||||
cJSON* config_event = generate_config_event_from_table();
|
||||
@@ -3777,12 +3839,12 @@ cJSON* generate_synthetic_config_event_for_subscription(const char* sub_id, cons
|
||||
cJSON_AddItemToArray(event_msg, cJSON_CreateString(sub_id));
|
||||
cJSON_AddItemToArray(event_msg, config_event);
|
||||
|
||||
log_success("Generated synthetic kind 33334 configuration event message");
|
||||
log_success("Generated synthetic configuration event message");
|
||||
return event_msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a synthetic kind 33334 configuration event from config table data
|
||||
* Generate a synthetic 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
|
||||
*/
|
||||
|
||||
@@ -98,6 +98,7 @@ typedef struct {
|
||||
int port_override; // -1 = not set, >0 = port value
|
||||
char admin_privkey_override[65]; // Empty string = not set, 64-char hex = override
|
||||
char relay_privkey_override[65]; // Empty string = not set, 64-char hex = override
|
||||
int strict_port; // 0 = allow port increment, 1 = fail if exact port unavailable
|
||||
} cli_options_t;
|
||||
|
||||
// Global unified configuration cache
|
||||
@@ -170,12 +171,12 @@ int process_admin_auth_event(cJSON* event, char* error_message, size_t error_siz
|
||||
|
||||
// Unified Kind 23456 handler functions
|
||||
int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||
int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size);
|
||||
int handle_system_command_unified(cJSON* event, const char* command, char* error_message, size_t error_size);
|
||||
int handle_auth_rule_modification_unified(cJSON* event, char* error_message, size_t error_size);
|
||||
int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi);
|
||||
int handle_system_command_unified(cJSON* event, const char* command, char* error_message, size_t error_size, struct lws* wsi);
|
||||
int handle_auth_rule_modification_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||
|
||||
// Admin response functions
|
||||
int send_admin_response_event(const cJSON* response_data, const char* recipient_pubkey);
|
||||
int send_admin_response_event(const cJSON* response_data, const char* recipient_pubkey, struct lws* wsi);
|
||||
cJSON* build_query_response(const char* query_type, cJSON* results_array, int total_count);
|
||||
|
||||
// Auth rules management functions
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
* Default Configuration Event Template
|
||||
*
|
||||
* This header contains the default configuration values for the C Nostr Relay.
|
||||
* These values are used to create the initial kind 33334 configuration event
|
||||
* during first-time startup.
|
||||
* These values are used to populate the config table during first-time startup.
|
||||
*
|
||||
* IMPORTANT: These values should never be accessed directly by other parts
|
||||
* of the program. They are only used during initial configuration event creation.
|
||||
|
||||
51
src/main.c
51
src/main.c
@@ -224,10 +224,7 @@ int handle_event_message(cJSON* event, char* error_message, size_t error_size);
|
||||
// Forward declaration for unified validation
|
||||
int nostr_validate_unified_request(const char* json_string, size_t json_length);
|
||||
|
||||
// Forward declaration for configuration event handling (kind 33334)
|
||||
int handle_configuration_event(cJSON* event, char* error_message, size_t error_size);
|
||||
|
||||
// Forward declaration for admin event processing (kinds 33334 and 33335)
|
||||
// Forward declaration for admin event processing (kinds 23455 and 23456)
|
||||
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||
|
||||
// Forward declaration for enhanced admin event authorization
|
||||
@@ -3035,7 +3032,7 @@ int is_authorized_admin_event(cJSON* event, char* error_buffer, size_t error_buf
|
||||
}
|
||||
|
||||
int event_kind = kind_json->valueint;
|
||||
if (event_kind != 33334 && event_kind != 33335 && event_kind != 23455 && event_kind != 23456) {
|
||||
if (event_kind != 23455 && event_kind != 23456) {
|
||||
snprintf(error_buffer, error_buffer_size, "Event kind %d is not an admin event type", event_kind);
|
||||
return -1;
|
||||
}
|
||||
@@ -3356,7 +3353,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Cleanup event JSON string
|
||||
free(event_json_str);
|
||||
|
||||
// Check for admin events (kinds 33334, 33335, 23455, and 23456) and intercept them
|
||||
// Check for admin events (kinds 23455 and 23456) and intercept them
|
||||
if (result == 0) {
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
if (kind_obj && cJSON_IsNumber(kind_obj)) {
|
||||
@@ -3378,7 +3375,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
}
|
||||
}
|
||||
|
||||
if (event_kind == 33334 || event_kind == 33335 || event_kind == 23455 || event_kind == 23456) {
|
||||
if (event_kind == 23455 || event_kind == 23456) {
|
||||
// Enhanced admin event security - check authorization first
|
||||
log_info("DEBUG ADMIN: Admin event detected, checking authorization");
|
||||
|
||||
@@ -3697,7 +3694,7 @@ int check_port_available(int port) {
|
||||
}
|
||||
|
||||
// Start libwebsockets-based WebSocket Nostr relay server
|
||||
int start_websocket_relay(int port_override) {
|
||||
int start_websocket_relay(int port_override, int strict_port) {
|
||||
struct lws_context_creation_info info;
|
||||
|
||||
log_info("Starting libwebsockets-based Nostr relay server...");
|
||||
@@ -3707,7 +3704,7 @@ int start_websocket_relay(int port_override) {
|
||||
int configured_port = (port_override > 0) ? port_override : get_config_int("relay_port", DEFAULT_PORT);
|
||||
int actual_port = configured_port;
|
||||
int port_attempts = 0;
|
||||
const int max_port_attempts = 5;
|
||||
const int max_port_attempts = 10; // Increased from 5 to 10
|
||||
|
||||
// Minimal libwebsockets configuration
|
||||
info.protocols = protocols;
|
||||
@@ -3726,8 +3723,8 @@ int start_websocket_relay(int port_override) {
|
||||
// Max payload size for Nostr events
|
||||
info.max_http_header_data = 4096;
|
||||
|
||||
// Find an available port with pre-checking
|
||||
while (port_attempts < max_port_attempts) {
|
||||
// Find an available port with pre-checking (or fail immediately in strict mode)
|
||||
while (port_attempts < (strict_port ? 1 : max_port_attempts)) {
|
||||
char attempt_msg[256];
|
||||
snprintf(attempt_msg, sizeof(attempt_msg), "Checking port availability: %d", actual_port);
|
||||
log_info(attempt_msg);
|
||||
@@ -3735,7 +3732,13 @@ int start_websocket_relay(int port_override) {
|
||||
// Pre-check if port is available
|
||||
if (!check_port_available(actual_port)) {
|
||||
port_attempts++;
|
||||
if (port_attempts < max_port_attempts) {
|
||||
if (strict_port) {
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg),
|
||||
"Strict port mode: port %d is not available", actual_port);
|
||||
log_error(error_msg);
|
||||
return -1;
|
||||
} else if (port_attempts < max_port_attempts) {
|
||||
char retry_msg[256];
|
||||
snprintf(retry_msg, sizeof(retry_msg), "Port %d is in use, trying port %d (attempt %d/%d)",
|
||||
actual_port, actual_port + 1, port_attempts + 1, max_port_attempts);
|
||||
@@ -3774,7 +3777,13 @@ int start_websocket_relay(int port_override) {
|
||||
log_warning(lws_error_msg);
|
||||
|
||||
port_attempts++;
|
||||
if (port_attempts < max_port_attempts) {
|
||||
if (strict_port) {
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg),
|
||||
"Strict port mode: failed to bind to port %d", actual_port);
|
||||
log_error(error_msg);
|
||||
break;
|
||||
} else if (port_attempts < max_port_attempts) {
|
||||
actual_port++;
|
||||
continue;
|
||||
}
|
||||
@@ -3840,6 +3849,7 @@ void print_usage(const char* program_name) {
|
||||
printf(" -p, --port PORT Override relay port (first-time startup only)\n");
|
||||
printf(" -a, --admin-privkey HEX Override admin private key (64-char hex)\n");
|
||||
printf(" -r, --relay-privkey HEX Override relay private key (64-char hex)\n");
|
||||
printf(" --strict-port Fail if exact port is unavailable (no port increment)\n");
|
||||
printf("\n");
|
||||
printf("Configuration:\n");
|
||||
printf(" This relay uses event-based configuration stored in the database.\n");
|
||||
@@ -3848,10 +3858,16 @@ void print_usage(const char* program_name) {
|
||||
printf(" After initial setup, all configuration is managed via database events.\n");
|
||||
printf(" Database file: <relay_pubkey>.db (created automatically)\n");
|
||||
printf("\n");
|
||||
printf("Port Binding:\n");
|
||||
printf(" Default: Try up to 10 consecutive ports if requested port is busy\n");
|
||||
printf(" --strict-port: Fail immediately if exact requested port is unavailable\n");
|
||||
printf("\n");
|
||||
printf("Examples:\n");
|
||||
printf(" %s # Start relay (auto-configure on first run)\n", program_name);
|
||||
printf(" %s -p 8080 # First-time setup with port 8080\n", program_name);
|
||||
printf(" %s --port 9000 # First-time setup with port 9000\n", program_name);
|
||||
printf(" %s --strict-port # Fail if default port 8888 is unavailable\n", program_name);
|
||||
printf(" %s -p 8080 --strict-port # Fail if port 8080 is unavailable\n", program_name);
|
||||
printf(" %s --help # Show this help\n", program_name);
|
||||
printf(" %s --version # Show version info\n", program_name);
|
||||
printf("\n");
|
||||
@@ -3870,7 +3886,8 @@ int main(int argc, char* argv[]) {
|
||||
cli_options_t cli_options = {
|
||||
.port_override = -1, // -1 = not set
|
||||
.admin_privkey_override = {0}, // Empty string = not set
|
||||
.relay_privkey_override = {0} // Empty string = not set
|
||||
.relay_privkey_override = {0}, // Empty string = not set
|
||||
.strict_port = 0 // 0 = allow port increment (default)
|
||||
};
|
||||
|
||||
// Parse command line arguments
|
||||
@@ -3965,6 +3982,10 @@ int main(int argc, char* argv[]) {
|
||||
i++; // Skip the key argument
|
||||
|
||||
log_info("Relay private key override specified");
|
||||
} else if (strcmp(argv[i], "--strict-port") == 0) {
|
||||
// Strict port mode option
|
||||
cli_options.strict_port = 1;
|
||||
log_info("Strict port mode enabled - will fail if exact port is unavailable");
|
||||
} else {
|
||||
log_error("Unknown argument. Use --help for usage information.");
|
||||
print_usage(argv[0]);
|
||||
@@ -4186,7 +4207,7 @@ int main(int argc, char* argv[]) {
|
||||
log_info("Starting relay server...");
|
||||
|
||||
// Start WebSocket Nostr relay server (port from configuration)
|
||||
int result = start_websocket_relay(-1); // Let config system determine port
|
||||
int result = start_websocket_relay(-1, cli_options.strict_port); // Let config system determine port, pass strict_port flag
|
||||
|
||||
// Cleanup
|
||||
cleanup_relay_info();
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
static const char* const EMBEDDED_SCHEMA_SQL =
|
||||
"-- C Nostr Relay Database Schema\n\
|
||||
-- SQLite schema for storing Nostr events with JSON tags support\n\
|
||||
-- Event-based configuration system using kind 33334 Nostr events\n\
|
||||
-- Configuration system using config table\n\
|
||||
\n\
|
||||
-- Schema version tracking\n\
|
||||
PRAGMA user_version = 7;\n\
|
||||
|
||||
Reference in New Issue
Block a user