dialog loading, validation, more logic in forms, IP address banning
This commit is contained in:
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Amsterdam Web Communities System
|
||||||
|
* Copyright (c) 2025 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/.
|
||||||
|
*/
|
||||||
|
// The database package contains database management and storage logic.
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// low64mask is bigint 0xFFFFFFFFFFFFFFFF, used in splitting large addresses.
|
||||||
|
var low64mask *big.Int
|
||||||
|
|
||||||
|
// knownBans is a cache of known banned addresses.
|
||||||
|
var knownBans map[string]string
|
||||||
|
|
||||||
|
// knownGood is a cache of known good IP addresses.
|
||||||
|
var knownGood map[string]bool
|
||||||
|
|
||||||
|
// banMutex synchronizes access to our cache.
|
||||||
|
var banMutex sync.Mutex
|
||||||
|
|
||||||
|
// init initializes the internals in this file.
|
||||||
|
func init() {
|
||||||
|
a := big.NewInt(1)
|
||||||
|
b := big.NewInt(0).Lsh(a, 64)
|
||||||
|
low64mask = big.NewInt(0).Sub(b, big.NewInt(1))
|
||||||
|
knownBans = make(map[string]string)
|
||||||
|
knownGood = make(map[string]bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AmTestIPBan tests an IP address to see if it's on the banned list.
|
||||||
|
* Parameters:
|
||||||
|
* ip_address - The IP address to be tested.
|
||||||
|
* Returns:
|
||||||
|
* Ban message if the address is banned, or empty string if it isn't.
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func AmTestIPBan(ip_address string) (string, error) {
|
||||||
|
banMutex.Lock()
|
||||||
|
defer banMutex.Unlock()
|
||||||
|
rc := knownBans[ip_address]
|
||||||
|
if rc != "" {
|
||||||
|
return rc, nil
|
||||||
|
}
|
||||||
|
if knownGood[ip_address] {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
addr := net.ParseIP(ip_address)
|
||||||
|
if addr == nil {
|
||||||
|
return "", fmt.Errorf("invalid address %s", ip_address)
|
||||||
|
}
|
||||||
|
iv := big.NewInt(0)
|
||||||
|
iv.SetBytes(addr)
|
||||||
|
iv_lo := big.NewInt(0).And(iv, low64mask).Uint64()
|
||||||
|
iv_hi := big.NewInt(0).Rsh(iv, 64).Uint64()
|
||||||
|
rows, err := amdb.Query(`
|
||||||
|
SELECT message FROM ipban WHERE (address_lo & mask_lo) = (? & mask_lo)
|
||||||
|
AND (address_hi & mask_hi) = (? & mask_hi) AND (expire IS NULL OR expire >= ?)
|
||||||
|
AND enable <> 0 ORDER BY mask_hi DESC, mask_lo DESC`, iv_lo, iv_hi, time.Now())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
if rows.Next() {
|
||||||
|
rows.Scan(&rc)
|
||||||
|
knownBans[ip_address] = rc
|
||||||
|
return rc, nil
|
||||||
|
}
|
||||||
|
knownGood[ip_address] = true
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Amsterdam Web Communities System
|
||||||
|
* Copyright (c) 2025 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/.
|
||||||
|
*/
|
||||||
|
// The database package contains database management and storage logic.
|
||||||
|
package database
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
const AMS_ID_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~*'$"
|
||||||
|
|
||||||
|
// AmIsValidAmsterdamID returns true if the given string is a valid Amsterdam ID.
|
||||||
|
func AmIsValidAmsterdamID(test string) bool {
|
||||||
|
if len(test) < 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, r := range test {
|
||||||
|
if !strings.ContainsRune(AMS_ID_CHARS, r) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -9,7 +9,11 @@
|
|||||||
// Package main contains the high-level Amsterdam logic.
|
// Package main contains the high-level Amsterdam logic.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "git.erbosoft.com/amy/amsterdam/ui"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.erbosoft.com/amy/amsterdam/ui"
|
||||||
|
)
|
||||||
|
|
||||||
/* LoginForm renders the Amsterdam login form.
|
/* LoginForm renders the Amsterdam login form.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
@@ -20,8 +24,20 @@ import "git.erbosoft.com/amy/amsterdam/ui"
|
|||||||
* Standard Go error status.
|
* Standard Go error status.
|
||||||
*/
|
*/
|
||||||
func LoginForm(ctxt ui.AmContext) (string, any, error) {
|
func LoginForm(ctxt ui.AmContext) (string, any, error) {
|
||||||
|
// Get target URI.
|
||||||
|
target := ctxt.Parameter("tgt")
|
||||||
|
if target == "" {
|
||||||
|
target = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user is already logged in, this is a no-op.
|
||||||
|
if !ctxt.CurrentUser().IsAnon {
|
||||||
|
return "redirect", target, nil
|
||||||
|
}
|
||||||
|
|
||||||
dlg, err := ui.AmLoadDialog("login")
|
dlg, err := ui.AmLoadDialog("login")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
dlg.Field("tgt").Value = target
|
||||||
ctxt.VarMap().Set("amsterdam_pageTitle", "Log In")
|
ctxt.VarMap().Set("amsterdam_pageTitle", "Log In")
|
||||||
return dlg.Render(ctxt)
|
return dlg.Render(ctxt)
|
||||||
}
|
}
|
||||||
@@ -37,6 +53,18 @@ func LoginForm(ctxt ui.AmContext) (string, any, error) {
|
|||||||
* Standard Go error status.
|
* Standard Go error status.
|
||||||
*/
|
*/
|
||||||
func NewAccountUserAgreement(ctxt ui.AmContext) (string, any, error) {
|
func NewAccountUserAgreement(ctxt ui.AmContext) (string, any, error) {
|
||||||
|
// Get target URI.
|
||||||
|
target := ctxt.Parameter("tgt")
|
||||||
|
if target == "" {
|
||||||
|
target = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user is already logged in, this is an error.
|
||||||
|
if !ctxt.CurrentUser().IsAnon {
|
||||||
|
return ui.ErrorPage(ctxt, fmt.Errorf("You cannot create a bew account while logged in on an existing one. You must log out first."))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxt.VarMap().Set("target", target)
|
||||||
ctxt.VarMap().Set("amsterdam_pageTitle", "New Account User Agreement")
|
ctxt.VarMap().Set("amsterdam_pageTitle", "New Account User Agreement")
|
||||||
return "framed_template", "agreement.jet", nil
|
return "framed_template", "agreement.jet", nil
|
||||||
}
|
}
|
||||||
@@ -50,8 +78,20 @@ func NewAccountUserAgreement(ctxt ui.AmContext) (string, any, error) {
|
|||||||
* Standard Go error status.
|
* Standard Go error status.
|
||||||
*/
|
*/
|
||||||
func NewAccountForm(ctxt ui.AmContext) (string, any, error) {
|
func NewAccountForm(ctxt ui.AmContext) (string, any, error) {
|
||||||
|
// Get target URI.
|
||||||
|
target := ctxt.Parameter("tgt")
|
||||||
|
if target == "" {
|
||||||
|
target = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user is already logged in, this is an error.
|
||||||
|
if !ctxt.CurrentUser().IsAnon {
|
||||||
|
return ui.ErrorPage(ctxt, fmt.Errorf("You cannot create a bew account while logged in on an existing one. You must log out first."))
|
||||||
|
}
|
||||||
|
|
||||||
dlg, err := ui.AmLoadDialog("newaccount")
|
dlg, err := ui.AmLoadDialog("newaccount")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
dlg.Field("tgt").Value = target
|
||||||
dlg.Field("country").Value = "XX"
|
dlg.Field("country").Value = "XX"
|
||||||
ctxt.VarMap().Set("amsterdam_pageTitle", "Create New Account")
|
ctxt.VarMap().Set("amsterdam_pageTitle", "Create New Account")
|
||||||
return dlg.Render(ctxt)
|
return dlg.Render(ctxt)
|
||||||
|
|||||||
+5
-3
@@ -425,14 +425,16 @@ CREATE TABLE imagestore (
|
|||||||
# Table listing IP addresses that are banned from logging in or registering.
|
# Table listing IP addresses that are banned from logging in or registering.
|
||||||
CREATE TABLE ipban (
|
CREATE TABLE ipban (
|
||||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||||
address BIGINT NOT NULL,
|
address_lo BIGINT UNSIGNED NOT NULL,
|
||||||
mask BIGINT NOT NULL,
|
address_hi BIGINT UNSIGNED NOT NULL,
|
||||||
|
mask_lo BIGINT UNSIGNED NOT NULL,
|
||||||
|
mask_hi BIGINT UNSIGNED NOT NULL,
|
||||||
enable TINYINT NOT NULL DEFAULT 1,
|
enable TINYINT NOT NULL DEFAULT 1,
|
||||||
expire DATETIME,
|
expire DATETIME,
|
||||||
message VARCHAR(255) NOT NULL,
|
message VARCHAR(255) NOT NULL,
|
||||||
block_by INT NOT NULL,
|
block_by INT NOT NULL,
|
||||||
block_on DATETIME NOT NULL,
|
block_on DATETIME NOT NULL,
|
||||||
INDEX by_mask (mask),
|
INDEX by_mask (mask_hi, mask_lo),
|
||||||
INDEX by_date (block_on)
|
INDEX by_date (block_on)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ package ui
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/database"
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
"github.com/CloudyKit/jet/v6"
|
"github.com/CloudyKit/jet/v6"
|
||||||
@@ -25,8 +26,11 @@ import (
|
|||||||
// AmContext is the interface for Amsterdam's wrapper context that exposes the required functionality.
|
// AmContext is the interface for Amsterdam's wrapper context that exposes the required functionality.
|
||||||
type AmContext interface {
|
type AmContext interface {
|
||||||
CurrentUser() *database.User
|
CurrentUser() *database.User
|
||||||
|
FormField(string) string
|
||||||
|
FormFieldInt(string) (int, error)
|
||||||
RC() int
|
RC() int
|
||||||
OutputType() string
|
OutputType() string
|
||||||
|
Parameter(string) string
|
||||||
Render(string) error
|
Render(string) error
|
||||||
SubRender(string) ([]byte, error)
|
SubRender(string) ([]byte, error)
|
||||||
Session() *sessions.Session
|
Session() *sessions.Session
|
||||||
@@ -57,6 +61,27 @@ func (c *amContext) CurrentUser() *database.User {
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* FormField returns the value of a form field from the request.
|
||||||
|
* Parameters:
|
||||||
|
* name - The name of the field to retrieve.
|
||||||
|
* Returns:
|
||||||
|
* The value given to that named field.
|
||||||
|
*/
|
||||||
|
func (c *amContext) FormField(name string) string {
|
||||||
|
return c.echoContext.FormValue(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FormField returns the value of a form field from the request, as an integer.
|
||||||
|
* Parameters:
|
||||||
|
* name - The name of the field to retrieve.
|
||||||
|
* Returns:
|
||||||
|
* The value given to that named field.
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func (c *amContext) FormFieldInt(name string) (int, error) {
|
||||||
|
return strconv.Atoi(c.echoContext.FormValue(name))
|
||||||
|
}
|
||||||
|
|
||||||
// RC returns the HTTP result code for the current operation.
|
// RC returns the HTTP result code for the current operation.
|
||||||
func (c *amContext) RC() int {
|
func (c *amContext) RC() int {
|
||||||
return c.httprc
|
return c.httprc
|
||||||
@@ -67,6 +92,20 @@ func (c *amContext) OutputType() string {
|
|||||||
return c.outputType
|
return c.outputType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Parameter returns the value of a parameter (query parameter or form field) from the request.
|
||||||
|
* Parameters:
|
||||||
|
* name - The name of the field to retrieve.
|
||||||
|
* Returns:
|
||||||
|
* The value given to that named field.
|
||||||
|
*/
|
||||||
|
func (c *amContext) Parameter(name string) string {
|
||||||
|
rc := c.echoContext.QueryParam(name)
|
||||||
|
if rc == "" && c.echoContext.Request().Method == "POST" {
|
||||||
|
rc = c.echoContext.FormValue(name)
|
||||||
|
}
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
/* Render renders a template to the output. Called at the top level only.
|
/* Render renders a template to the output. Called at the top level only.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* name = The name of the tempate to be rendered.
|
* name = The name of the tempate to be rendered.
|
||||||
|
|||||||
+191
@@ -13,7 +13,9 @@ package ui
|
|||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/mail"
|
||||||
|
|
||||||
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ type DialogItem struct {
|
|||||||
MaxLength int `yaml:"maxlength,omitempty"`
|
MaxLength int `yaml:"maxlength,omitempty"`
|
||||||
Value string `yaml:"value,omitempty"`
|
Value string `yaml:"value,omitempty"`
|
||||||
Param string `yaml:"param,omitempty"`
|
Param string `yaml:"param,omitempty"`
|
||||||
|
AuxData any
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dialog holds the dialog definition.
|
// Dialog holds the dialog definition.
|
||||||
@@ -72,6 +75,18 @@ func AmLoadDialog(name string) (*Dialog, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DateValues returns the date values stored in a date field.
|
||||||
|
func (fld *DialogItem) DateValues() []int {
|
||||||
|
if fld.Type == "date" && fld.AuxData != nil {
|
||||||
|
return fld.AuxData.([]int)
|
||||||
|
}
|
||||||
|
rc := make([]int, 3)
|
||||||
|
rc[0] = -1
|
||||||
|
rc[1] = -1
|
||||||
|
rc[2] = -1
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
/* Field returns a pointer to a dialog's field, given its name.
|
/* Field returns a pointer to a dialog's field, given its name.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* name - The name of the field to find.
|
* name - The name of the field to find.
|
||||||
@@ -107,3 +122,179 @@ func (d *Dialog) Render(ctxt AmContext) (string, any, error) {
|
|||||||
ctxt.VarMap().Set("amsterdam_dialog", d)
|
ctxt.VarMap().Set("amsterdam_dialog", d)
|
||||||
return "framed_template", "dialog.jet", nil
|
return "framed_template", "dialog.jet", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LoadFromForm loads the values in a dialog from the form fields in the request.
|
||||||
|
* Parameters:
|
||||||
|
* ctxt - The AmContext for this request.
|
||||||
|
*/
|
||||||
|
func (d *Dialog) LoadFromForm(ctxt AmContext) {
|
||||||
|
for _, fld := range d.Fields {
|
||||||
|
if fld.Type == "date" {
|
||||||
|
fld.Value = ""
|
||||||
|
dvals := make([]int, 3)
|
||||||
|
var err error
|
||||||
|
dvals[0], err = ctxt.FormFieldInt(fmt.Sprintf("%s_month", fld.Name))
|
||||||
|
if err != nil {
|
||||||
|
dvals[0] = -1
|
||||||
|
fld.Value = fmt.Sprintf("!undefined month %s: %v", fld.Name, err)
|
||||||
|
}
|
||||||
|
dvals[1], err = ctxt.FormFieldInt(fmt.Sprintf("%s_day", fld.Name))
|
||||||
|
if err != nil {
|
||||||
|
dvals[1] = -1
|
||||||
|
if fld.Value == "" {
|
||||||
|
fld.Value = fmt.Sprintf("!undefined day %s: %v", fld.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dvals[2], err = ctxt.FormFieldInt(fmt.Sprintf("%s_year", fld.Name))
|
||||||
|
if err != nil {
|
||||||
|
dvals[2] = -1
|
||||||
|
if fld.Value == "" {
|
||||||
|
fld.Value = fmt.Sprintf("!undefined year %s: %v", fld.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dvals[0] > 0 && dvals[1] > 0 && dvals[2] > 0 {
|
||||||
|
fld.Value = fmt.Sprintf("%04d%02d%02d", dvals[2], dvals[0], dvals[1])
|
||||||
|
} else if fld.Value == "" && fld.Required {
|
||||||
|
if dvals[0] <= 0 {
|
||||||
|
fld.Value = fmt.Sprintf("!month not set %s", fld.Name)
|
||||||
|
} else if dvals[1] <= 0 {
|
||||||
|
fld.Value = fmt.Sprintf("!day not set %s", fld.Name)
|
||||||
|
} else if dvals[2] <= 0 {
|
||||||
|
fld.Value = fmt.Sprintf("!year not set %s", fld.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fld.AuxData = dvals
|
||||||
|
} else {
|
||||||
|
fld.Value = ctxt.FormField(fld.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validatorFunc is a function that validates the contents of a dialog item.
|
||||||
|
type validatorFunc func(*DialogItem) error
|
||||||
|
|
||||||
|
// nilValidator is a validator function that doesn't do anything.
|
||||||
|
func nilValidator(*DialogItem) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* validateTextField validates a text field.
|
||||||
|
* Parameters:
|
||||||
|
* fld - The field to be validated.
|
||||||
|
* Returns:
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func validateTextField(fld *DialogItem) error {
|
||||||
|
if len(fld.Value) == 0 && fld.Required {
|
||||||
|
return fmt.Errorf("value of field \"%s\" is required", fld.Caption)
|
||||||
|
}
|
||||||
|
if len(fld.Value) > fld.MaxLength {
|
||||||
|
return fmt.Errorf("value of field \"%s\" can be no longer than %d characters", fld.Caption, fld.MaxLength)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* validateAmsIdField validates an Amsterdam ID field.
|
||||||
|
* Parameters:
|
||||||
|
* fld - The field to be validated.
|
||||||
|
* Returns:
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func validateAmsIdField(fld *DialogItem) error {
|
||||||
|
err := validateTextField(fld)
|
||||||
|
if err == nil {
|
||||||
|
if !database.AmIsValidAmsterdamID(fld.Value) {
|
||||||
|
err = fmt.Errorf("value of field \"%s\" is not a valid identifier", fld.Caption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
/* validateEmailField validates an E-mail address field.
|
||||||
|
* Parameters:
|
||||||
|
* fld - The field to be validated.
|
||||||
|
* Returns:
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func validateEmailField(fld *DialogItem) error {
|
||||||
|
err := validateTextField(fld)
|
||||||
|
if err == nil {
|
||||||
|
_, err = mail.ParseAddress(fld.Value)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
/* validateCountryField validates a country code field.
|
||||||
|
* Parameters:
|
||||||
|
* fld - The field to be validated.
|
||||||
|
* Returns:
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func validateCountryField(fld *DialogItem) error {
|
||||||
|
if fld.Value == "XX" && fld.Required {
|
||||||
|
return fmt.Errorf("country field \"%s\" not set", fld.Caption)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* validateDateField validates a date field.
|
||||||
|
* Parameters:
|
||||||
|
* fld - The field to be validated.
|
||||||
|
* Returns:
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func validateDateField(fld *DialogItem) error {
|
||||||
|
if len(fld.Value) == 0 && fld.Required {
|
||||||
|
return fmt.Errorf("date value %s not set", fld.Caption)
|
||||||
|
}
|
||||||
|
if fld.Value[0] == '!' {
|
||||||
|
return fmt.Errorf("date value %s erroneous: %s", fld.Caption, fld.Value[1:])
|
||||||
|
}
|
||||||
|
if fld.AuxData == nil {
|
||||||
|
return fmt.Errorf("date value %s not set properly", fld.Caption)
|
||||||
|
}
|
||||||
|
dv := fld.AuxData.([]int)
|
||||||
|
if dv[0] > 12 || dv[1] > 31 {
|
||||||
|
return fmt.Errorf("date value %s malformed", fld.Caption)
|
||||||
|
}
|
||||||
|
q := fmt.Sprintf("%04d%02d%02d", dv[2], dv[0], dv[1])
|
||||||
|
if q != fld.Value {
|
||||||
|
return fmt.Errorf("date value %s should be %s but is %s", fld.Caption, q, fld.Value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validators maps the field types to validator functions.
|
||||||
|
var validators = map[string]validatorFunc{
|
||||||
|
"ams_id": validateAmsIdField,
|
||||||
|
"button": nilValidator,
|
||||||
|
"checkbox": nilValidator,
|
||||||
|
"countrylist": validateCountryField,
|
||||||
|
"date": validateDateField,
|
||||||
|
"email": validateEmailField,
|
||||||
|
"header": nilValidator,
|
||||||
|
"hidden": nilValidator,
|
||||||
|
"password": validateTextField,
|
||||||
|
"text": validateTextField,
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate validates the values in the dialog.
|
||||||
|
* Returns:
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func (d *Dialog) Validate() error {
|
||||||
|
for _, fld := range d.Fields {
|
||||||
|
if len(fld.Value) > 0 || fld.Required {
|
||||||
|
vfunc := validators[fld.Type]
|
||||||
|
if vfunc != nil {
|
||||||
|
err := vfunc(&fld)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("don't know how to validate field %s of type %s", fld.Name, fld.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ package ui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,6 +23,8 @@ func sendPageData(ctxt echo.Context, amctxt AmContext, command string, data any)
|
|||||||
switch command {
|
switch command {
|
||||||
case "bytes":
|
case "bytes":
|
||||||
err = ctxt.Blob(amctxt.RC(), amctxt.OutputType(), data.([]byte))
|
err = ctxt.Blob(amctxt.RC(), amctxt.OutputType(), data.([]byte))
|
||||||
|
case "redirect":
|
||||||
|
err = ctxt.Redirect(http.StatusFound, data.(string))
|
||||||
case "string":
|
case "string":
|
||||||
err = ctxt.String(amctxt.RC(), fmt.Sprintf("%v", data))
|
err = ctxt.String(amctxt.RC(), fmt.Sprintf("%v", data))
|
||||||
case "template":
|
case "template":
|
||||||
@@ -64,6 +68,15 @@ func AmWrap(myfunc func(AmContext) (string, any, error)) echo.HandlerFunc {
|
|||||||
ctxt.Logger().Errorf("Session creation error: %v", aerr)
|
ctxt.Logger().Errorf("Session creation error: %v", aerr)
|
||||||
return aerr
|
return aerr
|
||||||
}
|
}
|
||||||
|
banmsg, banerr := database.AmTestIPBan(ctxt.RealIP())
|
||||||
|
if banerr != nil {
|
||||||
|
ctxt.Logger().Warnf("address %s could not be tested: %v", ctxt.RealIP(), banerr)
|
||||||
|
// but let the request pass anyway
|
||||||
|
} else if banmsg != "" {
|
||||||
|
amctxt.VarMap().Set("amsterdam_pageTitle", "IP Address Banned")
|
||||||
|
amctxt.VarMap().Set("message", banmsg)
|
||||||
|
return sendPageData(ctxt, amctxt, "framed_template", "ipban.jet")
|
||||||
|
}
|
||||||
what, rc, err := myfunc(amctxt)
|
what, rc, err := myfunc(amctxt)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if err = amctxt.Session().Save(ctxt.Request(), ctxt.Response()); err != nil {
|
if err = amctxt.Session().Save(ctxt.Request(), ctxt.Response()); err != nil {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="flex justify-center gap-4 mt-6">
|
<div class="flex justify-center gap-4 mt-6">
|
||||||
<button type="button" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded font-medium transition-colors"
|
<button type="button" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded font-medium transition-colors"
|
||||||
onclick="window.location.assign('/newacct2')">I Accept</button>
|
onclick="window.location.assign('/newacct2?tgt={{ target | url }}')">I Accept</button>
|
||||||
<button type="button" class="bg-red-600 hover:bg-red-700 text-white px-6 py-2 rounded font-medium transition-colors"
|
<button type="button" class="bg-red-600 hover:bg-red-700 text-white px-6 py-2 rounded font-medium transition-colors"
|
||||||
onclick="window.location.assign('/')">I Decline</button>
|
onclick="window.location.assign('/')">I Decline</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+7
-6
@@ -80,6 +80,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{{ else if .Type == "date" }}
|
{{ else if .Type == "date" }}
|
||||||
|
{{ dv := .DateValues() }}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<label class="w-64 text-right pr-4 text-black text-sm">
|
<label class="w-64 text-right pr-4 text-black text-sm">
|
||||||
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
||||||
@@ -88,21 +89,21 @@
|
|||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<select name="{{ .Name }}_month"
|
<select name="{{ .Name }}_month"
|
||||||
class="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
class="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
<option value="-1" selected>---</option>
|
<option value="-1" {{ if dv[0] == -1 }}selected{{ end }}>---</option>
|
||||||
{{ range i := GetMonthList() }}
|
{{ range i := GetMonthList() }}
|
||||||
<option value="{{ i + 1 }}">{{ . }}</option>
|
<option value="{{ i + 1 }}" {{ if dv[0] == i + 1 }}selected{{ end }}>{{ . }}</option>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</select>
|
</select>
|
||||||
<select name="{{ .Name }}_day" class="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
<select name="{{ .Name }}_day" class="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
<option value="-1" selected>---</option>
|
<option value="-1" {{ if dv[1] == -1 }}selected{{ end }}>---</option>
|
||||||
{{ range MakeIntRange(1, 32, 1) }}
|
{{ range MakeIntRange(1, 32, 1) }}
|
||||||
<option value="{{ . }}">{{ . }}</option>
|
<option value="{{ . }}" {{ if dv[1] == . }}selected{{ end }}>{{ . }}</option>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</select>
|
</select>
|
||||||
<select name="{{ .Name }}_year" class="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
<select name="{{ .Name }}_year" class="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
<option value="-1" selected>---</option>
|
<option value="-1" {{ if dv[2] == -1 }}selected{{ end }}>---</option>
|
||||||
{{ range MakeYearRange(.Param) }}
|
{{ range MakeYearRange(.Param) }}
|
||||||
<option value="{{ . }}">{{ . }}</option>
|
<option value="{{ . }}" {{ if dv[2] == . }}selected{{ end }}>{{ . }}</option>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{*
|
||||||
|
* Amsterdam Web Communities System
|
||||||
|
* Copyright (c) 2025 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/.
|
||||||
|
*}
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-1 p-4">
|
||||||
|
<div class="mb-8">
|
||||||
|
<h1 class="text-blue-800 text-4xl font-bold mb-2">This IP Address Has Been Banned</h1>
|
||||||
|
<hr class="border-2 border-gray-400 w-4/5 mb-4">
|
||||||
|
<p class="text-black text-sm mb-4"><strong>{{ message }}</strong></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user