package web import ( "fmt" "net/http" "strconv" "github.com/owen/vibeproxy/internal/auth" "github.com/owen/vibeproxy/internal/database" "github.com/owen/vibeproxy/internal/logging" "github.com/owen/vibeproxy/internal/models" ) func (h *Web) handleAccessListsList(w http.ResponseWriter, r *http.Request) { lists, err := database.ListAccessLists(h.db) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } render(w, "access_lists", PageData{ Active: "access_lists", Title: "Access Lists", CSRFToken: h.csrf.Token(w), Flash: r.URL.Query().Get("flash"), FlashKind: r.URL.Query().Get("kind"), Data: lists, }) } func (h *Web) handleAccessListForm(w http.ResponseWriter, r *http.Request) { var l models.AccessList isNew := true if idStr := r.PathValue("id"); idStr != "" { isNew = false id, _ := strconv.ParseInt(idStr, 10, 64) existing, err := database.GetAccessList(h.db, id) if err != nil { http.Error(w, "access list not found", http.StatusNotFound) return } l = *existing } render(w, "access_list_form", PageData{ Active: "access_lists", Title: pageTitle(isNew, "Access List"), CSRFToken: h.csrf.Token(w), Data: map[string]any{"List": l, "IsNew": isNew}, }) } func (h *Web) handleAccessListCreate(w http.ResponseWriter, r *http.Request) { h.saveAccessListForm(w, r, 0) } func (h *Web) handleAccessListUpdate(w http.ResponseWriter, r *http.Request) { id, _ := strconv.ParseInt(r.PathValue("id"), 10, 64) h.saveAccessListForm(w, r, id) } func (h *Web) saveAccessListForm(w http.ResponseWriter, r *http.Request, id int64) { if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } l := models.AccessList{ ID: id, Name: r.FormValue("name"), DefaultAllow: r.FormValue("default_allow") == "on", Comments: r.FormValue("comments"), } actions := r.Form["rule_action"] cidrs := r.Form["rule_cidr"] for i := range cidrs { if cidrs[i] == "" { continue } l.Rules = append(l.Rules, models.AccessRule{ Action: models.AccessRuleAction(get(actions, i)), CIDR: cidrs[i], Priority: i, }) } existingHashes := map[string]string{} if id != 0 { if existing, err := database.GetAccessList(h.db, id); err == nil { for _, u := range existing.BasicAuthUsers { existingHashes[u.Username] = u.PasswordHash } } } usernames := r.Form["auth_username"] passwords := r.Form["auth_password"] for i := range usernames { if usernames[i] == "" { continue } hash := "" if passwords[i] != "" { h2, err := auth.HashPassword(passwords[i]) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } hash = h2 } else if existing, ok := existingHashes[usernames[i]]; ok { hash = existing // password left blank on edit: keep the existing hash } else { continue // new user with no password set — skip } l.BasicAuthUsers = append(l.BasicAuthUsers, models.AccessListUser{Username: usernames[i], PasswordHash: hash}) } if l.Name == "" { render(w, "access_list_form", PageData{ Active: "access_lists", Title: pageTitle(id == 0, "Access List"), CSRFToken: h.csrf.Token(w), Flash: "Name is required", FlashKind: "danger", Data: map[string]any{"List": l, "IsNew": id == 0}, }) return } if _, err := database.SaveAccessList(h.db, &l); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } h.reload(r) logging.Audit(h.db, auth.ActorFromContext(r.Context()), "access_list_changed", l.Name, "") http.Redirect(w, r, "/access-lists?flash="+flashMsg("Access list saved")+"&kind=success", http.StatusFound) } func (h *Web) handleAccessListDelete(w http.ResponseWriter, r *http.Request) { id, _ := strconv.ParseInt(r.PathValue("id"), 10, 64) if err := database.DeleteAccessList(h.db, id); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } h.reload(r) logging.Audit(h.db, auth.ActorFromContext(r.Context()), "access_list_changed", fmt.Sprintf("id=%d", id), "deleted") http.Redirect(w, r, "/access-lists?flash="+flashMsg("Access list deleted")+"&kind=success", http.StatusFound) }