v0.23.0 — CSRF protection on all browser-facing POST endpoints

Controller:
- internal/web/csrf.go (new): CsrfProtect middleware, csrfToken/csrfField helpers
- auth.go: per-session CSRF token (csrfToken field, csrfTokenForSession method)
- server.go: executeTemplate wrapper auto-injects CSRFField+CSRFToken
- main.go: wire CsrfProtect on all routes; bump to v0.23.0
- handlers.go, storage_handlers.go, handler_restore.go: executeTemplate
- All templates: CSRFField in forms, meta csrf-token, csrfHeaders() JS helper,
  fetch calls updated; sendBeacon→fetch+keepalive in storage_attach.html

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 16:38:56 +01:00
parent ade01470d0
commit 02650e3202
20 changed files with 1143 additions and 75 deletions
+15
View File
@@ -265,6 +265,21 @@ func (s *Server) render(w http.ResponseWriter, name string, data interface{}) {
}
}
// executeTemplate renders a template with CSRF data auto-injected into the data map.
// Use this instead of render() for all authenticated page handlers.
func (s *Server) executeTemplate(w http.ResponseWriter, r *http.Request, name string, data map[string]interface{}) {
if data == nil {
data = make(map[string]interface{})
}
data["CSRFField"] = s.csrfField(r)
data["CSRFToken"] = s.csrfToken(r)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := s.tmpl.ExecuteTemplate(w, name, data); err != nil {
s.logger.Printf("[ERROR] Template error (%s): %v", name, err)
http.Error(w, "Internal error", http.StatusInternalServerError)
}
}
// --- Static file / asset serving ---
func (s *Server) serveCSSHandler(w http.ResponseWriter, r *http.Request) {