bugfixes and stiffening in the dialog rendering pipeline and the login dialog
This commit is contained in:
+6
-1
@@ -120,7 +120,10 @@ func transmitMessage(m *amMessage, body []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if err = cl.Auth(auth); err == nil {
|
if auth != nil {
|
||||||
|
err = cl.Auth(auth)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
if err = cl.Mail(m.fromAddr); err == nil {
|
if err = cl.Mail(m.fromAddr); err == nil {
|
||||||
for _, addr := range m.toAddrs {
|
for _, addr := range m.toAddrs {
|
||||||
if err = cl.Rcpt(addr); err != nil {
|
if err = cl.Rcpt(addr); err != nil {
|
||||||
@@ -187,6 +190,8 @@ func SetupMailSender() func() {
|
|||||||
// Initialize mail host and authentication.
|
// Initialize mail host and authentication.
|
||||||
mailHost = fmt.Sprintf("%s:%d", config.GlobalConfig.Email.Host, config.GlobalConfig.Email.Port)
|
mailHost = fmt.Sprintf("%s:%d", config.GlobalConfig.Email.Host, config.GlobalConfig.Email.Port)
|
||||||
switch config.GlobalConfig.Email.AuthType {
|
switch config.GlobalConfig.Email.AuthType {
|
||||||
|
case "none":
|
||||||
|
auth = nil
|
||||||
case "plain":
|
case "plain":
|
||||||
auth = smtp.PlainAuth("", config.GlobalConfig.Email.User, config.GlobalConfig.Email.Password,
|
auth = smtp.PlainAuth("", config.GlobalConfig.Email.User, config.GlobalConfig.Email.Password,
|
||||||
config.GlobalConfig.Email.Host)
|
config.GlobalConfig.Email.Host)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
"git.erbosoft.com/amy/amsterdam/email"
|
"git.erbosoft.com/amy/amsterdam/email"
|
||||||
"git.erbosoft.com/amy/amsterdam/ui"
|
"git.erbosoft.com/amy/amsterdam/ui"
|
||||||
"git.erbosoft.com/amy/amsterdam/util"
|
"git.erbosoft.com/amy/amsterdam/util"
|
||||||
"github.com/labstack/gommon/log"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
/* LoginForm renders the Amsterdam login form.
|
/* LoginForm renders the Amsterdam login form.
|
||||||
@@ -75,8 +75,12 @@ func Login(ctxt ui.AmContext) (string, any, error) {
|
|||||||
if action == "cancel" { // Cancel button pressed
|
if action == "cancel" { // Cancel button pressed
|
||||||
return "redirect", target, nil
|
return "redirect", target, nil
|
||||||
}
|
}
|
||||||
|
username := dlg.Field("user").Value // since the dialog won't check this for us
|
||||||
|
if len(username) == 0 {
|
||||||
|
return dlg.RenderError(ctxt, "User name not specified.")
|
||||||
|
}
|
||||||
if action == "remind" { // Password Reminder button pressed
|
if action == "remind" { // Password Reminder button pressed
|
||||||
user, uerr := database.AmGetUserByName(dlg.Field("user").Value)
|
user, uerr := database.AmGetUserByName(username)
|
||||||
if uerr == nil {
|
if uerr == nil {
|
||||||
var ci *database.ContactInfo
|
var ci *database.ContactInfo
|
||||||
ci, uerr = user.ContactInfo()
|
ci, uerr = user.ContactInfo()
|
||||||
@@ -97,7 +101,6 @@ func Login(ctxt ui.AmContext) (string, any, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dlg.Field("pass").Value = ""
|
|
||||||
if uerr == nil {
|
if uerr == nil {
|
||||||
return dlg.RenderInfo(ctxt, "Password reminder has been sent to your E-mail address.")
|
return dlg.RenderInfo(ctxt, "Password reminder has been sent to your E-mail address.")
|
||||||
} else {
|
} else {
|
||||||
@@ -106,9 +109,8 @@ func Login(ctxt ui.AmContext) (string, any, error) {
|
|||||||
}
|
}
|
||||||
if action == "login" { // Login button pressed
|
if action == "login" { // Login button pressed
|
||||||
// authenticate the user
|
// authenticate the user
|
||||||
user, uerr := database.AmAuthenticateUser(dlg.Field("user").Value, dlg.Field("pass").Value, ctxt.RemoteIP())
|
user, uerr := database.AmAuthenticateUser(username, dlg.Field("pass").Value, ctxt.RemoteIP())
|
||||||
if uerr != nil {
|
if uerr != nil {
|
||||||
dlg.Field("pass").Value = ""
|
|
||||||
return dlg.RenderError(ctxt, uerr.Error())
|
return dlg.RenderError(ctxt, uerr.Error())
|
||||||
}
|
}
|
||||||
ctxt.ReplaceUser(user)
|
ctxt.ReplaceUser(user)
|
||||||
@@ -128,7 +130,6 @@ func Login(ctxt ui.AmContext) (string, any, error) {
|
|||||||
return "redirect", "/verify?tgt=" + url.PathEscape(target), nil
|
return "redirect", "/verify?tgt=" + url.PathEscape(target), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dlg.Field("pass").Value = ""
|
|
||||||
return dlg.RenderError(ctxt, "No known button click on POST to login function.")
|
return dlg.RenderError(ctxt, "No known button click on POST to login function.")
|
||||||
}
|
}
|
||||||
return ui.ErrorPage(ctxt, err)
|
return ui.ErrorPage(ctxt, err)
|
||||||
@@ -190,6 +191,7 @@ func VerifyEmailForm(ctxt ui.AmContext) (string, any, error) {
|
|||||||
return ui.ErrorPage(ctxt, err)
|
return ui.ErrorPage(ctxt, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sendEmailConfirmationEmail sends the "E-mail confirmation number" E-mail message.
|
||||||
func sendEmailConfirmationEmail(user *database.User, ci *database.ContactInfo, remoteIP string) error {
|
func sendEmailConfirmationEmail(user *database.User, ci *database.ContactInfo, remoteIP string) error {
|
||||||
if ci != nil && ci.Email != nil && *ci.Email != "" {
|
if ci != nil && ci.Email != nil && *ci.Email != "" {
|
||||||
msg := email.AmNewEmailMessage(user.Uid, remoteIP)
|
msg := email.AmNewEmailMessage(user.Uid, remoteIP)
|
||||||
@@ -397,6 +399,14 @@ func NewAccount(ctxt ui.AmContext) (string, any, error) {
|
|||||||
return ui.ErrorPage(ctxt, err)
|
return ui.ErrorPage(ctxt, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* PasswordRecovery handles a click on a "password recovery" link to fix the user's password.
|
||||||
|
* Parameters:
|
||||||
|
* ctxt - The AmContext for the request.
|
||||||
|
* Returns:
|
||||||
|
* Command string dictating what to be rendered.
|
||||||
|
* Data as a parameter for the command string.
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
func PasswordRecovery(ctxt ui.AmContext) (string, any, error) {
|
func PasswordRecovery(ctxt ui.AmContext) (string, any, error) {
|
||||||
var emailaddy string
|
var emailaddy string
|
||||||
uid, err := ctxt.URLParamInt("uid")
|
uid, err := ctxt.URLParamInt("uid")
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ func (c *amContext) Scratchpad() map[string]any {
|
|||||||
func (c *amContext) SubRender(name string) ([]byte, error) {
|
func (c *amContext) SubRender(name string) ([]byte, error) {
|
||||||
view, err := views.GetTemplate(name)
|
view, err := views.GetTemplate(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Errorf("unable to load template \"%s\": %v", name, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
|
|||||||
+20
-12
@@ -47,6 +47,12 @@ type Dialog struct {
|
|||||||
Fields []DialogItem `yaml:"fields"`
|
Fields []DialogItem `yaml:"fields"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VRange is used as a return type for ValueRange.
|
||||||
|
type VRange struct {
|
||||||
|
Low int
|
||||||
|
High int
|
||||||
|
}
|
||||||
|
|
||||||
//go:embed dialogs/*
|
//go:embed dialogs/*
|
||||||
var dialogs embed.FS
|
var dialogs embed.FS
|
||||||
|
|
||||||
@@ -99,14 +105,14 @@ func (fld *DialogItem) IsChecked() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValueRange returns the minimum and maximum values for an integer field.
|
// ValueRange returns the minimum and maximum values for an integer field.
|
||||||
func (fld *DialogItem) ValueRange() (int, int) {
|
func (fld *DialogItem) ValueRange() VRange {
|
||||||
if fld.Type == "integer" && fld.Param != "" {
|
if fld.Type == "integer" && fld.Param != "" {
|
||||||
parms := strings.Split(fld.Param, "-")
|
parms := strings.Split(fld.Param, "-")
|
||||||
low, _ := strconv.Atoi(parms[0])
|
low, _ := strconv.Atoi(parms[0])
|
||||||
high, _ := strconv.Atoi(parms[1])
|
high, _ := strconv.Atoi(parms[1])
|
||||||
return low, high
|
return VRange{Low: low, High: high}
|
||||||
}
|
}
|
||||||
return -1, -1
|
return VRange{Low: -1, High: -1}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AsDate returns the value of a date field as a Go date.
|
// AsDate returns the value of a date field as a Go date.
|
||||||
@@ -152,10 +158,12 @@ func (d *Dialog) Field(name string) *DialogItem {
|
|||||||
*/
|
*/
|
||||||
func (d *Dialog) Render(ctxt AmContext) (string, any, error) {
|
func (d *Dialog) Render(ctxt AmContext) (string, any, error) {
|
||||||
required := false
|
required := false
|
||||||
for _, fld := range d.Fields {
|
for i, fld := range d.Fields {
|
||||||
if fld.Required {
|
if fld.Required {
|
||||||
required = true
|
required = true // display the "required" blurb
|
||||||
break
|
}
|
||||||
|
if fld.Type == "password" { // clear all "password" fields as a security measure
|
||||||
|
d.Fields[i].Value = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctxt.VarMap().Set("amsterdam_required", required)
|
ctxt.VarMap().Set("amsterdam_required", required)
|
||||||
@@ -304,12 +312,12 @@ func validateIntegerField(fld *DialogItem) error {
|
|||||||
v, err = strconv.Atoi(fld.Value)
|
v, err = strconv.Atoi(fld.Value)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fld.AuxData = v // cache parsed value
|
fld.AuxData = v // cache parsed value
|
||||||
lo, hi := fld.ValueRange()
|
vr := fld.ValueRange()
|
||||||
if lo != -1 && hi != -1 {
|
if vr.Low != -1 && vr.High != -1 {
|
||||||
if v < lo {
|
if v < vr.Low {
|
||||||
return fmt.Errorf("value of field \"%s\" cannot be less than %d", fld.Caption, lo)
|
return fmt.Errorf("value of field \"%s\" cannot be less than %d", fld.Caption, vr.Low)
|
||||||
} else if v > hi {
|
} else if v > vr.High {
|
||||||
return fmt.Errorf("value of field \"%s\" cannot be greater than %d", fld.Caption, hi)
|
return fmt.Errorf("value of field \"%s\" cannot be greater than %d", fld.Caption, vr.High)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+4
-1
@@ -18,7 +18,7 @@ import (
|
|||||||
"git.erbosoft.com/amy/amsterdam/config"
|
"git.erbosoft.com/amy/amsterdam/config"
|
||||||
"git.erbosoft.com/amy/amsterdam/database"
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/labstack/gommon/log"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sendPageData(ctxt echo.Context, amctxt AmContext, command string, data any) error {
|
func sendPageData(ctxt echo.Context, amctxt AmContext, command string, data any) error {
|
||||||
@@ -39,6 +39,9 @@ func sendPageData(ctxt echo.Context, amctxt AmContext, command string, data any)
|
|||||||
default:
|
default:
|
||||||
err = fmt.Errorf("unknown rendering type: %s", command)
|
err = fmt.Errorf("unknown rendering type: %s", command)
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("sendPageData() barfed with %v", err)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -29,6 +29,7 @@ import (
|
|||||||
"github.com/CloudyKit/jet/v6/loaders/multi"
|
"github.com/CloudyKit/jet/v6/loaders/multi"
|
||||||
"github.com/biter777/countries"
|
"github.com/biter777/countries"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed views/*
|
//go:embed views/*
|
||||||
@@ -130,7 +131,7 @@ func makeYearRange(a jet.Arguments) reflect.Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CapitalizeString changes the first character of trhe string to a capital.
|
/* CapitalizeString changes the first character of the string to a capital.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* s - The string to be capitalized.
|
* s - The string to be capitalized.
|
||||||
* Returns:
|
* Returns:
|
||||||
@@ -187,6 +188,7 @@ func (r *TemplateRenderer) Render(w io.Writer, name string, data any, c echo.Con
|
|||||||
view, err := views.GetTemplate(name)
|
view, err := views.GetTemplate(name)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Errorf("Unable to load template \"%s\": %v", name, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var vmap jet.VarMap = nil
|
var vmap jet.VarMap = nil
|
||||||
|
|||||||
+5
-3
@@ -6,6 +6,7 @@
|
|||||||
* 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
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*}
|
*}
|
||||||
|
<!-- BEGIN DIALOG {{ amsterdam_dialog.Name }} -->
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h1 class="text-blue-800 text-4xl font-bold mb-2">{{ amsterdam_dialog.Title }}</h1>
|
<h1 class="text-blue-800 text-4xl font-bold mb-2">{{ amsterdam_dialog.Title }}</h1>
|
||||||
@@ -78,9 +79,9 @@
|
|||||||
{{ if .MaxLength > 0 }}maxlength="{{ .MaxLength }}"{{ end }}
|
{{ if .MaxLength > 0 }}maxlength="{{ .MaxLength }}"{{ end }}
|
||||||
value="{{ .Value }}"
|
value="{{ .Value }}"
|
||||||
class="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
|
class="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
|
||||||
{{ lo, hi := .ValueRange() }}
|
{{ vr := .ValueRange() }}
|
||||||
{{ if lo != -1 && hi != -1 }}
|
{{ if vr.Low != -1 && vr.High != -1 }}
|
||||||
<span class="text-sm">({{ lo }}-{{ hi }})</span>
|
<span class="text-sm">({{ vr.Low }}-{{ vr.High }})</span>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ else if .Type == "password" }}
|
{{ else if .Type == "password" }}
|
||||||
@@ -170,3 +171,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- END DIALOG {{ amsterdam_dialog.Name }} -->
|
||||||
|
|||||||
Reference in New Issue
Block a user