v0.6.1: Code review bugfixes — 7 correctness/safety/quality fixes

- Fix http.NotFound(w, nil) → pass actual request in handlers
- Fix dashboard running/stopped counts to match displayed stacks
- Fix Secure cookie blocking HTTP login (dynamic based on request)
- Remove misleading subtle.ConstantTimeCompare in session check
- Fix cleanupSessions goroutine leak (proper ticker + done channel)
- Add http.MaxBytesReader (1MB) to API POST endpoints
- Cache time.LoadLocation("Europe/Budapest") in template funcmap

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 14:40:13 +01:00
parent 104c97040c
commit ded0cbb842
6 changed files with 56 additions and 57 deletions
+24 -17
View File
@@ -2,7 +2,6 @@ package web
import (
"crypto/rand"
"crypto/subtle"
"encoding/hex"
"fmt"
"net/http"
@@ -13,7 +12,6 @@ import (
)
type session struct {
token string
expiresAt time.Time
}
@@ -81,14 +79,15 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
}
token := s.createSession()
isSecure := r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https"
http.SetCookie(w, &http.Cookie{
Name: sessionCookieName,
Value: token,
Path: "/",
MaxAge: int(sessionMaxAge.Seconds()),
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
Secure: true,
SameSite: http.SameSiteLaxMode,
Secure: isSecure,
})
s.logger.Printf("[INFO] Login from %s", r.RemoteAddr)
@@ -111,7 +110,7 @@ func (s *Server) createSession() string {
token := hex.EncodeToString(b)
s.sessionsMu.Lock()
s.sessions[token] = &session{token: token, expiresAt: time.Now().Add(sessionMaxAge)}
s.sessions[token] = &session{expiresAt: time.Now().Add(sessionMaxAge)}
s.sessionsMu.Unlock()
return token
@@ -120,27 +119,35 @@ func (s *Server) createSession() string {
func (s *Server) isValidSession(token string) bool {
s.sessionsMu.RLock()
defer s.sessionsMu.RUnlock()
sess, ok := s.sessions[token]
if !ok || time.Now().After(sess.expiresAt) {
return false
}
return subtle.ConstantTimeCompare([]byte(sess.token), []byte(token)) == 1
return ok && time.Now().Before(sess.expiresAt)
}
func (s *Server) cleanupSessions() {
for range time.Tick(15 * time.Minute) {
s.sessionsMu.Lock()
now := time.Now()
for t, sess := range s.sessions {
if now.After(sess.expiresAt) {
delete(s.sessions, t)
ticker := time.NewTicker(15 * time.Minute)
defer ticker.Stop()
for {
select {
case <-s.done:
return
case <-ticker.C:
s.sessionsMu.Lock()
now := time.Now()
for t, sess := range s.sessions {
if now.After(sess.expiresAt) {
delete(s.sessions, t)
}
}
s.sessionsMu.Unlock()
}
s.sessionsMu.Unlock()
}
}
// Close signals the server to stop background goroutines.
func (s *Server) Close() {
close(s.done)
}
func (s *Server) renderLogin(w http.ResponseWriter, errorMsg string) {
data := map[string]interface{}{
"Title": "Bejelentkezés",