diff --git a/src/database.py b/src/database.py
index f7b0a59..84eff37 100644
--- a/src/database.py
+++ b/src/database.py
@@ -2269,8 +2269,8 @@ class DatabaseManager:
ip_stats = IpStats(
ip=sanitized_ip,
total_requests=0,
- first_seen=datetime.utcnow(),
- last_seen=datetime.utcnow(),
+ first_seen=datetime.now(),
+ last_seen=datetime.now(),
)
session.add(ip_stats)
diff --git a/src/routes/api.py b/src/routes/api.py
index 276537f..2338390 100644
--- a/src/routes/api.py
+++ b/src/routes/api.py
@@ -152,7 +152,6 @@ async def ban_override(request: Request, body: BanOverrideRequest):
db = get_db()
action_map = {"ban": True, "unban": False, "reset": None}
- override_value = action_map.get(body.action)
if body.action not in action_map:
return JSONResponse(
content={"error": "Invalid action. Use: ban, unban, reset"},
@@ -162,7 +161,7 @@ async def ban_override(request: Request, body: BanOverrideRequest):
if body.action == "ban":
success = db.force_ban_ip(body.ip)
else:
- success = db.set_ban_override(body.ip, override_value)
+ success = db.set_ban_override(body.ip, action_map[body.action])
if success:
get_app_logger().info(f"Ban override: {body.action} on IP {body.ip}")
diff --git a/src/templates/static/js/dashboard.js b/src/templates/static/js/dashboard.js
index 41749cb..39e7df9 100644
--- a/src/templates/static/js/dashboard.js
+++ b/src/templates/static/js/dashboard.js
@@ -252,19 +252,28 @@ document.addEventListener('alpine:init', () => {
}));
});
+// Helper to access Alpine.js component data
+function getAlpineData(selector) {
+ const container = document.querySelector(selector);
+ if (!container) return null;
+ return Alpine.$data ? Alpine.$data(container) : (container._x_dataStack && container._x_dataStack[0]);
+}
+
// Global function for opening IP Insight (used by map popups)
window.openIpInsight = function(ip) {
- // Find the Alpine component and call openIpInsight
- const container = document.querySelector('[x-data="dashboardApp()"]');
- if (container) {
- // Try Alpine 3.x API first, then fall back to older API
- const data = Alpine.$data ? Alpine.$data(container) : (container._x_dataStack && container._x_dataStack[0]);
- if (data && typeof data.openIpInsight === 'function') {
- data.openIpInsight(ip);
- }
+ const data = getAlpineData('[x-data="dashboardApp()"]');
+ if (data && typeof data.openIpInsight === 'function') {
+ data.openIpInsight(ip);
}
};
+// Escape HTML to prevent XSS when inserting into innerHTML
+function escapeHtml(str) {
+ const div = document.createElement('div');
+ div.textContent = str;
+ return div.innerHTML;
+}
+
// Custom modal system (replaces native confirm/alert)
window.krawlModal = {
_create(icon, iconClass, message, buttons) {
@@ -314,13 +323,14 @@ window.krawlModal = {
// Global ban action for IP insight page (auth-gated)
window.ipBanAction = async function(ip, action) {
// Check if authenticated
- const container = document.querySelector('[x-data="dashboardApp()"]');
- const data = container && (Alpine.$data ? Alpine.$data(container) : (container._x_dataStack && container._x_dataStack[0]));
+ const data = getAlpineData('[x-data="dashboardApp()"]');
if (!data || !data.authenticated) {
if (data && typeof data.promptAuth === 'function') data.promptAuth();
return;
}
- const confirmed = await krawlModal.confirm(`Are you sure you want to ${action} IP ${ip}?`);
+ const safeIp = escapeHtml(ip);
+ const safeAction = escapeHtml(action);
+ const confirmed = await krawlModal.confirm(`Are you sure you want to ${safeAction} IP ${safeIp}?`);
if (!confirmed) return;
try {
const resp = await fetch(`${window.__DASHBOARD_PATH__}/api/ban-override`, {
@@ -331,7 +341,7 @@ window.ipBanAction = async function(ip, action) {
});
const result = await resp.json().catch(() => ({}));
if (resp.ok) {
- krawlModal.success(result.message || `${action} successful for ${ip}`);
+ krawlModal.success(escapeHtml(result.message || `${action} successful for ${ip}`));
const overrides = document.getElementById('overrides-container');
if (overrides) {
htmx.ajax('GET', `${window.__DASHBOARD_PATH__}/htmx/ban/overrides?page=1`, {
@@ -340,7 +350,7 @@ window.ipBanAction = async function(ip, action) {
});
}
} else {
- krawlModal.error(result.error || `Failed to ${action} IP ${ip}`);
+ krawlModal.error(escapeHtml(result.error || `Failed to ${action} IP ${ip}`));
}
} catch {
krawlModal.error('Request failed');
@@ -355,8 +365,7 @@ function updateBanActionVisibility(authenticated) {
}
// Update visibility after HTMX swaps in new content
document.addEventListener('htmx:afterSwap', () => {
- const container = document.querySelector('[x-data="dashboardApp()"]');
- const data = container && (Alpine.$data ? Alpine.$data(container) : (container._x_dataStack && container._x_dataStack[0]));
+ const data = getAlpineData('[x-data="dashboardApp()"]');
if (data) updateBanActionVisibility(data.authenticated);
});