v0.3.15 - How can administration take so long
This commit is contained in:
329
api/index.html
329
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>
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user