package web import ( "net/http" "strings" "github.com/owen/vibeproxy/internal/auth" "github.com/owen/vibeproxy/internal/backup" "github.com/owen/vibeproxy/internal/database" "github.com/owen/vibeproxy/internal/logging" ) func (h *Web) handleSettings(w http.ResponseWriter, r *http.Request) { s := h.store.Current().Settings render(w, "settings", PageData{ Active: "settings", Title: "Settings", CSRFToken: h.csrf.Token(w), Flash: r.URL.Query().Get("flash"), FlashKind: r.URL.Query().Get("kind"), Data: s, }) } func (h *Web) handleSettingsUpdate(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } kv := map[string]string{ "acme_email": r.FormValue("acme_email"), "acme_staging": boolStr(r.FormValue("acme_staging") == "on"), "acme_renewal_window_days": r.FormValue("acme_renewal_window_days"), "cert_storage_path": r.FormValue("cert_storage_path"), "log_retention_days": r.FormValue("log_retention_days"), "trusted_proxy_cidrs": strings.Join(splitLines(r.FormValue("trusted_proxy_cidrs")), ","), "backup_enabled": boolStr(r.FormValue("backup_enabled") == "on"), "backup_interval_hours": r.FormValue("backup_interval_hours"), "backup_retention_count": r.FormValue("backup_retention_count"), "backup_path": r.FormValue("backup_path"), } for k, v := range kv { if err := database.SetSetting(h.db, k, v); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } h.reload(r) logging.Audit(h.db, auth.ActorFromContext(r.Context()), "settings_changed", "settings", "") http.Redirect(w, r, "/settings?flash="+flashMsg("Settings saved")+"&kind=success", http.StatusFound) } func boolStr(b bool) string { if b { return "true" } return "false" } func (h *Web) handlePasswordChange(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } username := auth.ActorFromContext(r.Context()) current := r.FormValue("current_password") newPass := r.FormValue("new_password") confirm := r.FormValue("confirm_password") user, err := database.GetUserByUsername(h.db, username) if err != nil { http.Error(w, "user not found", http.StatusInternalServerError) return } ok, _ := auth.VerifyPassword(current, user.PasswordHash) if !ok { http.Redirect(w, r, "/settings?flash="+flashMsg("Current password is incorrect")+"&kind=danger", http.StatusFound) return } if len(newPass) < 8 || newPass != confirm { http.Redirect(w, r, "/settings?flash="+flashMsg("New passwords must match and be at least 8 characters")+"&kind=danger", http.StatusFound) return } hash, err := auth.HashPassword(newPass) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if err := database.UpdateUserPassword(h.db, username, hash, false); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } logging.Audit(h.db, username, "password_changed", username, "") http.Redirect(w, r, "/settings?flash="+flashMsg("Password changed")+"&kind=success", http.StatusFound) } func (h *Web) handleBackupNow(w http.ResponseWriter, r *http.Request) { s := h.store.Current().Settings path, err := backup.Run(h.db, s.BackupPath, s.BackupRetentionCount) if err != nil { http.Redirect(w, r, "/settings?flash="+flashMsg("Backup failed: "+err.Error())+"&kind=danger", http.StatusFound) return } logging.Audit(h.db, auth.ActorFromContext(r.Context()), "settings_changed", "backup", "manual backup: "+path) http.Redirect(w, r, "/settings?flash="+flashMsg("Backup created: "+path)+"&kind=success", http.StatusFound) }