From ae93557d151b95dcd679e00f93eda47f79666ea2 Mon Sep 17 00:00:00 2001 From: sneak Date: Thu, 22 May 2025 09:12:46 -0700 Subject: [PATCH] Add next broadcast timer and enhance web security --- models.go | 2 ++ templates/index.html | 4 +++ webserver.go | 75 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/models.go b/models.go index c20d474..fdac7a8 100644 --- a/models.go +++ b/models.go @@ -32,6 +32,8 @@ type DashboardData struct { TotalBroadcast int NewInLastHour int UnsummarizedCount int + NextBroadcastIn string // Time until the next broadcast attempt + LastBroadcastTime time.Time // When the last broadcast occurred NextUp []Article History []Article RecentLogs []LogEntry diff --git a/templates/index.html b/templates/index.html index 560b8c1..3309f2e 100644 --- a/templates/index.html +++ b/templates/index.html @@ -186,6 +186,10 @@
Awaiting Summary
{{.UnsummarizedCount}}
+
+
Next Broadcast In
+
{{.NextBroadcastIn}}
+
diff --git a/webserver.go b/webserver.go index cf27653..07e5019 100644 --- a/webserver.go +++ b/webserver.go @@ -2,6 +2,7 @@ package main import ( "context" + "fmt" "html/template" "net/http" "strings" @@ -19,8 +20,30 @@ func webServer(shutdown chan struct{}) { return } - // Define HTTP handlers - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + // Create a custom request handler with security headers + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Add security headers + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("X-Frame-Options", "DENY") + w.Header().Set("Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'") + w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") + w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains") + + // Enforce request method + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // Limit request body size (1MB) + r.Body = http.MaxBytesReader(w, r.Body, 1<<20) + + // Only serve the index page + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + data, err := getDashboardData() if err != nil { http.Error(w, "Error fetching data: "+err.Error(), http.StatusInternalServerError) @@ -42,10 +65,14 @@ func webServer(shutdown chan struct{}) { } }) - // Start the server + // Configure server with appropriate timeouts server := &http.Server{ - Addr: ":8080", - Handler: nil, // Use default mux + Addr: ":8080", + Handler: handler, + ReadTimeout: 10 * time.Second, // Time to read the request + WriteTimeout: 30 * time.Second, // Time to write the response + IdleTimeout: 60 * time.Second, // Keep-alive connections timeout + MaxHeaderBytes: 1 << 20, // 1MB max header size } // Create a goroutine for the server @@ -91,9 +118,15 @@ func getDashboardData() (DashboardData, error) { } // Count broadcast articles, recent articles, and unsummarized articles + var lastBroadcastTime time.Time for _, a := range articles { if !a.BroadcastTime.IsZero() && a.BroadcastTime.Unix() > 1 { data.TotalBroadcast++ + + // Track the most recent broadcast time + if a.BroadcastTime.After(lastBroadcastTime) { + lastBroadcastTime = a.BroadcastTime + } } if a.FirstSeen.After(hourAgo) { @@ -105,6 +138,38 @@ func getDashboardData() (DashboardData, error) { } } + // Set the last broadcast time + data.LastBroadcastTime = lastBroadcastTime + + // Calculate time until next broadcast + if lastBroadcastTime.IsZero() { + data.NextBroadcastIn = "As soon as articles are summarized" + } else { + nextBroadcastTime := lastBroadcastTime.Add(BROADCAST_INTERVAL) + if now.After(nextBroadcastTime) { + // If we're past the interval but haven't broadcast yet, + // likely waiting for articles to be summarized + if data.UnsummarizedCount > 0 { + data.NextBroadcastIn = "Waiting for articles to be summarized" + } else { + data.NextBroadcastIn = "Pending (checking every " + BROADCAST_CHECK_INTERVAL.String() + ")" + } + } else { + // We're still within the interval, calculate remaining time + timeUntilNextBroadcast := nextBroadcastTime.Sub(now) + + // Format as hours and minutes + hours := int(timeUntilNextBroadcast.Hours()) + minutes := int(timeUntilNextBroadcast.Minutes()) % 60 + + if hours > 0 { + data.NextBroadcastIn = fmt.Sprintf("%dh %dm", hours, minutes) + } else { + data.NextBroadcastIn = fmt.Sprintf("%dm", minutes) + } + } + } + // Get broadcast history (last 100) history, err := getBroadcastHistory(100) if err != nil {