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
+4
View File
@@ -65,6 +65,8 @@ type AmConfig struct {
AuthType string `yaml:"authType"` AuthType string `yaml:"authType"`
User string `yaml:"user"` User string `yaml:"user"`
Password string `yaml:"password"` Password string `yaml:"password"`
MailFromAddr string `yaml:"mailFromAddr"`
MailFromName string `yaml:"mailFromName"`
Signature string `yaml:"signature"` Signature string `yaml:"signature"`
Disclaimer string `yaml:"disclaimer"` Disclaimer string `yaml:"disclaimer"`
} `yaml:"email"` } `yaml:"email"`
@@ -130,6 +132,8 @@ func overlayConfig(dest *AmConfig, loaded *AmConfig, defaults *AmConfig) {
dest.Email.AuthType = overlayString(loaded.Email.AuthType, defaults.Email.AuthType) dest.Email.AuthType = overlayString(loaded.Email.AuthType, defaults.Email.AuthType)
dest.Email.User = overlayString(loaded.Email.User, defaults.Email.User) dest.Email.User = overlayString(loaded.Email.User, defaults.Email.User)
dest.Email.Password = overlayString(loaded.Email.Password, defaults.Email.Password) 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.Signature = overlayString(loaded.Email.Signature, defaults.Email.Signature)
dest.Email.Disclaimer = overlayString(loaded.Email.Disclaimer, defaults.Email.Disclaimer) dest.Email.Disclaimer = overlayString(loaded.Email.Disclaimer, defaults.Email.Disclaimer)
dest.Rendering.TemplateDir = overlayString(loaded.Rendering.TemplateDir, defaults.Rendering.TemplateDir) dest.Rendering.TemplateDir = overlayString(loaded.Rendering.TemplateDir, defaults.Rendering.TemplateDir)
+2
View File
@@ -23,6 +23,8 @@ email:
authType: plain authType: plain
user: jrn user: jrn
password: foobiebletch password: foobiebletch
mailFromAddr: "nobody@example.com"
mailFromName: "Amsterdam E-Mail Service"
signature: |- signature: |-
Amsterdam - community services, conferencing and more. <http://git.erbosoft.com/amy/amsterdam> Amsterdam - community services, conferencing and more. <http://git.erbosoft.com/amy/amsterdam>
disclaimer: |- 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. /* AmGetUser returns a reference to the specified user.
* Parameters: * Parameters:
* uid - The UID of the user. * uid - The UID of the user.
+2
View File
@@ -13,6 +13,7 @@ package email
import ( import (
"fmt" "fmt"
"git.erbosoft.com/amy/amsterdam/config"
"git.erbosoft.com/amy/amsterdam/util" "git.erbosoft.com/amy/amsterdam/util"
"github.com/CloudyKit/jet/v6" "github.com/CloudyKit/jet/v6"
) )
@@ -126,6 +127,7 @@ func AmNewEmailMessage(sender int32, ip string) Message {
} }
rc.uid = sender rc.uid = sender
rc.ip = ip rc.ip = ip
rc.SetFrom(config.GlobalConfig.Email.MailFromAddr, config.GlobalConfig.Email.MailFromName)
return rc 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 then the system can change your password for you. To do so, please visit
the following URL: 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 Your password will be changed and a new password will be E-mailed to you
at this address. at this address.
+23 -4
View File
@@ -10,9 +10,11 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"git.erbosoft.com/amy/amsterdam/database" "git.erbosoft.com/amy/amsterdam/database"
"git.erbosoft.com/amy/amsterdam/email"
"git.erbosoft.com/amy/amsterdam/ui" "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 if action == "remind" { // Password Reminder button pressed
user, uerr := database.AmGetUserByName(dlg.Field("user").Value) user, uerr := database.AmGetUserByName(dlg.Field("user").Value)
if uerr == nil { if uerr == nil {
_ = user var ci *database.ContactInfo
// TODO: send password reminder 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 = "" 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 if action == "login" { // Login button pressed
// authenticate the user // authenticate the user
+14
View File
@@ -146,6 +146,20 @@ func (d *Dialog) RenderError(ctxt AmContext, errormessage string) (string, any,
return d.Render(ctxt) 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. /* LoadFromForm loads the values in a dialog from the form fields in the request.
* Parameters: * Parameters:
* ctxt - The AmContext for this request. * ctxt - The AmContext for this request.
+13
View File
@@ -38,6 +38,19 @@
</div> </div>
</div> </div>
{{ end }} {{ 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="bg-gray-50 p-6 rounded-lg">
<div class="space-y-4"> <div class="space-y-4">