Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0751a7c55c | ||
|
|
f1728932a9 | ||
|
|
ef8bdef2a8 | ||
|
|
c11a8ba292 | ||
|
|
0f124fe575 | ||
|
|
6b20452fab | ||
|
|
10c19bc243 | ||
|
|
3d7aa2196f | ||
|
|
a416c3f275 | ||
|
|
bba9baabc3 | ||
|
|
31187c4c4f | ||
|
|
55f862b879 | ||
|
|
76c9b3fcf0 |
@@ -1486,3 +1486,16 @@ body.dark-mode .admin-verification-content {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
/* ================================
|
||||
IP BANS TABLE - compact rows
|
||||
================================ */
|
||||
#ip-bans-table td, #ip-bans-table th {
|
||||
padding: 4px 8px;
|
||||
line-height: 1.3;
|
||||
font-size: 13px;
|
||||
}
|
||||
#ip-bans-table button {
|
||||
padding: 2px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@@ -435,6 +435,21 @@ WEB OF TRUST
|
||||
<div id="add-ban-status" class="status-message"></div>
|
||||
</div>
|
||||
|
||||
<!-- Whitelist Management -->
|
||||
<div class="input-group">
|
||||
<h3>IP Whitelist (Never Banned)</h3>
|
||||
<p style="font-size:13px;opacity:0.8;">IPs in this list are never idle-banned. Comma-separated.</p>
|
||||
<div class="form-group">
|
||||
<label for="whitelist-ip-input">Add IP to Whitelist:</label>
|
||||
<input type="text" id="whitelist-ip-input" placeholder="103.81.231.220">
|
||||
</div>
|
||||
<div class="inline-buttons">
|
||||
<button type="button" id="add-whitelist-btn">ADD TO WHITELIST</button>
|
||||
</div>
|
||||
<div id="whitelist-status" class="status-message"></div>
|
||||
<div id="whitelist-current" style="margin-top:8px;font-size:13px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Controls -->
|
||||
<div class="input-group">
|
||||
<div class="inline-buttons">
|
||||
|
||||
+153
-59
@@ -1328,12 +1328,24 @@ async function subscribeToConfiguration() {
|
||||
},
|
||||
onclose(reason) {
|
||||
console.log('Subscription closed:', reason);
|
||||
// Reset subscription state to allow re-subscription
|
||||
isSubscribed = false;
|
||||
isSubscribing = false;
|
||||
isRelayConnected = false;
|
||||
updateConfigStatus(false);
|
||||
log('WebSocket connection closed - subscription state reset', 'WARNING');
|
||||
// Only reset state if the admin relay itself closed (not external relays timing out)
|
||||
// reason is an array of close reasons, one per relay in the subscription
|
||||
const adminUrl = relayConnectionUrl.value.trim();
|
||||
const reasons = Array.isArray(reason) ? reason : [reason];
|
||||
// Check if the admin relay's connection closed (not just external relays timing out)
|
||||
// If all reasons are 'connection timed out', these are external relays, not our relay
|
||||
const allTimeout = reasons.every(r => r === 'connection timed out');
|
||||
if (!allTimeout) {
|
||||
// Admin relay actually closed — reset state
|
||||
isSubscribed = false;
|
||||
isSubscribing = false;
|
||||
isRelayConnected = false;
|
||||
updateConfigStatus(false);
|
||||
log('WebSocket connection closed - subscription state reset', 'WARNING');
|
||||
} else {
|
||||
// External relays timed out — keep admin relay state intact
|
||||
log('External relay connections timed out (admin relay still connected)', 'INFO');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5556,6 +5568,12 @@ function handleSqlQueryResponse(response) {
|
||||
console.log('=== HANDLING SQL QUERY RESPONSE ===');
|
||||
console.log('Response:', response);
|
||||
|
||||
// Route IP bans queries to the IP bans handler
|
||||
if (response.query && response.query.includes('ip_bans')) {
|
||||
handleIpBansResponse(response);
|
||||
return;
|
||||
}
|
||||
|
||||
// Always display SQL query results when received
|
||||
displaySqlQueryResults(response);
|
||||
|
||||
@@ -6184,36 +6202,23 @@ function initializeRelayEvents() {
|
||||
// ================================
|
||||
|
||||
let ipBansFilter = 'all'; // 'all', 'banned', 'expired'
|
||||
let ipBansCachedResults = null; // Cache last fetched results for client-side filtering
|
||||
|
||||
// Load IP bans from database
|
||||
async function loadIpBans() {
|
||||
const query = "SELECT ip, failure_count, ban_count, banned_until, first_failure, has_authed_successfully, last_success_at, total_connections, total_failures, total_successes, first_seen FROM ip_bans ORDER BY banned_until DESC, total_failures DESC";
|
||||
const query = "SELECT ip, failure_count, ban_count, banned_until, first_failure, has_authed_successfully, last_success_at, total_connections, total_failures, total_successes, first_seen, idle_failure_count, idle_ban_count, idle_banned_until FROM ip_bans ORDER BY MAX(banned_until, idle_banned_until) DESC, total_failures DESC";
|
||||
await executeSqlQueryRaw(query, 'ip_bans_load');
|
||||
}
|
||||
|
||||
// Execute SQL query and handle IP bans response
|
||||
async function executeSqlQueryRaw(query, queryId) {
|
||||
if (!pool || pool.length === 0) {
|
||||
if (!relayPool) {
|
||||
log('Not connected to relay', 'ERROR');
|
||||
return;
|
||||
}
|
||||
|
||||
const relay = pool[0];
|
||||
if (!relay) {
|
||||
log('No relay connection available', 'ERROR');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a kind 23456 event with SQL query
|
||||
const event = {
|
||||
kind: 23456,
|
||||
content: query,
|
||||
tags: [['t', 'sql_query'], ['id', queryId]],
|
||||
created_at: Math.floor(Date.now() / 1000)
|
||||
};
|
||||
|
||||
try {
|
||||
await relay.publish(event);
|
||||
await sendAdminCommand(['sql_query', query, queryId]);
|
||||
log('Loading IP bans...', 'INFO');
|
||||
} catch (error) {
|
||||
log('Failed to load IP bans: ' + error.message, 'ERROR');
|
||||
@@ -6222,7 +6227,22 @@ async function executeSqlQueryRaw(query, queryId) {
|
||||
|
||||
// Handle IP bans SQL response
|
||||
function handleIpBansResponse(responseData) {
|
||||
if (!responseData.results || responseData.results.length === 0) {
|
||||
// Convert rows+columns format to array of objects
|
||||
let results = [];
|
||||
if (responseData._cachedResults) {
|
||||
results = responseData._cachedResults;
|
||||
} else if (responseData.rows && responseData.columns) {
|
||||
const cols = responseData.columns;
|
||||
results = responseData.rows.map(row => {
|
||||
const obj = {};
|
||||
cols.forEach((col, i) => { obj[col] = row[i]; });
|
||||
return obj;
|
||||
});
|
||||
} else if (responseData.results) {
|
||||
results = responseData.results;
|
||||
}
|
||||
|
||||
if (results.length === 0) {
|
||||
document.getElementById('ip-bans-tbody').innerHTML = '<tr><td colspan="7" style="text-align: center;">No IP bans found</td></tr>';
|
||||
document.getElementById('ip-bans-total').textContent = '0';
|
||||
document.getElementById('ip-bans-active').textContent = '0';
|
||||
@@ -6231,24 +6251,26 @@ function handleIpBansResponse(responseData) {
|
||||
}
|
||||
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
let totalIPs = responseData.results.length;
|
||||
let totalIPs = results.length;
|
||||
let currentlyBanned = 0;
|
||||
let totalBans = 0;
|
||||
|
||||
// Helper: is this row currently banned (auth or idle)
|
||||
const isRowBanned = row => (row.banned_until > now) || (row.idle_banned_until > now);
|
||||
const isRowExpired = row => !isRowBanned(row) && ((row.ban_count > 0) || (row.idle_ban_count > 0));
|
||||
|
||||
// Filter results based on current filter
|
||||
let filteredResults = responseData.results;
|
||||
let filteredResults = results;
|
||||
if (ipBansFilter === 'banned') {
|
||||
filteredResults = responseData.results.filter(row => row.banned_until > now);
|
||||
filteredResults = results.filter(isRowBanned);
|
||||
} else if (ipBansFilter === 'expired') {
|
||||
filteredResults = responseData.results.filter(row => row.banned_until <= now && row.banned_until > 0);
|
||||
filteredResults = results.filter(isRowExpired);
|
||||
}
|
||||
|
||||
// Calculate stats
|
||||
responseData.results.forEach(row => {
|
||||
totalBans += parseInt(row.ban_count || 0);
|
||||
if (row.banned_until > now) {
|
||||
currentlyBanned++;
|
||||
}
|
||||
// Calculate stats (include both auth and idle bans)
|
||||
results.forEach(row => {
|
||||
totalBans += parseInt(row.ban_count || 0) + parseInt(row.idle_ban_count || 0);
|
||||
if (isRowBanned(row)) currentlyBanned++;
|
||||
});
|
||||
|
||||
// Update stats
|
||||
@@ -6256,25 +6278,44 @@ function handleIpBansResponse(responseData) {
|
||||
document.getElementById('ip-bans-active').textContent = currentlyBanned;
|
||||
document.getElementById('ip-bans-issued').textContent = totalBans;
|
||||
|
||||
// Cache results for client-side filtering
|
||||
ipBansCachedResults = results;
|
||||
|
||||
// Get whitelist for display
|
||||
const whitelistStr = (currentConfig && currentConfig.idle_ban_whitelist) || '';
|
||||
const whitelistedIPs = new Set(whitelistStr.split(',').map(s => s.trim()).filter(s => s.length > 0));
|
||||
|
||||
// Render table
|
||||
const tbody = document.getElementById('ip-bans-tbody');
|
||||
tbody.innerHTML = filteredResults.map(row => {
|
||||
const isBanned = row.banned_until > now;
|
||||
const status = isBanned ? '🔴 Banned' : (row.ban_count > 0 ? '🟡 Expired' : '🟢 Clean');
|
||||
const bannedUntil = row.banned_until > 0 ? new Date(row.banned_until * 1000).toLocaleString() : '-';
|
||||
const isBanned = isRowBanned(row);
|
||||
const isIdleBanned = row.idle_banned_until > now;
|
||||
const isAuthBanned = row.banned_until > now;
|
||||
const isWhitelisted = whitelistedIPs.has(row.ip);
|
||||
let statusLabel;
|
||||
if (isWhitelisted) statusLabel = '⭐ Whitelisted';
|
||||
else if (isAuthBanned && isIdleBanned) statusLabel = '🔴 Banned (auth+idle)';
|
||||
else if (isAuthBanned) statusLabel = '🔴 Banned (auth)';
|
||||
else if (isIdleBanned) statusLabel = '🔴 Banned (idle)';
|
||||
else if ((row.ban_count > 0) || (row.idle_ban_count > 0)) statusLabel = '🟡 Expired';
|
||||
else statusLabel = '🟢 Clean';
|
||||
|
||||
// Show the later of the two ban expiry times
|
||||
const effectiveBannedUntil = Math.max(row.banned_until || 0, row.idle_banned_until || 0);
|
||||
const bannedUntil = effectiveBannedUntil > 0 ? new Date(effectiveBannedUntil * 1000).toLocaleString() : '-';
|
||||
const failures = row.total_failures || 0;
|
||||
const authedSuccessfully = row.has_authed_successfully ? '✅ Yes' : '❌ No';
|
||||
const connectionAttempts = row.total_connections || 0;
|
||||
|
||||
return `<tr>
|
||||
<td>${escapeHtml(row.ip)}</td>
|
||||
<td>${status}</td>
|
||||
<td>${statusLabel}</td>
|
||||
<td>${bannedUntil}</td>
|
||||
<td>${failures}</td>
|
||||
<td>${authedSuccessfully}</td>
|
||||
<td>${connectionAttempts}</td>
|
||||
<td>
|
||||
${isBanned ? `<button type="button" onclick="unbanIp('${escapeHtml(row.ip)}')">Unban</button>` : ''}
|
||||
${isBanned && !isWhitelisted ? `<button type="button" onclick="unbanIp('${escapeHtml(row.ip)}')">Unban</button>` : ''}
|
||||
<button type="button" onclick="deleteIpBan('${escapeHtml(row.ip)}')">Delete</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
@@ -6339,7 +6380,7 @@ async function deleteIpBan(ip) {
|
||||
setTimeout(() => loadIpBans(), 500);
|
||||
}
|
||||
|
||||
// Set filter and reload
|
||||
// Set filter and re-render from cache (no re-fetch needed)
|
||||
function setIpBansFilter(filter) {
|
||||
ipBansFilter = filter;
|
||||
|
||||
@@ -6348,8 +6389,12 @@ function setIpBansFilter(filter) {
|
||||
document.getElementById('ip-ban-filter-banned').classList.toggle('active', filter === 'banned');
|
||||
document.getElementById('ip-ban-filter-expired').classList.toggle('active', filter === 'expired');
|
||||
|
||||
// Reload with new filter
|
||||
loadIpBans();
|
||||
// Re-render from cache if available, otherwise fetch
|
||||
if (ipBansCachedResults !== null) {
|
||||
handleIpBansResponse({ rows: null, columns: null, _cachedResults: ipBansCachedResults });
|
||||
} else {
|
||||
loadIpBans();
|
||||
}
|
||||
}
|
||||
|
||||
// Escape HTML to prevent XSS
|
||||
@@ -6360,6 +6405,63 @@ function escapeHtml(text) {
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Add IP to whitelist
|
||||
async function addToWhitelist() {
|
||||
const input = document.getElementById('whitelist-ip-input');
|
||||
const status = document.getElementById('whitelist-status');
|
||||
const ip = input.value.trim();
|
||||
if (!ip) { status.innerHTML = '<span style="color:red">Enter an IP address</span>'; return; }
|
||||
|
||||
// Get current whitelist from config
|
||||
const current = (currentConfig && currentConfig.idle_ban_whitelist) || '';
|
||||
const ips = current.split(',').map(s => s.trim()).filter(s => s.length > 0);
|
||||
if (ips.includes(ip)) { status.innerHTML = '<span style="color:orange">Already whitelisted</span>'; return; }
|
||||
ips.push(ip);
|
||||
const newValue = ips.join(', ');
|
||||
|
||||
try {
|
||||
await sendAdminCommand(['config_set', 'idle_ban_whitelist', newValue]);
|
||||
status.innerHTML = `<span style="color:green">✅ ${ip} added to whitelist</span>`;
|
||||
input.value = '';
|
||||
updateWhitelistDisplay(newValue);
|
||||
// Update local config cache
|
||||
if (currentConfig) currentConfig.idle_ban_whitelist = newValue;
|
||||
} catch (e) {
|
||||
status.innerHTML = `<span style="color:red">Failed: ${e.message}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Update whitelist display
|
||||
function updateWhitelistDisplay(whitelistStr) {
|
||||
const div = document.getElementById('whitelist-current');
|
||||
if (!div) return;
|
||||
const ips = (whitelistStr || '').split(',').map(s => s.trim()).filter(s => s.length > 0);
|
||||
if (ips.length === 0) {
|
||||
div.innerHTML = '<em>No IPs whitelisted</em>';
|
||||
} else {
|
||||
div.innerHTML = 'Current whitelist: ' + ips.map(ip =>
|
||||
`<span style="background:var(--accent-color,#ff0000);color:white;padding:1px 6px;border-radius:3px;margin:2px;display:inline-block">
|
||||
${escapeHtml(ip)}
|
||||
<button onclick="removeFromWhitelist('${escapeHtml(ip)}')" style="background:none;border:none;color:white;cursor:pointer;padding:0 0 0 4px;font-size:11px">✕</button>
|
||||
</span>`
|
||||
).join('');
|
||||
}
|
||||
}
|
||||
|
||||
// Remove IP from whitelist
|
||||
async function removeFromWhitelist(ip) {
|
||||
const current = (currentConfig && currentConfig.idle_ban_whitelist) || '';
|
||||
const ips = current.split(',').map(s => s.trim()).filter(s => s.length > 0 && s !== ip);
|
||||
const newValue = ips.join(', ');
|
||||
try {
|
||||
await sendAdminCommand(['config_set', 'idle_ban_whitelist', newValue]);
|
||||
updateWhitelistDisplay(newValue);
|
||||
if (currentConfig) currentConfig.idle_ban_whitelist = newValue;
|
||||
} catch (e) {
|
||||
log('Failed to remove from whitelist: ' + e.message, 'ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize IP Bans event listeners
|
||||
function initIpBansEventListeners() {
|
||||
const addBanBtn = document.getElementById('add-ban-btn');
|
||||
@@ -6367,13 +6469,14 @@ function initIpBansEventListeners() {
|
||||
const filterAllBtn = document.getElementById('ip-ban-filter-all');
|
||||
const filterBannedBtn = document.getElementById('ip-ban-filter-banned');
|
||||
const filterExpiredBtn = document.getElementById('ip-ban-filter-expired');
|
||||
const addWhitelistBtn = document.getElementById('add-whitelist-btn');
|
||||
|
||||
if (addBanBtn) {
|
||||
addBanBtn.addEventListener('click', banIp);
|
||||
}
|
||||
|
||||
if (refreshBtn) {
|
||||
refreshBtn.addEventListener('click', loadIpBans);
|
||||
refreshBtn.addEventListener('click', () => { ipBansCachedResults = null; loadIpBans(); });
|
||||
}
|
||||
|
||||
if (filterAllBtn) {
|
||||
@@ -6388,24 +6491,15 @@ function initIpBansEventListeners() {
|
||||
filterExpiredBtn.addEventListener('click', () => setIpBansFilter('expired'));
|
||||
}
|
||||
|
||||
if (addWhitelistBtn) {
|
||||
addWhitelistBtn.addEventListener('click', addToWhitelist);
|
||||
}
|
||||
|
||||
// Show current whitelist on page load
|
||||
updateWhitelistDisplay(currentConfig && currentConfig.idle_ban_whitelist);
|
||||
|
||||
console.log('IP Bans event listeners initialized');
|
||||
}
|
||||
|
||||
// Handle SQL query responses for IP bans
|
||||
const originalHandleSqlQueryResponse = handleSqlQueryResponse;
|
||||
handleSqlQueryResponse = function(response) {
|
||||
console.log('=== HANDLING SQL QUERY RESPONSE ===');
|
||||
console.log('Response:', response);
|
||||
|
||||
// Check if this is an IP bans query
|
||||
if (response.query_id && (response.query_id.startsWith('ip_ban') || response.query_id === 'ip_bans_load')) {
|
||||
handleIpBansResponse(response);
|
||||
return;
|
||||
}
|
||||
|
||||
// Call original handler for other queries
|
||||
return originalHandleSqlQueryResponse(response);
|
||||
};
|
||||
|
||||
// Initialize when DOM is ready
|
||||
document.addEventListener('DOMContentLoaded', initIpBansEventListeners);
|
||||
@@ -85,7 +85,7 @@ static const struct {
|
||||
|
||||
// Debug Level (0=none, 1=errors, 2=warnings, 3=info, 4=debug, 5=trace)
|
||||
// Can be changed at runtime without restart via config_set admin command
|
||||
{"debug_level", "0"},
|
||||
{"debug_level", "3"},
|
||||
|
||||
// IP Auth Failure Ban Settings
|
||||
// Ban IPs that repeatedly fail NIP-42 authentication
|
||||
@@ -103,8 +103,8 @@ static const struct {
|
||||
// Ban IPs that connect but never send REQ or EVENT (idle or early disconnect)
|
||||
{"idle_connection_timeout_sec", "30"}, // Seconds before idle connection is closed (0 = disabled)
|
||||
{"idle_ban_enabled", "true"}, // Whether to ban IPs with idle failures
|
||||
{"idle_ban_threshold", "3"}, // Idle failures before ban
|
||||
{"idle_ban_window_sec", "60"}, // Window to count idle failures in
|
||||
{"idle_ban_threshold", "1"}, // Idle failures before ban (1 = ban on first offense)
|
||||
{"idle_ban_window_sec", "30"}, // Window to count idle failures in
|
||||
{"idle_ban_duration_sec", "300"}, // Initial ban duration (doubles each time, max 24h)
|
||||
|
||||
// SQLite Performance Tuning
|
||||
|
||||
File diff suppressed because one or more lines are too long
+28
-2
@@ -250,9 +250,34 @@ void ip_ban_save_to_db(sqlite3* db) {
|
||||
DEBUG_TRACE("IP ban table saved: %d entries written to DB", saved);
|
||||
}
|
||||
|
||||
// Check if an IP is in the idle_ban_whitelist config (comma-separated list)
|
||||
static int ip_is_whitelisted(const char* ip) {
|
||||
const char* whitelist = get_config_value("idle_ban_whitelist");
|
||||
if (!whitelist || whitelist[0] == '\0') return 0;
|
||||
|
||||
// Make a mutable copy to tokenize
|
||||
char buf[1024];
|
||||
strncpy(buf, whitelist, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
||||
char* token = strtok(buf, ",");
|
||||
while (token) {
|
||||
// Trim leading/trailing spaces
|
||||
while (*token == ' ') token++;
|
||||
char* end = token + strlen(token) - 1;
|
||||
while (end > token && *end == ' ') { *end = '\0'; end--; }
|
||||
if (strcmp(token, ip) == 0) return 1;
|
||||
token = strtok(NULL, ",");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ip_ban_is_banned(const char* ip) {
|
||||
if (!ip || !g_initialized) return 0;
|
||||
|
||||
// Whitelisted IPs are never banned
|
||||
if (ip_is_whitelisted(ip)) return 0;
|
||||
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
int idx = find_slot(ip);
|
||||
if (idx < 0 || g_ban_table[idx].state == IP_BAN_EMPTY) {
|
||||
@@ -359,9 +384,10 @@ void ip_ban_record_failure(const char* ip) {
|
||||
void ip_ban_record_idle_failure(const char* ip) {
|
||||
if (!ip || !g_initialized) return;
|
||||
if (!get_config_bool("idle_ban_enabled", 1)) return;
|
||||
if (ip_is_whitelisted(ip)) return; // Never record idle failures for whitelisted IPs
|
||||
|
||||
int threshold = get_config_int("idle_ban_threshold", 3);
|
||||
int window_sec = get_config_int("idle_ban_window_sec", 60);
|
||||
int threshold = get_config_int("idle_ban_threshold", 1);
|
||||
int window_sec = get_config_int("idle_ban_window_sec", 30);
|
||||
int ban_duration = get_config_int("idle_ban_duration_sec", 300);
|
||||
|
||||
pthread_mutex_lock(&g_ban_mutex);
|
||||
|
||||
+2
-2
@@ -13,8 +13,8 @@
|
||||
// Using CRELAY_ prefix to avoid conflicts with nostr_core_lib VERSION macros
|
||||
#define CRELAY_VERSION_MAJOR 1
|
||||
#define CRELAY_VERSION_MINOR 2
|
||||
#define CRELAY_VERSION_PATCH 31
|
||||
#define CRELAY_VERSION "v1.2.31"
|
||||
#define CRELAY_VERSION_PATCH 44
|
||||
#define CRELAY_VERSION "v1.2.44"
|
||||
|
||||
// Relay metadata (authoritative source for NIP-11 information)
|
||||
#define RELAY_NAME "C-Relay"
|
||||
|
||||
+17
-1
@@ -405,6 +405,12 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_HTTP:
|
||||
// Handle HTTP requests
|
||||
// Mark session as active so HTTP requests don't trigger idle ban
|
||||
if (pss) {
|
||||
pthread_mutex_lock(&pss->session_lock);
|
||||
pss->session_active = 1;
|
||||
pthread_mutex_unlock(&pss->session_lock);
|
||||
}
|
||||
{
|
||||
char *requested_uri = (char *)in;
|
||||
|
||||
@@ -458,6 +464,12 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
int is_nip11_request = (strstr(accept_header, "application/nostr+json") != NULL);
|
||||
|
||||
if (is_nip11_request) {
|
||||
// Mark session as active so HTTP requests don't trigger idle ban
|
||||
if (pss) {
|
||||
pthread_mutex_lock(&pss->session_lock);
|
||||
pss->session_active = 1;
|
||||
pthread_mutex_unlock(&pss->session_lock);
|
||||
}
|
||||
// Handle NIP-11 request
|
||||
if (handle_nip11_http_request(wsi, accept_header) == 0) {
|
||||
return 0; // Successfully handled
|
||||
@@ -622,6 +634,9 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
pss->challenge_created = 0;
|
||||
pss->challenge_expires = 0;
|
||||
|
||||
// Mark as WebSocket connection (not HTTP)
|
||||
pss->is_websocket = 1;
|
||||
|
||||
// Register in global connection list for idle tracking
|
||||
connection_list_add(wsi, pss);
|
||||
|
||||
@@ -2248,7 +2263,8 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// This catches:
|
||||
// 1. Idle connections that timed out (never sent REQ/EVENT/AUTH)
|
||||
// 2. Early disconnects (client closed before sending REQ/EVENT/AUTH)
|
||||
if (!pss->session_active && strlen(pss->client_ip) > 0) {
|
||||
// Only record for WebSocket connections, not HTTP requests (NIP-11, embedded files)
|
||||
if (!pss->session_active && pss->is_websocket && strlen(pss->client_ip) > 0) {
|
||||
// Use separate idle failure recording (has its own threshold/duration)
|
||||
ip_ban_record_idle_failure(pss->client_ip);
|
||||
DEBUG_LOG("Recording idle/early-disconnect failure for IP %s (connected %ld seconds)",
|
||||
|
||||
@@ -93,6 +93,7 @@ struct per_session_data {
|
||||
// Session activity tracking for idle connection banning
|
||||
int session_active; // 1 if client sent REQ or EVENT, 0 otherwise
|
||||
int idle_timeout_sec; // Timeout value for this session (copied from config)
|
||||
int is_websocket; // 1 if this is a WebSocket connection, 0 for HTTP
|
||||
};
|
||||
|
||||
// NIP-11 HTTP session data structure for managing buffer lifetime
|
||||
|
||||
Reference in New Issue
Block a user