v0.22.0: First-run setup wizard, local infra backup, hub verification
New controller features:
- Web-based setup wizard replaces docker-setup.sh interactive config
- Dual listener: :8080 (Traefik) + :8081 (direct HTTP for LAN)
- Drive scanner finds .felhom-infra-backup/ on all block devices
- Hub recovery pull (GET /api/v1/recovery/{id}) with retrieval password
- Fresh install: Hub config download or manual wizard
- CSRF protection, state persistence, Hungarian UI
- Local infra backup written to all connected drives after each backup cycle
- .felhom-infra-backup/backup.json + metadata.json with SHA256 checksum
- Hub verification: parse customer_blocked from report push response
- Limited mode after 7 days without verification
- Recovery info page on Settings + recovery-info.txt file generation
- Pending events queue: DR events sent to Hub on next report push
- docker-setup.sh v6.0.0: removed interactive wizard, minimal controller.yaml only
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
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 {
|
||||
// Fallback to time-based (extremely unlikely)
|
||||
return "fallback-csrf-token"
|
||||
}
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user