added contact info support and the code for sending a password reminder
This commit is contained in:
+12
-8
@@ -59,14 +59,16 @@ type AmConfig struct {
|
|||||||
Dsn string `yaml:"dsn"`
|
Dsn string `yaml:"dsn"`
|
||||||
} `yaml:"database"`
|
} `yaml:"database"`
|
||||||
Email struct {
|
Email struct {
|
||||||
Host string `yaml:"host"`
|
Host string `yaml:"host"`
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
Tls string `yaml:"tls"`
|
Tls string `yaml:"tls"`
|
||||||
AuthType string `yaml:"authType"`
|
AuthType string `yaml:"authType"`
|
||||||
User string `yaml:"user"`
|
User string `yaml:"user"`
|
||||||
Password string `yaml:"password"`
|
Password string `yaml:"password"`
|
||||||
Signature string `yaml:"signature"`
|
MailFromAddr string `yaml:"mailFromAddr"`
|
||||||
Disclaimer string `yaml:"disclaimer"`
|
MailFromName string `yaml:"mailFromName"`
|
||||||
|
Signature string `yaml:"signature"`
|
||||||
|
Disclaimer string `yaml:"disclaimer"`
|
||||||
} `yaml:"email"`
|
} `yaml:"email"`
|
||||||
Rendering struct {
|
Rendering struct {
|
||||||
TemplateDir string `yaml:"templatedir"`
|
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.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)
|
||||||
|
|||||||
@@ -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: |-
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user