45f75a916c
Bug fixes: - Add applyEnvOverrides to LoadFromBytes (M05) - Set state=failed on compose-up failure in selfupdate (M16) - Clamp usableMB to min 0 in memory check (M22) - Remove "manual" schedule from triggerAllCrossBackups (M23) - Add mmcblk device handling for partition paths (M21) - Fix stripPartition for mmcblk devices (L25) - Fix TruncateStr for UTF-8 and negative maxLen (L05/L06) - Fix AllDone to return false for empty restore plans (L14) - Fix PushOnce to return actual errors (L39) - Restore pending events on save failure in DrainPendingEvents (M03) - Add duplicate check in AddStoragePath (M04) - Call CleanupTempMounts after drive scan (H13) - Log SetStep save errors (M25) Hardening: - Guard scheduler Start() against double-start (M14) - Acquire mutex in scheduler Stop() before reading cancel (L24) - Cap log lines parameter to 10000 (L31) - Require POST for logout (L32) - Use sync.Once for Server.Close() (L49) - Panic on crypto/rand.Read failure in setup CSRF (L40) - Validate Bearer token against Hub API key in CSRF (H16 fix) - Replace custom hasPrefix with strings.HasPrefix (L13) - Replace simpleHash with crc32.ChecksumIEEE (L48) Cleanup: - Remove dead imageName function (L02) - Remove dead detectHostIPViaRoute function (L03) - Rename shadowed copy variable to cp (L07) - Copy DefaultEnabledEvents in GetNotificationPrefs early return (L09) - Update BUGHUNT.md with comprehensive audit results Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
54 lines
1.3 KiB
Go
54 lines
1.3 KiB
Go
package setup
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"net/http"
|
|
)
|
|
|
|
const csrfCookieName = "felhom_csrf"
|
|
const csrfFormField = "_csrf"
|
|
|
|
// generateCSRFToken creates a random 32-byte hex token.
|
|
func generateCSRFToken() string {
|
|
b := make([]byte, 32)
|
|
if _, err := rand.Read(b); err != nil {
|
|
panic("crypto/rand.Read failed: " + err.Error())
|
|
}
|
|
return hex.EncodeToString(b)
|
|
}
|
|
|
|
// setCSRFCookie sets the CSRF cookie on the response.
|
|
func setCSRFCookie(w http.ResponseWriter, token string) {
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: csrfCookieName,
|
|
Value: token,
|
|
Path: "/",
|
|
SameSite: http.SameSiteStrictMode,
|
|
HttpOnly: false, // JavaScript needs to read it for AJAX if needed
|
|
})
|
|
}
|
|
|
|
// validateCSRF checks that the form field matches the cookie.
|
|
func validateCSRF(r *http.Request) bool {
|
|
cookie, err := r.Cookie(csrfCookieName)
|
|
if err != nil || cookie.Value == "" {
|
|
return false
|
|
}
|
|
formToken := r.FormValue(csrfFormField)
|
|
if formToken == "" {
|
|
return false
|
|
}
|
|
return cookie.Value == formToken
|
|
}
|
|
|
|
// ensureCSRFToken returns the existing CSRF token from the cookie, or generates a new one.
|
|
func ensureCSRFToken(w http.ResponseWriter, r *http.Request) string {
|
|
if cookie, err := r.Cookie(csrfCookieName); err == nil && cookie.Value != "" {
|
|
return cookie.Value
|
|
}
|
|
token := generateCSRFToken()
|
|
setCSRFCookie(w, token)
|
|
return token
|
|
}
|