diff --git a/email/sender.go b/email/sender.go index 846373c..32930f0 100644 --- a/email/sender.go +++ b/email/sender.go @@ -120,7 +120,10 @@ func transmitMessage(m *amMessage, body []byte) { } } 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 { for _, addr := range m.toAddrs { if err = cl.Rcpt(addr); err != nil { @@ -187,6 +190,8 @@ func SetupMailSender() func() { // Initialize mail host and authentication. mailHost = fmt.Sprintf("%s:%d", config.GlobalConfig.Email.Host, config.GlobalConfig.Email.Port) switch config.GlobalConfig.Email.AuthType { + case "none": + auth = nil case "plain": auth = smtp.PlainAuth("", config.GlobalConfig.Email.User, config.GlobalConfig.Email.Password, config.GlobalConfig.Email.Host) diff --git a/login.go b/login.go index 13c9c6a..60dd281 100644 --- a/login.go +++ b/login.go @@ -19,7 +19,7 @@ import ( "git.erbosoft.com/amy/amsterdam/email" "git.erbosoft.com/amy/amsterdam/ui" "git.erbosoft.com/amy/amsterdam/util" - "github.com/labstack/gommon/log" + log "github.com/sirupsen/logrus" ) /* LoginForm renders the Amsterdam login form. @@ -75,8 +75,12 @@ func Login(ctxt ui.AmContext) (string, any, error) { if action == "cancel" { // Cancel button pressed 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 - user, uerr := database.AmGetUserByName(dlg.Field("user").Value) + user, uerr := database.AmGetUserByName(username) if uerr == nil { var ci *database.ContactInfo ci, uerr = user.ContactInfo() @@ -97,7 +101,6 @@ func Login(ctxt ui.AmContext) (string, any, error) { } } - dlg.Field("pass").Value = "" if uerr == nil { return dlg.RenderInfo(ctxt, "Password reminder has been sent to your E-mail address.") } else { @@ -106,9 +109,8 @@ func Login(ctxt ui.AmContext) (string, any, error) { } if action == "login" { // Login button pressed // 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 { - dlg.Field("pass").Value = "" return dlg.RenderError(ctxt, uerr.Error()) } ctxt.ReplaceUser(user) @@ -128,7 +130,6 @@ func Login(ctxt ui.AmContext) (string, any, error) { 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 ui.ErrorPage(ctxt, err) @@ -190,6 +191,7 @@ func VerifyEmailForm(ctxt ui.AmContext) (string, any, error) { 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 { if ci != nil && ci.Email != nil && *ci.Email != "" { msg := email.AmNewEmailMessage(user.Uid, remoteIP) @@ -397,6 +399,14 @@ func NewAccount(ctxt ui.AmContext) (string, any, error) { 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) { var emailaddy string uid, err := ctxt.URLParamInt("uid") diff --git a/ui/amcontext.go b/ui/amcontext.go index 1134614..3e6da78 100644 --- a/ui/amcontext.go +++ b/ui/amcontext.go @@ -200,6 +200,7 @@ func (c *amContext) Scratchpad() map[string]any { func (c *amContext) SubRender(name string) ([]byte, error) { view, err := views.GetTemplate(name) if err != nil { + log.Errorf("unable to load template \"%s\": %v", name, err) return nil, err } buf := new(bytes.Buffer) diff --git a/ui/dialog.go b/ui/dialog.go index 5b295b7..54f4e5e 100644 --- a/ui/dialog.go +++ b/ui/dialog.go @@ -47,6 +47,12 @@ type Dialog struct { Fields []DialogItem `yaml:"fields"` } +// VRange is used as a return type for ValueRange. +type VRange struct { + Low int + High int +} + //go:embed dialogs/* var dialogs embed.FS @@ -99,14 +105,14 @@ func (fld *DialogItem) IsChecked() bool { } // 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 != "" { parms := strings.Split(fld.Param, "-") low, _ := strconv.Atoi(parms[0]) 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. @@ -152,10 +158,12 @@ func (d *Dialog) Field(name string) *DialogItem { */ func (d *Dialog) Render(ctxt AmContext) (string, any, error) { required := false - for _, fld := range d.Fields { + for i, fld := range d.Fields { if fld.Required { - required = true - break + required = true // display the "required" blurb + } + if fld.Type == "password" { // clear all "password" fields as a security measure + d.Fields[i].Value = "" } } ctxt.VarMap().Set("amsterdam_required", required) @@ -304,12 +312,12 @@ func validateIntegerField(fld *DialogItem) error { v, err = strconv.Atoi(fld.Value) if err == nil { fld.AuxData = v // cache parsed value - lo, hi := fld.ValueRange() - if lo != -1 && hi != -1 { - if v < lo { - return fmt.Errorf("value of field \"%s\" cannot be less than %d", fld.Caption, lo) - } else if v > hi { - return fmt.Errorf("value of field \"%s\" cannot be greater than %d", fld.Caption, hi) + vr := fld.ValueRange() + if vr.Low != -1 && vr.High != -1 { + if v < vr.Low { + return fmt.Errorf("value of field \"%s\" cannot be less than %d", fld.Caption, vr.Low) + } else if v > vr.High { + return fmt.Errorf("value of field \"%s\" cannot be greater than %d", fld.Caption, vr.High) } } } else { diff --git a/ui/render_wrap.go b/ui/render_wrap.go index edd72eb..2a47e94 100644 --- a/ui/render_wrap.go +++ b/ui/render_wrap.go @@ -18,7 +18,7 @@ import ( "git.erbosoft.com/amy/amsterdam/config" "git.erbosoft.com/amy/amsterdam/database" "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 { @@ -39,6 +39,9 @@ func sendPageData(ctxt echo.Context, amctxt AmContext, command string, data any) default: err = fmt.Errorf("unknown rendering type: %s", command) } + if err != nil { + log.Errorf("sendPageData() barfed with %v", err) + } return err } diff --git a/ui/templates.go b/ui/templates.go index 52ca69f..0fd7828 100644 --- a/ui/templates.go +++ b/ui/templates.go @@ -29,6 +29,7 @@ import ( "github.com/CloudyKit/jet/v6/loaders/multi" "github.com/biter777/countries" "github.com/labstack/echo/v4" + log "github.com/sirupsen/logrus" ) //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: * s - The string to be capitalized. * Returns: @@ -187,6 +188,7 @@ func (r *TemplateRenderer) Render(w io.Writer, name string, data any, c echo.Con view, err := views.GetTemplate(name) if err != nil { + log.Errorf("Unable to load template \"%s\": %v", name, err) return err } var vmap jet.VarMap = nil diff --git a/ui/views/dialog.jet b/ui/views/dialog.jet index d93aeb1..0d4cec9 100644 --- a/ui/views/dialog.jet +++ b/ui/views/dialog.jet @@ -6,6 +6,7 @@ * 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/. *} +