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:
@@ -174,7 +174,7 @@ func (s *Server) dashboardHandler(w http.ResponseWriter, _ *http.Request) {
|
||||
data["DiskWarnings"] = s.alertManager.GetInlineAlerts("dashboard")
|
||||
}
|
||||
|
||||
s.render(w, "dashboard", data)
|
||||
s.executeTemplate(w, r, "dashboard", data)
|
||||
}
|
||||
|
||||
func (s *Server) stacksHandler(w http.ResponseWriter, _ *http.Request) {
|
||||
@@ -201,7 +201,7 @@ func (s *Server) stacksHandler(w http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
data["StorageLabels"] = storageLabels
|
||||
|
||||
s.render(w, "stacks", data)
|
||||
s.executeTemplate(w, r, "stacks", data)
|
||||
}
|
||||
|
||||
func (s *Server) logsHandler(w http.ResponseWriter, r *http.Request, name string) {
|
||||
@@ -226,7 +226,7 @@ func (s *Server) logsHandler(w http.ResponseWriter, r *http.Request, name string
|
||||
data := s.baseData("logs", stack.Meta.DisplayName+" — Naplók")
|
||||
data["Stack"] = stack
|
||||
data["Logs"] = logs
|
||||
s.render(w, "logs", data)
|
||||
s.executeTemplate(w, r, "logs", data)
|
||||
}
|
||||
|
||||
func (s *Server) deployHandler(w http.ResponseWriter, r *http.Request, name string) {
|
||||
@@ -370,7 +370,7 @@ func (s *Server) deployHandler(w http.ResponseWriter, r *http.Request, name stri
|
||||
data["FlashError"] = flashErr
|
||||
}
|
||||
|
||||
s.render(w, "deploy", data)
|
||||
s.executeTemplate(w, r, "deploy", data)
|
||||
}
|
||||
|
||||
func (s *Server) appDetailHandler(w http.ResponseWriter, r *http.Request, slug string) {
|
||||
@@ -403,7 +403,7 @@ func (s *Server) appDetailHandler(w http.ResponseWriter, r *http.Request, slug s
|
||||
data["HasAppInfo"] = found.Meta.HasAppInfo()
|
||||
data["HasOptionalConfig"] = found.Meta.HasOptionalConfig()
|
||||
|
||||
s.render(w, "app_info", data)
|
||||
s.executeTemplate(w, r, "app_info", data)
|
||||
}
|
||||
|
||||
func (s *Server) monitoringHandler(w http.ResponseWriter, _ *http.Request) {
|
||||
@@ -453,7 +453,7 @@ func (s *Server) monitoringHandler(w http.ResponseWriter, _ *http.Request) {
|
||||
data["AllPingsConfigured"] = allConfigured
|
||||
}
|
||||
|
||||
s.render(w, "monitoring", data)
|
||||
s.executeTemplate(w, r, "monitoring", data)
|
||||
}
|
||||
|
||||
// isPingConfigured returns true if a healthcheck ping UUID is non-empty and not a placeholder.
|
||||
@@ -638,7 +638,7 @@ func (s *Server) backupsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data["Backup"] = nil
|
||||
}
|
||||
|
||||
s.render(w, "backups", data)
|
||||
s.executeTemplate(w, r, "backups", data)
|
||||
}
|
||||
|
||||
// Tier2DriveGroup holds grouped Tier 2 cross-drive backup items for one destination drive.
|
||||
@@ -1017,7 +1017,7 @@ func (s *Server) settingsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if msg := r.URL.Query().Get("storage_msg"); msg == "success" {
|
||||
data["StorageSuccess"] = r.URL.Query().Get("storage_detail")
|
||||
}
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
}
|
||||
|
||||
func (s *Server) settingsPasswordHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -1032,21 +1032,21 @@ func (s *Server) settingsPasswordHandler(w http.ResponseWriter, r *http.Request)
|
||||
effectiveHash := s.effectivePasswordHash()
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(effectiveHash), []byte(currentPassword)); err != nil {
|
||||
data["PasswordError"] = "Hibás jelenlegi jelszó"
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate new password length
|
||||
if len(newPassword) < 8 {
|
||||
data["PasswordError"] = "A jelszónak legalább 8 karakter hosszúnak kell lennie"
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate passwords match
|
||||
if newPassword != confirmPassword {
|
||||
data["PasswordError"] = "A két jelszó nem egyezik"
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1055,7 +1055,7 @@ func (s *Server) settingsPasswordHandler(w http.ResponseWriter, r *http.Request)
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to hash new password: %v", err)
|
||||
data["PasswordError"] = "Belső hiba a jelszó mentésekor"
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1063,7 +1063,7 @@ func (s *Server) settingsPasswordHandler(w http.ResponseWriter, r *http.Request)
|
||||
if err := s.settings.SetPasswordHash(string(hash)); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to save password to settings.json: %v", err)
|
||||
data["PasswordError"] = "Belső hiba a jelszó mentésekor"
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1126,7 +1126,7 @@ func (s *Server) settingsNotificationsHandler(w http.ResponseWriter, r *http.Req
|
||||
s.logger.Printf("[ERROR] Failed to save notification prefs: %v", err)
|
||||
data := s.settingsData()
|
||||
data["NotificationError"] = "Hiba a beállítások mentésekor"
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1144,7 +1144,7 @@ func (s *Server) settingsNotificationsHandler(w http.ResponseWriter, r *http.Req
|
||||
} else {
|
||||
data["NotificationSuccess"] = "Értesítési beállítások mentve."
|
||||
}
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
}
|
||||
|
||||
func (s *Server) settingsNotificationsTestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -1152,7 +1152,7 @@ func (s *Server) settingsNotificationsTestHandler(w http.ResponseWriter, r *http
|
||||
|
||||
if s.notifier == nil {
|
||||
data["NotificationError"] = "Az értesítések nincsenek bekapcsolva"
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1160,12 +1160,12 @@ func (s *Server) settingsNotificationsTestHandler(w http.ResponseWriter, r *http
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERROR] Test notification failed: %v", err)
|
||||
data["NotificationError"] = fmt.Sprintf("Teszt email küldése sikertelen: %v", err)
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
data["NotificationSuccess"] = "Teszt email elküldve."
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
}
|
||||
|
||||
// --- Storage path management handlers ---
|
||||
@@ -1279,21 +1279,21 @@ func (s *Server) settingsStorageAddHandler(w http.ResponseWriter, r *http.Reques
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil || !fi.IsDir() {
|
||||
data["StorageError"] = "Az útvonal nem létezik vagy nem mappa."
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Is mount point
|
||||
if !system.IsMountPoint(path) {
|
||||
data["StorageError"] = "Ez az útvonal nem külön csatlakoztatott meghajtó. Adatok az SSD-re kerülnének!"
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Writable
|
||||
if !system.IsWritable(path) {
|
||||
data["StorageError"] = "Az útvonal nem írható."
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1301,7 +1301,7 @@ func (s *Server) settingsStorageAddHandler(w http.ResponseWriter, r *http.Reques
|
||||
for _, existing := range s.settings.GetStoragePaths() {
|
||||
if system.PathsOverlap(path, existing.Path) {
|
||||
data["StorageError"] = fmt.Sprintf("Az útvonal átfedi a már regisztrált %s útvonalat.", existing.Path)
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1322,7 +1322,7 @@ func (s *Server) settingsStorageAddHandler(w http.ResponseWriter, r *http.Reques
|
||||
if err := s.settings.AddStoragePath(sp); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to add storage path: %v", err)
|
||||
data["StorageError"] = "Hiba a mentés során."
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1341,7 +1341,7 @@ func (s *Server) settingsStorageRemoveHandler(w http.ResponseWriter, r *http.Req
|
||||
apps := s.appsUsingPath(path)
|
||||
if len(apps) > 0 {
|
||||
data["StorageError"] = fmt.Sprintf("Nem törölhető: az alábbi alkalmazások használják: %s", strings.Join(apps, ", "))
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1349,7 +1349,7 @@ func (s *Server) settingsStorageRemoveHandler(w http.ResponseWriter, r *http.Req
|
||||
for _, sp := range s.settings.GetStoragePaths() {
|
||||
if sp.Path == path && sp.IsDefault {
|
||||
data["StorageError"] = "Az alapértelmezett adattároló nem törölhető."
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1357,13 +1357,13 @@ func (s *Server) settingsStorageRemoveHandler(w http.ResponseWriter, r *http.Req
|
||||
// Check: last path
|
||||
if len(s.settings.GetStoragePaths()) <= 1 {
|
||||
data["StorageError"] = "Az utolsó adattároló nem törölhető."
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.settings.RemoveStoragePath(path); err != nil {
|
||||
data["StorageError"] = "Hiba a törlés során."
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1406,7 +1406,7 @@ func (s *Server) settingsStorageLabelHandler(w http.ResponseWriter, r *http.Requ
|
||||
if label == "" || len(label) > 50 {
|
||||
data := s.settingsData()
|
||||
data["StorageError"] = "A megnevezés nem lehet üres és legfeljebb 50 karakter."
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1414,7 +1414,7 @@ func (s *Server) settingsStorageLabelHandler(w http.ResponseWriter, r *http.Requ
|
||||
s.logger.Printf("[ERROR] Failed to set storage label: %v", err)
|
||||
data := s.settingsData()
|
||||
data["StorageError"] = "Hiba a megnevezés mentésekor."
|
||||
s.render(w, "settings", data)
|
||||
s.executeTemplate(w, r, "settings", data)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user