hub v0.3.8 — CSRF protection + secure session model
- server.go: replace literal hub_session=authenticated with random 64-char hex
session tokens stored server-side (hubSession map + sync.RWMutex); per-session
CSRF tokens; CleanupSessions goroutine; SameSite=Lax+Secure cookie; CSRF
validation in ServeHTTP; csrfToken/csrfField helpers
- configs.go: add html/template import; pass CSRFField/CSRFToken to all template
renders; renderConfigForm gains r *http.Request parameter
- config_form.html: {{.CSRFField}} in form
- customer_unified.html: meta csrf-token + csrfHeaders() JS; {{.CSRFField}} in
all 5 POST forms; csrfHeaders() on 3 fetch calls
- main.go: start CleanupSessions goroutine
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
@@ -106,10 +107,12 @@ func (s *Server) handleConfigList(w http.ResponseWriter, r *http.Request) {
|
||||
Customers []customerListEntry
|
||||
ActiveNav string
|
||||
Flash string
|
||||
CSRFToken string
|
||||
}{
|
||||
Customers: entries,
|
||||
ActiveNav: "configs",
|
||||
Flash: r.URL.Query().Get("flash"),
|
||||
CSRFToken: s.csrfToken(r),
|
||||
}
|
||||
s.templates.ExecuteTemplate(w, "configs.html", data)
|
||||
}
|
||||
@@ -276,6 +279,8 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
|
||||
|
||||
Flash string
|
||||
ActiveNav string
|
||||
CSRFField template.HTML
|
||||
CSRFToken string
|
||||
}
|
||||
|
||||
data := pageData{
|
||||
@@ -312,6 +317,8 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
|
||||
|
||||
Flash: r.URL.Query().Get("flash"),
|
||||
ActiveNav: "configs",
|
||||
CSRFField: s.csrfField(r),
|
||||
CSRFToken: s.csrfToken(r),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
@@ -328,11 +335,13 @@ func (s *Server) handleConfigNewForm(w http.ResponseWriter, r *http.Request) {
|
||||
Overrides map[string]interface{}
|
||||
ActiveNav string
|
||||
Error string
|
||||
CSRFField template.HTML
|
||||
}{
|
||||
IsNew: true,
|
||||
Config: &store.CustomerConfig{},
|
||||
Overrides: make(map[string]interface{}),
|
||||
ActiveNav: "configs",
|
||||
CSRFField: s.csrfField(r),
|
||||
}
|
||||
s.templates.ExecuteTemplate(w, "config_form.html", data)
|
||||
}
|
||||
@@ -346,7 +355,7 @@ func (s *Server) handleConfigCreate(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
customerID := strings.TrimSpace(r.FormValue("customer_id"))
|
||||
if customerID == "" || !validCustomerID.MatchString(customerID) {
|
||||
s.renderConfigForm(w, true, &store.CustomerConfig{
|
||||
s.renderConfigForm(w, r, true, &store.CustomerConfig{
|
||||
CustomerName: r.FormValue("customer_name"),
|
||||
Domain: r.FormValue("domain"),
|
||||
Email: r.FormValue("email"),
|
||||
@@ -357,7 +366,7 @@ func (s *Server) handleConfigCreate(w http.ResponseWriter, r *http.Request) {
|
||||
// Check for duplicates
|
||||
existing, _ := s.store.GetCustomerConfig(customerID)
|
||||
if existing != nil {
|
||||
s.renderConfigForm(w, true, &store.CustomerConfig{
|
||||
s.renderConfigForm(w, r, true, &store.CustomerConfig{
|
||||
CustomerID: customerID,
|
||||
CustomerName: r.FormValue("customer_name"),
|
||||
Domain: r.FormValue("domain"),
|
||||
@@ -418,11 +427,13 @@ func (s *Server) handleConfigEditForm(w http.ResponseWriter, r *http.Request, cu
|
||||
Overrides map[string]interface{}
|
||||
ActiveNav string
|
||||
Error string
|
||||
CSRFField template.HTML
|
||||
}{
|
||||
IsNew: false,
|
||||
Config: cfg,
|
||||
Overrides: overrides,
|
||||
ActiveNav: "configs",
|
||||
CSRFField: s.csrfField(r),
|
||||
}
|
||||
s.templates.ExecuteTemplate(w, "config_form.html", data)
|
||||
}
|
||||
@@ -655,7 +666,7 @@ func (s *Server) handleCreateConfigFromReport(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
// renderConfigForm is a helper to re-render the form with an error.
|
||||
func (s *Server) renderConfigForm(w http.ResponseWriter, isNew bool, cfg *store.CustomerConfig, overrides map[string]interface{}, errMsg string) {
|
||||
func (s *Server) renderConfigForm(w http.ResponseWriter, r *http.Request, isNew bool, cfg *store.CustomerConfig, overrides map[string]interface{}, errMsg string) {
|
||||
if overrides == nil {
|
||||
overrides = make(map[string]interface{})
|
||||
}
|
||||
@@ -665,12 +676,14 @@ func (s *Server) renderConfigForm(w http.ResponseWriter, isNew bool, cfg *store.
|
||||
Overrides map[string]interface{}
|
||||
ActiveNav string
|
||||
Error string
|
||||
CSRFField template.HTML
|
||||
}{
|
||||
IsNew: isNew,
|
||||
Config: cfg,
|
||||
Overrides: overrides,
|
||||
ActiveNav: "configs",
|
||||
Error: errMsg,
|
||||
CSRFField: s.csrfField(r),
|
||||
}
|
||||
s.templates.ExecuteTemplate(w, "config_form.html", data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user