landed IP address ban editing
This commit is contained in:
+90
-13
@@ -19,6 +19,8 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IPBanEntry represents an IP address banned from the system.
|
// IPBanEntry represents an IP address banned from the system.
|
||||||
@@ -149,26 +151,74 @@ func setupIPBanSweep() func() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// nukeIPBanCache completely clears the IP ban cache.
|
// nukeIPBanCache completely clears the IP ban cache.
|
||||||
func nukeIPBanCache() {
|
func nukeIPBanCache(bad, good bool) {
|
||||||
banMutex.Lock()
|
banMutex.Lock()
|
||||||
defer banMutex.Unlock()
|
defer banMutex.Unlock()
|
||||||
banSweeperReset <- true // send the reset signal to the sweeper
|
if bad {
|
||||||
for k := range knownBans {
|
banSweeperReset <- true // send the reset signal to the sweeper
|
||||||
delete(knownBans, k)
|
for k := range knownBans {
|
||||||
|
delete(knownBans, k)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for k := range knownGood {
|
if good {
|
||||||
delete(knownGood, k)
|
for k := range knownGood {
|
||||||
|
delete(knownGood, k)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AmIPToString converts an IP addrsss, in terms of low and high 64-bit values, to a string.
|
// IsV4 returns true if the entry's address is an IPv4 address.
|
||||||
func AmIPToString(low, high uint64) string {
|
func (ipb *IPBanEntry) IsV4() bool {
|
||||||
|
ip := AmCombineIP(ipb.AddressLow, ipb.AddressHigh)
|
||||||
|
return ip.To4() != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEnable sets the enable flag on an IP ban entry.
|
||||||
|
func (ipb *IPBanEntry) SetEnable(ctx context.Context, flag bool) error {
|
||||||
|
if flag == ipb.Enable {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err := amdb.ExecContext(ctx, "UPDATE ipban SET enable = ? WHERE id = ?", flag, ipb.Id)
|
||||||
|
if err == nil {
|
||||||
|
nukeIPBanCache(ipb.Enable, !ipb.Enable)
|
||||||
|
ipb.Enable = flag
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes an IP ban entry.
|
||||||
|
func (ipb *IPBanEntry) Delete(ctx context.Context) error {
|
||||||
|
_, err := amdb.ExecContext(ctx, "DELETE FROM ipban WHERE id = ?", ipb.Id)
|
||||||
|
if err == nil {
|
||||||
|
nukeIPBanCache(true, false) // can only affect "ban" cache entries, not "good" ones
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AmCombineIP converts an IP address, in terms of low and high 64-bit values, to a net.IP value.
|
||||||
|
func AmCombineIP(low, high uint64) net.IP {
|
||||||
t := big.NewInt(0).Lsh(new(big.Int).SetUint64(high), 64)
|
t := big.NewInt(0).Lsh(new(big.Int).SetUint64(high), 64)
|
||||||
addr := big.NewInt(0).Or(t, new(big.Int).SetUint64(low))
|
addr := big.NewInt(0).Or(t, new(big.Int).SetUint64(low))
|
||||||
ip := net.IP(addr.FillBytes(make([]byte, 16)))
|
return net.IP(addr.FillBytes(make([]byte, 16)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AmIPToString converts an IP address, in terms of low and high 64-bit values, to a string.
|
||||||
|
func AmIPToString(low, high uint64, asV4 bool) string {
|
||||||
|
ip := AmCombineIP(low, high)
|
||||||
|
if asV4 {
|
||||||
|
ip = ip[12:16]
|
||||||
|
}
|
||||||
return ip.String()
|
return ip.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AmBreakUpIP breaks up the IP address into low and high uint64 values.
|
||||||
|
func AmBreakUpIP(addr net.IP) (uint64, uint64) {
|
||||||
|
iv := big.NewInt(0).SetBytes(addr)
|
||||||
|
ivLo := big.NewInt(0).And(iv, low64mask).Uint64()
|
||||||
|
ivHi := big.NewInt(0).Rsh(iv, 64).Uint64()
|
||||||
|
return ivLo, ivHi
|
||||||
|
}
|
||||||
|
|
||||||
/* AmTestIPBan tests an IP address to see if it's on the banned list.
|
/* AmTestIPBan tests an IP address to see if it's on the banned list.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* ctx - Standard Go context parameter.
|
* ctx - Standard Go context parameter.
|
||||||
@@ -191,10 +241,7 @@ func AmTestIPBan(ctx context.Context, ipAddress string) (string, error) {
|
|||||||
if addr == nil {
|
if addr == nil {
|
||||||
return "", fmt.Errorf("invalid address %s", ipAddress)
|
return "", fmt.Errorf("invalid address %s", ipAddress)
|
||||||
}
|
}
|
||||||
iv := big.NewInt(0)
|
ivLo, ivHi := AmBreakUpIP(addr)
|
||||||
iv.SetBytes(addr)
|
|
||||||
ivLo := big.NewInt(0).And(iv, low64mask).Uint64()
|
|
||||||
ivHi := big.NewInt(0).Rsh(iv, 64).Uint64()
|
|
||||||
row := amdb.QueryRowContext(ctx, `SELECT message, expire FROM ipban WHERE (address_lo & mask_lo) = (? & mask_lo)
|
row := amdb.QueryRowContext(ctx, `SELECT message, expire FROM ipban WHERE (address_lo & mask_lo) = (? & mask_lo)
|
||||||
AND (address_hi & mask_hi) = (? & mask_hi) AND (expire IS NULL OR expire >= NOW())
|
AND (address_hi & mask_hi) = (? & mask_hi) AND (expire IS NULL OR expire >= NOW())
|
||||||
AND enable <> 0 ORDER BY mask_hi DESC, mask_lo DESC`, ivLo, ivHi)
|
AND enable <> 0 ORDER BY mask_hi DESC, mask_lo DESC`, ivLo, ivHi)
|
||||||
@@ -236,3 +283,33 @@ func AmGetIPBan(ctx context.Context, id int32) (*IPBanEntry, error) {
|
|||||||
}
|
}
|
||||||
return &(dbdata[0]), nil
|
return &(dbdata[0]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AmAddIPBan adds a new IP address ban.
|
||||||
|
func AmAddIPBan(ctx context.Context, addr, mask net.IP, expires *time.Time, message string, byUser *User) error {
|
||||||
|
log.Debugf("AmAddIPBan: addr = %s (%v), mask = %s(%v)", addr.String(), addr, mask.String(), mask)
|
||||||
|
if addr.IsUnspecified() {
|
||||||
|
return errors.New("cannot add IP ban with unspecified address")
|
||||||
|
}
|
||||||
|
if mask.IsUnspecified() {
|
||||||
|
return errors.New("cannot add IP ban with unspecified mask")
|
||||||
|
}
|
||||||
|
newEntry := IPBanEntry{
|
||||||
|
Expire: expires,
|
||||||
|
Message: message,
|
||||||
|
BlockByUid: byUser.Uid,
|
||||||
|
}
|
||||||
|
newEntry.AddressLow, newEntry.AddressHigh = AmBreakUpIP(addr)
|
||||||
|
if newEntry.AddressLow == 0 && newEntry.AddressHigh == 0 {
|
||||||
|
return errors.New("invalid or incorrectly-parsed address")
|
||||||
|
}
|
||||||
|
newEntry.MaskLow, newEntry.MaskHigh = AmBreakUpIP(mask)
|
||||||
|
if newEntry.MaskLow == 0 && newEntry.MaskHigh == 0 {
|
||||||
|
return errors.New("invalid or incorrectly-parsed mask")
|
||||||
|
}
|
||||||
|
_, err := amdb.NamedExecContext(ctx, `INSERT INTO ipban (address_lo, address_hi, mask_lo, mask_hi, enable, expire, message, block_by, block_on)
|
||||||
|
VALUES (:address_lo, :address_hi, :mask_lo, :mask_hi, 1, :expire, :message, :block_by, NOW())`, &newEntry)
|
||||||
|
if err == nil {
|
||||||
|
nukeIPBanCache(false, true)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ _(italicized items can be deferred)_
|
|||||||
- Quote banner rotation
|
- Quote banner rotation
|
||||||
- Sysadmin Menu:
|
- Sysadmin Menu:
|
||||||
- ~~Edit Global Properties~~
|
- ~~Edit Global Properties~~
|
||||||
- View/Edit IP Address Bans
|
- ~~View/Edit IP Address Bans~~
|
||||||
- ~~User Account Management~~
|
- ~~User Account Management~~
|
||||||
- System Audit Logs
|
- System Audit Logs
|
||||||
- Import User Accounts
|
- Import User Accounts
|
||||||
|
|||||||
@@ -84,6 +84,8 @@ func setupEcho() *echo.Echo {
|
|||||||
e.GET("/sysadmin/users/:uname/photo", ui.AmWrap(AdminUserPhotoForm))
|
e.GET("/sysadmin/users/:uname/photo", ui.AmWrap(AdminUserPhotoForm))
|
||||||
e.POST("/sysadmin/users/:uname/photo", ui.AmWrap(AdminUserPhoto))
|
e.POST("/sysadmin/users/:uname/photo", ui.AmWrap(AdminUserPhoto))
|
||||||
e.GET("/sysadmin/ipban", ui.AmWrap(IPBanList))
|
e.GET("/sysadmin/ipban", ui.AmWrap(IPBanList))
|
||||||
|
e.GET("/sysadmin/ipban/add", ui.AmWrap(AddIPBanForm))
|
||||||
|
e.POST("/sysadmin/ipban/add", ui.AmWrap(AddIPBan))
|
||||||
e.GET("/create_comm", ui.AmWrap(CreateCommunityForm))
|
e.GET("/create_comm", ui.AmWrap(CreateCommunityForm))
|
||||||
e.POST("/create_comm", ui.AmWrap(CreateCommunity))
|
e.POST("/create_comm", ui.AmWrap(CreateCommunity))
|
||||||
e.GET("/manage_comm", ui.AmWrap(ManageCommunities))
|
e.GET("/manage_comm", ui.AmWrap(ManageCommunities))
|
||||||
|
|||||||
+148
-2
@@ -14,9 +14,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/database"
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
"git.erbosoft.com/amy/amsterdam/ui"
|
"git.erbosoft.com/amy/amsterdam/ui"
|
||||||
@@ -503,8 +505,9 @@ func AdminUserPhoto(ctxt ui.AmContext) (string, any) {
|
|||||||
// templateIPtoString converts an IP address in terms of "low" and "high" 64-bit values to a string.
|
// templateIPtoString converts an IP address in terms of "low" and "high" 64-bit values to a string.
|
||||||
func templateIPtoString(a jet.Arguments) reflect.Value {
|
func templateIPtoString(a jet.Arguments) reflect.Value {
|
||||||
low := a.Get(0).Convert(reflect.TypeFor[uint64]()).Interface().(uint64)
|
low := a.Get(0).Convert(reflect.TypeFor[uint64]()).Interface().(uint64)
|
||||||
high := a.Get(1).Convert(reflect.TypeFor[uint64]()).Interface().(uint64)
|
high := a.Get(1).Convert(reflect.TypeFor[uint64]()).Uint()
|
||||||
return reflect.ValueOf(database.AmIPToString(low, high))
|
v4 := a.Get(2).Convert(reflect.TypeFor[bool]()).Bool()
|
||||||
|
return reflect.ValueOf(database.AmIPToString(low, high, v4))
|
||||||
}
|
}
|
||||||
|
|
||||||
/* IPBanList displays the IP address ban list and allows modification.
|
/* IPBanList displays the IP address ban list and allows modification.
|
||||||
@@ -519,21 +522,164 @@ func IPBanList(ctxt ui.AmContext) (string, any) {
|
|||||||
return "error", ENOACCESS
|
return "error", ENOACCESS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctxt.HasParameter("t") {
|
||||||
|
// toggle enable status
|
||||||
|
id := ctxt.QueryParamInt("t", -1)
|
||||||
|
if id > 0 {
|
||||||
|
ipb, err := database.AmGetIPBan(ctxt.Ctx(), int32(id))
|
||||||
|
if err == nil {
|
||||||
|
err = ipb.SetEnable(ctxt.Ctx(), !(ipb.Enable))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ctxt.VarMap().Set("errorMessage", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ctxt.HasParameter("r") {
|
||||||
|
// delete entry
|
||||||
|
id := ctxt.QueryParamInt("r", -1)
|
||||||
|
if id > 0 {
|
||||||
|
ipb, err := database.AmGetIPBan(ctxt.Ctx(), int32(id))
|
||||||
|
if err == nil {
|
||||||
|
err = ipb.Delete(ctxt.Ctx())
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ctxt.VarMap().Set("errorMessage", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ipbans, err := database.AmListIPBans(ctxt.Ctx())
|
ipbans, err := database.AmListIPBans(ctxt.Ctx())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
usernames := make([]string, len(ipbans))
|
usernames := make([]string, len(ipbans))
|
||||||
|
ipv4 := make([]bool, len(ipbans))
|
||||||
for i, ipb := range ipbans {
|
for i, ipb := range ipbans {
|
||||||
user, err := database.AmGetUser(ctxt.Ctx(), ipb.BlockByUid)
|
user, err := database.AmGetUser(ctxt.Ctx(), ipb.BlockByUid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
usernames[i] = user.Username
|
usernames[i] = user.Username
|
||||||
|
ipv4[i] = ipb.IsV4()
|
||||||
}
|
}
|
||||||
ctxt.VarMap().Set("ipbans", ipbans)
|
ctxt.VarMap().Set("ipbans", ipbans)
|
||||||
ctxt.VarMap().Set("usernames", usernames)
|
ctxt.VarMap().Set("usernames", usernames)
|
||||||
|
ctxt.VarMap().Set("ipv4", ipv4)
|
||||||
ctxt.VarMap().SetFunc("IPtoString", templateIPtoString)
|
ctxt.VarMap().SetFunc("IPtoString", templateIPtoString)
|
||||||
ctxt.SetFrameTitle("Manage IP Address Bans")
|
ctxt.SetFrameTitle("Manage IP Address Bans")
|
||||||
return "framed", "manage_ipban.jet"
|
return "framed", "manage_ipban.jet"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* AddIPBanForm displays the form for adding a banned IP address.
|
||||||
|
* Parameters:
|
||||||
|
* ctxt - The AmContext for the request.
|
||||||
|
* Returns:
|
||||||
|
* Command string dictating what to be rendered.
|
||||||
|
* Data as a parameter for the command string.
|
||||||
|
*/
|
||||||
|
func AddIPBanForm(ctxt ui.AmContext) (string, any) {
|
||||||
|
if !database.AmTestPermission("Global.SysAdminAccess", ctxt.CurrentUser().BaseLevel) {
|
||||||
|
return "error", ENOACCESS
|
||||||
|
}
|
||||||
|
dlg, err := ui.AmLoadDialog("ipban")
|
||||||
|
if err != nil {
|
||||||
|
return "error", err
|
||||||
|
}
|
||||||
|
dlg.Field("mask").Value = "255.255.255.255"
|
||||||
|
dlg.Field("etime").SetInt(1)
|
||||||
|
dlg.Field("eunit").Value = "D"
|
||||||
|
return dlg.Render(ctxt)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AddIPBan adds a new banned IP address.
|
||||||
|
* Parameters:
|
||||||
|
* ctxt - The AmContext for the request.
|
||||||
|
* Returns:
|
||||||
|
* Command string dictating what to be rendered.
|
||||||
|
* Data as a parameter for the command string.
|
||||||
|
*/
|
||||||
|
func AddIPBan(ctxt ui.AmContext) (string, any) {
|
||||||
|
if !database.AmTestPermission("Global.SysAdminAccess", ctxt.CurrentUser().BaseLevel) {
|
||||||
|
return "error", ENOACCESS
|
||||||
|
}
|
||||||
|
dlg, err := ui.AmLoadDialog("ipban")
|
||||||
|
if err != nil {
|
||||||
|
return "error", err
|
||||||
|
}
|
||||||
|
dlg.LoadFromForm(ctxt)
|
||||||
|
btn := dlg.WhichButton(ctxt)
|
||||||
|
if btn == "cancel" {
|
||||||
|
return "redirect", "/sysadmin/ipban"
|
||||||
|
} else if btn != "add" {
|
||||||
|
return "error", EBUTTON
|
||||||
|
}
|
||||||
|
err = dlg.Validate()
|
||||||
|
if err == nil {
|
||||||
|
theAddress := net.ParseIP(dlg.Field("address").Value)
|
||||||
|
isIPv4 := (theAddress.To4() != nil)
|
||||||
|
var theMask net.IP
|
||||||
|
maskStr := dlg.Field("mask").Value
|
||||||
|
if maskStr[0:1] == "/" {
|
||||||
|
maskbits, err := strconv.Atoi(maskStr[1:])
|
||||||
|
if err != nil {
|
||||||
|
return dlg.RenderError(ctxt, fmt.Sprintf("invalid CIDR value: %v", err))
|
||||||
|
}
|
||||||
|
if isIPv4 {
|
||||||
|
if maskbits > (net.IPv4len * 8) {
|
||||||
|
return dlg.RenderError(ctxt, fmt.Sprintf("invalid CIDR value: %v", err))
|
||||||
|
}
|
||||||
|
maskbits += (net.IPv6len - net.IPv4len) * 8
|
||||||
|
} else {
|
||||||
|
if maskbits > (net.IPv6len * 8) {
|
||||||
|
return dlg.RenderError(ctxt, fmt.Sprintf("invalid CIDR value: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tmp := net.CIDRMask(maskbits, net.IPv6len*8)
|
||||||
|
theMask = net.IP(tmp)
|
||||||
|
log.Debugf("computed mask value: %s", theMask.String())
|
||||||
|
} else {
|
||||||
|
theMask = net.ParseIP(maskStr)
|
||||||
|
check := (theMask.To4() != nil)
|
||||||
|
if check != isIPv4 {
|
||||||
|
return dlg.RenderError(ctxt, fmt.Sprintf("inconsistent mask value: %s", maskStr))
|
||||||
|
}
|
||||||
|
a := 0
|
||||||
|
b := 0
|
||||||
|
if isIPv4 {
|
||||||
|
a, b = net.IPMask(theMask.To4()).Size()
|
||||||
|
} else {
|
||||||
|
a, b = net.IPMask(theMask).Size()
|
||||||
|
}
|
||||||
|
if a == 0 && b == 0 {
|
||||||
|
return dlg.RenderError(ctxt, fmt.Sprintf("not a valid mask value: %s", maskStr))
|
||||||
|
}
|
||||||
|
log.Debugf("parsed and vetted mask value: %s", theMask.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var expires *time.Time = nil
|
||||||
|
if dlg.Field("echeck").IsChecked() {
|
||||||
|
n, err := dlg.Field("etime").ValueInt()
|
||||||
|
if err != nil {
|
||||||
|
return dlg.RenderError(ctxt, fmt.Sprintf("invalid time value: %s", dlg.Field("etime").Value))
|
||||||
|
}
|
||||||
|
v := time.Now()
|
||||||
|
switch dlg.Field("eunit").Value {
|
||||||
|
case "D":
|
||||||
|
v = v.AddDate(0, 0, n)
|
||||||
|
case "W":
|
||||||
|
v = v.AddDate(0, 0, n*7)
|
||||||
|
case "M":
|
||||||
|
v = v.AddDate(0, n, 0)
|
||||||
|
case "Y":
|
||||||
|
v = v.AddDate(n, 0, 0)
|
||||||
|
}
|
||||||
|
v = v.UTC()
|
||||||
|
expires = &v
|
||||||
|
}
|
||||||
|
err = database.AmAddIPBan(ctxt.Ctx(), theAddress, theMask, expires, dlg.Field("msg").Value, ctxt.CurrentUser())
|
||||||
|
if err == nil {
|
||||||
|
return "redirect", "/sysadmin/ipban"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dlg.RenderError(ctxt, err.Error())
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import (
|
|||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"net"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -108,6 +110,10 @@ func AmLoadDialog(name string) (*Dialog, error) {
|
|||||||
d.Fields[i].MaxLength = d.Fields[i].Size
|
d.Fields[i].MaxLength = d.Fields[i].Size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if fld.Type == "ipaddress" {
|
||||||
|
d.Fields[i].Size = 15 // max IPv4
|
||||||
|
d.Fields[i].MaxLength = 39 // max IPv6
|
||||||
|
}
|
||||||
if fld.Type == "dropdown" && len(fld.Choices) == 0 {
|
if fld.Type == "dropdown" && len(fld.Choices) == 0 {
|
||||||
return nil, fmt.Errorf("dropdown field %s in dialog %s has no choices", fld.Name, name)
|
return nil, fmt.Errorf("dropdown field %s in dialog %s has no choices", fld.Name, name)
|
||||||
}
|
}
|
||||||
@@ -547,6 +553,35 @@ func validateEmailField(fld *DialogItem) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* validateIPAddressField validates an IP address field.
|
||||||
|
* Parameters:
|
||||||
|
* fld - The field to be validated.
|
||||||
|
* Returns:
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func validateIPAddressField(fld *DialogItem) error {
|
||||||
|
err := validateTextField(fld)
|
||||||
|
if err == nil {
|
||||||
|
if strings.Contains(fld.Param, "mask") {
|
||||||
|
// look for a CIDR mask value like "/24"
|
||||||
|
var ok bool
|
||||||
|
ok, err = regexp.Match("^/[0-9]+$", []byte(fld.Value))
|
||||||
|
if err == nil {
|
||||||
|
if ok {
|
||||||
|
return nil // found it!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
ip := net.ParseIP(fld.Value)
|
||||||
|
if ip == nil {
|
||||||
|
err = fmt.Errorf("value of field \"%s\" is not a valid IP address", fld.Caption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
/* validateCountryField validates a country code field.
|
/* validateCountryField validates a country code field.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* fld - The field to be validated.
|
* fld - The field to be validated.
|
||||||
@@ -613,6 +648,7 @@ var validators = map[string]validatorFunc{
|
|||||||
"header": nilValidator,
|
"header": nilValidator,
|
||||||
"hidden": nilValidator,
|
"hidden": nilValidator,
|
||||||
"integer": validateIntegerField,
|
"integer": validateIntegerField,
|
||||||
|
"ipaddress": validateIPAddressField,
|
||||||
"localelist": nilValidator,
|
"localelist": nilValidator,
|
||||||
"password": validateTextField,
|
"password": validateTextField,
|
||||||
"rolelist": validateRoleListField,
|
"rolelist": validateRoleListField,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#
|
#
|
||||||
# Amsterdam Web Communities System
|
# Amsterdam Web Communities System
|
||||||
# Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
# Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||||
#
|
#
|
||||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#
|
||||||
|
# Amsterdam Web Communities System
|
||||||
|
# Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||||
|
#
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
#
|
||||||
|
name: "ipban.add"
|
||||||
|
formName: "ipbanform"
|
||||||
|
menuSelector: "nochange"
|
||||||
|
title: "Add IP Address Ban"
|
||||||
|
action: "/sysadmin/ipban/add"
|
||||||
|
fields:
|
||||||
|
- type: "ipaddress"
|
||||||
|
name: "address"
|
||||||
|
caption: "IP address"
|
||||||
|
required: true
|
||||||
|
- type: "ipaddress"
|
||||||
|
name: "mask"
|
||||||
|
caption: "IP address mask"
|
||||||
|
required: true
|
||||||
|
param: "mask"
|
||||||
|
- type: "checkbox"
|
||||||
|
name: "echeck"
|
||||||
|
caption: "IP address ban expires"
|
||||||
|
- type: "integer"
|
||||||
|
name: "etime"
|
||||||
|
caption: "Expires in"
|
||||||
|
param: "1-100000"
|
||||||
|
- type: "dropdown"
|
||||||
|
name: "eunit"
|
||||||
|
caption: "Expires in"
|
||||||
|
subcaption: "(units)"
|
||||||
|
required: true
|
||||||
|
choices:
|
||||||
|
- id: "D"
|
||||||
|
text: "days"
|
||||||
|
- id: "W"
|
||||||
|
text: "weeks"
|
||||||
|
- id: "M"
|
||||||
|
text: "months"
|
||||||
|
- id: "Y"
|
||||||
|
text: "years"
|
||||||
|
- type: "text"
|
||||||
|
name: "msg"
|
||||||
|
caption: "Message to display"
|
||||||
|
size: 64
|
||||||
|
maxlength: 255
|
||||||
|
- type: "button"
|
||||||
|
name: "add"
|
||||||
|
caption: "Add"
|
||||||
|
param: "blue"
|
||||||
|
- type: "button"
|
||||||
|
name: "cancel"
|
||||||
|
caption: "Cancel"
|
||||||
|
param: "red"
|
||||||
+1
-1
@@ -37,7 +37,7 @@ menudefs:
|
|||||||
link: "/sysadmin/globals"
|
link: "/sysadmin/globals"
|
||||||
permission: "Global.SysAdminAccess"
|
permission: "Global.SysAdminAccess"
|
||||||
- text: "View/Edit IP Address Bans"
|
- text: "View/Edit IP Address Bans"
|
||||||
link: "/TODO/sysadmin/ip_bans"
|
link: "/sysadmin/ipban"
|
||||||
permission: "Global.SysAdminAccess"
|
permission: "Global.SysAdminAccess"
|
||||||
- text: "View/Edit Banned Users"
|
- text: "View/Edit Banned Users"
|
||||||
link: "/TODO/sysadmin/user_bans"
|
link: "/TODO/sysadmin/user_bans"
|
||||||
|
|||||||
+1
-1
@@ -61,7 +61,7 @@
|
|||||||
<div class="bg-gray-50 p-6 rounded-lg">
|
<div class="bg-gray-50 p-6 rounded-lg">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
{{ range __dialog.Fields }}
|
{{ range __dialog.Fields }}
|
||||||
{{ if .Type == "text" || .Type == "ams_id" || .Type == "email" }}
|
{{ if .Type == "text" || .Type == "ams_id" || .Type == "email" || .Type == "ipaddress" }}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<label for="{{ .Name }}"
|
<label for="{{ .Name }}"
|
||||||
class="w-64 text-right pr-4 {{ if .Disabled }}text-gray-400{{ else }}text-black{{ end }} text-sm">
|
class="w-64 text-right pr-4 {{ if .Disabled }}text-gray-400{{ else }}text-black{{ end }} text-sm">
|
||||||
|
|||||||
@@ -21,6 +21,20 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{ if isset(errorMessage) }}
|
||||||
|
<!-- Error Message Banner -->
|
||||||
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-6" id="error-banner">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<span class="text-red-500 text-xl">⚠️</span>
|
||||||
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
|
<p class="text-sm font-medium" id="error-message">{{ CapitalizeString(errorMessage) }}.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
<!-- Warning Box -->
|
<!-- Warning Box -->
|
||||||
<div class="bg-red-50 border-l-4 border-red-400 p-4 mb-6 rounded max-w-6xl">
|
<div class="bg-red-50 border-l-4 border-red-400 p-4 mb-6 rounded max-w-6xl">
|
||||||
<div class="flex items-start">
|
<div class="flex items-start">
|
||||||
@@ -72,10 +86,10 @@
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-sm font-mono {{ if ipb.Enable }}text-gray-800{{ else }}text-gray-500{{ end }}">
|
<td class="px-4 py-3 text-sm font-mono {{ if ipb.Enable }}text-gray-800{{ else }}text-gray-500{{ end }}">
|
||||||
{{ IPtoString(ipb.AddressLow, ipb.AddressHigh) }}
|
{{ IPtoString(ipb.AddressLow, ipb.AddressHigh, ipv4[i]) }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-sm font-mono {{ if ipb.Enable }}text-gray-600{{ else }}text-gray-400{{ end }}">
|
<td class="px-4 py-3 text-sm font-mono {{ if ipb.Enable }}text-gray-600{{ else }}text-gray-400{{ end }}">
|
||||||
{{ IPtoString(ipb.MaskLow, ipb.MaskHigh) }}
|
{{ IPtoString(ipb.MaskLow, ipb.MaskHigh, ipv4[i]) }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-sm {{ if ipb.Enable }}text-gray-700{{ else }}text-gray-500{{ end }} whitespace-nowrap">
|
<td class="px-4 py-3 text-sm {{ if ipb.Enable }}text-gray-700{{ else }}text-gray-500{{ end }} whitespace-nowrap">
|
||||||
{{ if isset(ipb.Expires) }}
|
{{ if isset(ipb.Expires) }}
|
||||||
|
|||||||
Reference in New Issue
Block a user