added contact info support and the code for sending a password reminder

This commit is contained in:
2025-10-01 23:02:33 -06:00
parent 2acac513f8
commit 1952b34cce
9 changed files with 177 additions and 13 deletions
+12 -8
View File
@@ -59,14 +59,16 @@ type AmConfig struct {
Dsn string `yaml:"dsn"`
} `yaml:"database"`
Email struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Tls string `yaml:"tls"`
AuthType string `yaml:"authType"`
User string `yaml:"user"`
Password string `yaml:"password"`
Signature string `yaml:"signature"`
Disclaimer string `yaml:"disclaimer"`
Host string `yaml:"host"`
Port int `yaml:"port"`
Tls string `yaml:"tls"`
AuthType string `yaml:"authType"`
User string `yaml:"user"`
Password string `yaml:"password"`
MailFromAddr string `yaml:"mailFromAddr"`
MailFromName string `yaml:"mailFromName"`
Signature string `yaml:"signature"`
Disclaimer string `yaml:"disclaimer"`
} `yaml:"email"`
Rendering struct {
TemplateDir string `yaml:"templatedir"`
@@ -130,6 +132,8 @@ func overlayConfig(dest *AmConfig, loaded *AmConfig, defaults *AmConfig) {
dest.Email.AuthType = overlayString(loaded.Email.AuthType, defaults.Email.AuthType)
dest.Email.User = overlayString(loaded.Email.User, defaults.Email.User)
dest.Email.Password = overlayString(loaded.Email.Password, defaults.Email.Password)
dest.Email.MailFromAddr = overlayString(loaded.Email.MailFromAddr, defaults.Email.MailFromAddr)
dest.Email.MailFromName = overlayString(loaded.Email.MailFromName, defaults.Email.MailFromName)
dest.Email.Signature = overlayString(loaded.Email.Signature, defaults.Email.Signature)
dest.Email.Disclaimer = overlayString(loaded.Email.Disclaimer, defaults.Email.Disclaimer)
dest.Rendering.TemplateDir = overlayString(loaded.Rendering.TemplateDir, defaults.Rendering.TemplateDir)
+2
View File
@@ -23,6 +23,8 @@ email:
authType: plain
user: jrn
password: foobiebletch
mailFromAddr: "nobody@example.com"
mailFromName: "Amsterdam E-Mail Service"
signature: |-
Amsterdam - community services, conferencing and more. <http://git.erbosoft.com/amy/amsterdam>
disclaimer: |-
+102
View File
@@ -0,0 +1,102 @@
/*
* 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"
"sync"
"time"
lru "github.com/hashicorp/golang-lru"
)
// ContactInfo stores the contact information for a user or community.
type ContactInfo struct {
ContactId int32 `db:"contactid"`
GivenName string `db:"given_name"`
FamilyName string `db:"family_name"`
MiddleInit string `db:"middle_init"`
Prefix *string `db:"prefix"`
Suffix *string `db:"suffix"`
Company *string `db:"company"`
Addr1 *string `db:"addr1"`
Addr2 *string `db:"addr2"`
Locality *string `db:"locality"`
Region *string `db:"region"`
PostalCode *string `db:"pcode"`
Country *string `db:"country"`
Phone *string `db:"phone"`
Fax *string `db:"fax"`
Mobile *string `db:"mobile"`
Email *string `db:"email"`
PrivateAddr bool `db:"pvt_addr"`
PrivatePhone bool `db:"pvt_phone"`
PrivateFax bool `db:"pvt_fax"`
PrivateEmail bool `db:"pvt_email"`
OwnerUid int32 `db:"owner_uid"`
OwnerCommId int32 `db:"owner_commid"`
PhotoURL *string `db:"photo_url"`
URL *string `db:"url"`
LastUpdate *time.Time
}
// contactCache is the cache for ContactInfo objects.
var contactCache *lru.TwoQueueCache = nil
// getContactMutex is a mutex on AmGetContactInfo.
var getContactMutex sync.Mutex
// init initializes the contact info cache.
func init() {
var err error
contactCache, err = lru.New2Q(100)
if err != nil {
panic(err)
}
}
// internalContactInfo retrieves the contact info from the database.
func internalContactInfo(id int32) (*ContactInfo, error) {
var dbdata []ContactInfo
err := amdb.Select(&dbdata, "SELECT * from contacts WHERE contactid = ?", id)
if err == nil {
if len(dbdata) > 1 {
err = fmt.Errorf("internalContactInfo(%d): Too many responses (%d)", id, len(dbdata))
} else if len(dbdata) == 0 {
return nil, nil
} else {
return &(dbdata[0]), nil
}
}
return nil, err
}
/* AmGetContactInfo retrieves the contact info for a given identifier.
* Parameters:
* id - The contact info ID top retrieve.
* Returns:
*
*/
func AmGetContactInfo(id int32) (*ContactInfo, error) {
getContactMutex.Lock()
defer getContactMutex.Unlock()
rc, ok := contactCache.Get(id)
if ok {
return rc.(*ContactInfo), nil
}
rc2, err := internalContactInfo(id)
if err == nil {
if rc2 != nil {
contactCache.Add(id, rc2)
}
return rc2, nil
}
return nil, err
}
+8
View File
@@ -60,6 +60,14 @@ func init() {
}
}
// ContactInfo returns the contact info structure for the user.
func (u *User) ContactInfo() (*ContactInfo, error) {
if u.ContactID < 0 {
return nil, nil
}
return AmGetContactInfo(u.ContactID)
}
/* AmGetUser returns a reference to the specified user.
* Parameters:
* uid - The UID of the user.
+2
View File
@@ -13,6 +13,7 @@ package email
import (
"fmt"
"git.erbosoft.com/amy/amsterdam/config"
"git.erbosoft.com/amy/amsterdam/util"
"github.com/CloudyKit/jet/v6"
)
@@ -126,6 +127,7 @@ func AmNewEmailMessage(sender int32, ip string) Message {
}
rc.uid = sender
rc.ip = ip
rc.SetFrom(config.GlobalConfig.Email.MailFromAddr, config.GlobalConfig.Email.MailFromName)
return rc
}
+1 -1
View File
@@ -7,7 +7,7 @@ If this reminder is not sufficient for you to remember what your password is,
then the system can change your password for you. To do so, please visit
the following URL:
http://example.com/passrecovery/{{ change_uid }}.{{ change.auth }}
http://example.com/passrecovery/{{ change_uid }}.{{ change_auth }}
Your password will be changed and a new password will be E-mailed to you
at this address.
+23 -4
View File
@@ -10,9 +10,11 @@
package main
import (
"errors"
"fmt"
"git.erbosoft.com/amy/amsterdam/database"
"git.erbosoft.com/amy/amsterdam/email"
"git.erbosoft.com/amy/amsterdam/ui"
)
@@ -72,13 +74,30 @@ func Login(ctxt ui.AmContext) (string, any, error) {
if action == "remind" { // Password Reminder button pressed
user, uerr := database.AmGetUserByName(dlg.Field("user").Value)
if uerr == nil {
_ = user
// TODO: send password reminder
var ci *database.ContactInfo
ci, uerr = user.ContactInfo()
if uerr == nil {
if ci != nil && ci.Email != nil && *ci.Email != "" {
msg := email.AmNewEmailMessage(user.Uid, ctxt.RemoteIP())
msg.AddTo(*ci.Email, "")
msg.SetTemplate("pass_remind.jet")
msg.AddVariable("username", user.Username)
msg.AddVariable("reminder", user.PassReminder)
msg.AddVariable("change_uid", user.Uid)
msg.AddVariable("change_auth", "TODO") // TODO: add change auth link
msg.Send()
} else {
uerr = errors.New("cannot find email address")
}
}
}
dlg.Field("pass").Value = ""
return dlg.RenderError(ctxt, "Password reminder has been sent to your E-mail address.")
if uerr == nil {
return dlg.RenderInfo(ctxt, "Password reminder has been sent to your E-mail address.")
} else {
return dlg.RenderError(ctxt, uerr.Error())
}
}
if action == "login" { // Login button pressed
// authenticate the user
+14
View File
@@ -146,6 +146,20 @@ func (d *Dialog) RenderError(ctxt AmContext, errormessage string) (string, any,
return d.Render(ctxt)
}
/* RenderInfo sets up the rendering parameters to send this dialog to the output with an info message.
* Parameters:
* ctxt - The AmContext for this request.
* infoMessage - The info message to be displayed.
* Returns:
* Command string dictating what to be rendered.
* Data as a parameter for the command string.
* Standard Go error status.
*/
func (d *Dialog) RenderInfo(ctxt AmContext, infoMessage string) (string, any, error) {
ctxt.VarMap().Set("amsterdam_infoMessage", infoMessage)
return d.Render(ctxt)
}
/* LoadFromForm loads the values in a dialog from the form fields in the request.
* Parameters:
* ctxt - The AmContext for this request.
+13
View File
@@ -38,6 +38,19 @@
</div>
</div>
{{ end }}
{{ if isset(amsterdam_infoMessage) }}
<!-- Info Message Banner -->
<div class="bg-blue-100 border border-blue-400 text-blue-700 px-4 py-3 rounded mb-6 hidden" id="info-banner">
<div class="flex items-center">
<div class="flex-shrink-0">
<span class="text-blue-500 text-xl">️</span>
</div>
<div class="ml-3">
<p class="text-sm font-medium" id="info-message">{{ amsterdam_infoMessage }}.</p>
</div>
</div>
</div>
{{ end }}
<div class="bg-gray-50 p-6 rounded-lg">
<div class="space-y-4">