package webui import ( "net" "net/http" "mailgoserver/internal/db" ) func (a *App) ipsList(w http.ResponseWriter, r *http.Request) { ips, err := a.DB.ListWhitelistedIPs() if err != nil { setFlash(w, "error", "Error loading IP whitelist") } scope := scopeFromContext(r) var pairs [][2]any for _, ip := range ips { if !scope.Allowed(ip.DomainID) { continue } pairs = append(pairs, [2]any{ip.WhitelistedIP, M{"domain_name": ip.DomainName}}) } a.render(w, r, "ips.html", M{"active": "ips", "ips": pairs}) } func (a *App) addIPForm(w http.ResponseWriter, r *http.Request) { domains, _ := a.accessibleDomains(r) a.render(w, r, "add_ip.html", M{"active": "ips", "domains": domains, "prefill_ip": r.URL.Query().Get("ip")}) } // addIP mirrors ip_whitelist.py's add_ip() POST branch: IPv4-only validation via // net.ParseIP + To4(), matching Python's socket.inet_aton behavior (CIDR is rejected // here despite edit_ip.html's placeholder implying CIDR support — that mismatch is // preserved from the Python version). func (a *App) addIP(w http.ResponseWriter, r *http.Request) { ip := r.FormValue("ip_address") domainID := int64(atoi(r.FormValue("domain_id"))) storeMessage := r.FormValue("store_message_content") == "on" if net.ParseIP(ip).To4() == nil || domainID == 0 { setFlash(w, "error", "A valid IPv4 address and domain are required") http.Redirect(w, r, Prefix+"/ips/add", http.StatusFound) return } if !requireDomainAccess(w, r, domainID) { return } if exists, _ := a.DB.IPPairExists(ip, domainID, -1); exists { setFlash(w, "error", "This IP is already whitelisted for this domain") http.Redirect(w, r, Prefix+"/ips/add", http.StatusFound) return } if _, err := a.DB.CreateWhitelistedIP(ip, domainID, storeMessage); err != nil { setFlash(w, "error", "Error adding IP") http.Redirect(w, r, Prefix+"/ips/add", http.StatusFound) return } setFlash(w, "success", "IP address whitelisted successfully") http.Redirect(w, r, Prefix+"/ips", http.StatusFound) } // ipWithAccess fetches a whitelisted-IP row by path ID and confirms it belongs to a // domain the current admin can manage. func (a *App) ipWithAccess(w http.ResponseWriter, r *http.Request) (rec *db.WhitelistedIP, ok bool) { rec, err := a.DB.GetWhitelistedIPByID(pathID(r)) if err != nil || rec == nil { http.NotFound(w, r) return nil, false } if !requireDomainAccess(w, r, rec.DomainID) { return nil, false } return rec, true } func (a *App) disableIP(w http.ResponseWriter, r *http.Request) { rec, ok := a.ipWithAccess(w, r) if !ok { return } if err := a.DB.SetWhitelistedIPActive(rec.ID, false); err != nil { setFlash(w, "error", "Error disabling IP") } else { setFlash(w, "success", "IP disabled") } http.Redirect(w, r, Prefix+"/ips", http.StatusFound) } func (a *App) enableIP(w http.ResponseWriter, r *http.Request) { rec, ok := a.ipWithAccess(w, r) if !ok { return } if err := a.DB.SetWhitelistedIPActive(rec.ID, true); err != nil { setFlash(w, "error", "Error enabling IP") } else { setFlash(w, "success", "IP enabled") } http.Redirect(w, r, Prefix+"/ips", http.StatusFound) } func (a *App) removeIP(w http.ResponseWriter, r *http.Request) { rec, ok := a.ipWithAccess(w, r) if !ok { return } if err := a.DB.RemoveWhitelistedIP(rec.ID); err != nil { setFlash(w, "error", "Error removing IP") } else { setFlash(w, "success", "IP permanently removed") } http.Redirect(w, r, Prefix+"/ips", http.StatusFound) } func (a *App) editIPForm(w http.ResponseWriter, r *http.Request) { rec, ok := a.ipWithAccess(w, r) if !ok { return } domains, _ := a.accessibleDomains(r) a.render(w, r, "edit_ip.html", M{"active": "ips", "ip_record": rec, "domains": domains}) } // editIP mirrors ip_whitelist.py's edit_ip() POST branch. func (a *App) editIP(w http.ResponseWriter, r *http.Request) { rec, ok := a.ipWithAccess(w, r) if !ok { return } id := rec.ID domains, _ := a.accessibleDomains(r) ip := r.FormValue("ip_address") domainID := int64(atoi(r.FormValue("domain_id"))) storeMessage := r.FormValue("store_message_content") == "on" if net.ParseIP(ip).To4() == nil || domainID == 0 { setFlash(w, "error", "A valid IPv4 address and domain are required") a.render(w, r, "edit_ip.html", M{"active": "ips", "ip_record": rec, "domains": domains}) return } if !requireDomainAccess(w, r, domainID) { return } if exists, _ := a.DB.IPPairExists(ip, domainID, id); exists { setFlash(w, "error", "This IP is already whitelisted for this domain") a.render(w, r, "edit_ip.html", M{"active": "ips", "ip_record": rec, "domains": domains}) return } if err := a.DB.UpdateWhitelistedIP(id, ip, domainID, storeMessage); err != nil { setFlash(w, "error", "Error updating IP") http.Redirect(w, r, Prefix+"/ips", http.StatusFound) return } setFlash(w, "success", "IP whitelist entry updated successfully") http.Redirect(w, r, Prefix+"/ips", http.StatusFound) }