Files
mailgoserver/internal/webui/ip_whitelist.go
T
2026-08-12 12:56:22 +01:00

158 lines
4.8 KiB
Go

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)
}