v0.3.15 - How can administration take so long

This commit is contained in:
Your Name
2025-09-27 15:50:42 -04:00
parent c1c05991cf
commit 6fd3e531c3
10 changed files with 343 additions and 218 deletions

View File

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