implemented the E-mail sending functionality
This commit is contained in:
+10
-6
@@ -59,12 +59,14 @@ 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"`
|
||||||
|
Disclaimer string `yaml:"disclaimer"`
|
||||||
} `yaml:"email"`
|
} `yaml:"email"`
|
||||||
Rendering struct {
|
Rendering struct {
|
||||||
TemplateDir string `yaml:"templatedir"`
|
TemplateDir string `yaml:"templatedir"`
|
||||||
@@ -128,6 +130,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.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)
|
dest.Rendering.TemplateDir = overlayString(loaded.Rendering.TemplateDir, defaults.Rendering.TemplateDir)
|
||||||
dest.Rendering.CookieKey = overlayString(loaded.Rendering.CookieKey, defaults.Rendering.CookieKey)
|
dest.Rendering.CookieKey = overlayString(loaded.Rendering.CookieKey, defaults.Rendering.CookieKey)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ email:
|
|||||||
authType: plain
|
authType: plain
|
||||||
user: jrn
|
user: jrn
|
||||||
password: foobiebletch
|
password: foobiebletch
|
||||||
|
signature: |-
|
||||||
|
Amsterdam - community services, conferencing and more. <http://git.erbosoft.com/amy/amsterdam>
|
||||||
|
disclaimer: |-
|
||||||
|
Message sent via Amsterdam Web Communities System - <http://git.erbosoft.com/amy/amsterdam>
|
||||||
|
The Amsterdam Project is not responsible for the contents of this message
|
||||||
|
Report abuses to: <abuse@example.com>
|
||||||
rendering:
|
rendering:
|
||||||
templatedir: custom_templates
|
templatedir: custom_templates
|
||||||
cookiekey: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
|
cookiekey: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
|
||||||
|
|||||||
+86
-11
@@ -10,7 +10,12 @@
|
|||||||
// Package email contains support for E-mail messages sent by Amsterdam.
|
// Package email contains support for E-mail messages sent by Amsterdam.
|
||||||
package email
|
package email
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.erbosoft.com/amy/amsterdam/util"
|
||||||
|
"github.com/CloudyKit/jet/v6"
|
||||||
|
)
|
||||||
|
|
||||||
// Message is the interface for an E-mail message to be sent.
|
// Message is the interface for an E-mail message to be sent.
|
||||||
type Message interface {
|
type Message interface {
|
||||||
@@ -21,18 +26,32 @@ type Message interface {
|
|||||||
SetSubject(string)
|
SetSubject(string)
|
||||||
SetText(string)
|
SetText(string)
|
||||||
AddHeader(string, string)
|
AddHeader(string, string)
|
||||||
|
SetTemplate(string)
|
||||||
|
AddVariable(string, any)
|
||||||
|
Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// amMessage is the internal structure of the Message.
|
||||||
type amMessage struct {
|
type amMessage struct {
|
||||||
from string
|
from string
|
||||||
to []string
|
fromAddr string
|
||||||
cc []string
|
to []string
|
||||||
bcc []string
|
toAddrs []string
|
||||||
subject string
|
cc []string
|
||||||
text string
|
bcc []string
|
||||||
headers map[string]string
|
subject string
|
||||||
|
text string
|
||||||
|
headers map[string]string
|
||||||
|
template string
|
||||||
|
vars jet.VarMap
|
||||||
|
uid int32
|
||||||
|
ip string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// freeMessages is a free list for amMessage structures.
|
||||||
|
var freeMessages util.FreeList[amMessage]
|
||||||
|
|
||||||
|
// formatAddress outputs an E-mail address with optional name associated with it.
|
||||||
func formatAddress(addr string, name string) string {
|
func formatAddress(addr string, name string) string {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return addr
|
return addr
|
||||||
@@ -41,35 +60,91 @@ func formatAddress(addr string, name string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetFrom sets the From: address of the message.
|
||||||
func (m *amMessage) SetFrom(addr string, name string) {
|
func (m *amMessage) SetFrom(addr string, name string) {
|
||||||
m.from = formatAddress(addr, name)
|
m.from = formatAddress(addr, name)
|
||||||
|
m.fromAddr = addr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddTo ads a To: address to the message.
|
||||||
func (m *amMessage) AddTo(addr string, name string) {
|
func (m *amMessage) AddTo(addr string, name string) {
|
||||||
m.to = append(m.to, formatAddress(addr, name))
|
m.to = append(m.to, formatAddress(addr, name))
|
||||||
|
m.toAddrs = append(m.toAddrs, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddCC ads a Cc: address to the message.
|
||||||
func (m *amMessage) AddCC(addr string, name string) {
|
func (m *amMessage) AddCC(addr string, name string) {
|
||||||
m.cc = append(m.cc, formatAddress(addr, name))
|
m.cc = append(m.cc, formatAddress(addr, name))
|
||||||
|
m.toAddrs = append(m.toAddrs, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddBCC ads a Bcc: address to the message.
|
||||||
func (m *amMessage) AddBCC(addr string, name string) {
|
func (m *amMessage) AddBCC(addr string, name string) {
|
||||||
m.bcc = append(m.bcc, formatAddress(addr, name))
|
m.bcc = append(m.bcc, formatAddress(addr, name))
|
||||||
|
m.toAddrs = append(m.toAddrs, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSubject sets the message's subject.
|
||||||
func (m *amMessage) SetSubject(s string) {
|
func (m *amMessage) SetSubject(s string) {
|
||||||
m.subject = s
|
m.subject = s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetText sets the text of the message.
|
||||||
func (m *amMessage) SetText(txt string) {
|
func (m *amMessage) SetText(txt string) {
|
||||||
m.text = txt
|
m.text = txt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddHaader adds a new header to the message.
|
||||||
func (m *amMessage) AddHeader(name string, value string) {
|
func (m *amMessage) AddHeader(name string, value string) {
|
||||||
m.headers[name] = value
|
m.headers[name] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
func AmNewEmailMessage() Message {
|
func (m *amMessage) SetTemplate(templ string) {
|
||||||
rc := amMessage{to: make([]string, 0), cc: make([]string, 0), bcc: make([]string, 0), headers: make(map[string]string)}
|
m.template = templ
|
||||||
return &rc
|
}
|
||||||
|
|
||||||
|
func (m *amMessage) AddVariable(name string, value any) {
|
||||||
|
m.vars.Set(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *amMessage) Send() {
|
||||||
|
sendChan <- m
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AmNewEmailMessage creates a new message and returns it.
|
||||||
|
* Parameters:
|
||||||
|
* sender = User ID of the person sending the message.
|
||||||
|
* ip = IP address of the person sending the message.
|
||||||
|
* Returns:
|
||||||
|
* The new Message.
|
||||||
|
*/
|
||||||
|
func AmNewEmailMessage(sender int32, ip string) Message {
|
||||||
|
rc := freeMessages.Get()
|
||||||
|
if rc == nil {
|
||||||
|
rc = &amMessage{to: make([]string, 0), cc: make([]string, 0), bcc: make([]string, 0),
|
||||||
|
headers: make(map[string]string)}
|
||||||
|
}
|
||||||
|
rc.uid = sender
|
||||||
|
rc.ip = ip
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
// recycleMessage cleans out a message and puts it back on the free list.
|
||||||
|
func recycleMessage(m *amMessage) {
|
||||||
|
m.from = ""
|
||||||
|
m.fromAddr = ""
|
||||||
|
m.to = make([]string, 0)
|
||||||
|
m.toAddrs = make([]string, 0)
|
||||||
|
m.cc = make([]string, 0)
|
||||||
|
m.bcc = make([]string, 0)
|
||||||
|
m.subject = ""
|
||||||
|
m.text = ""
|
||||||
|
for k := range m.headers {
|
||||||
|
delete(m.headers, k)
|
||||||
|
}
|
||||||
|
m.template = ""
|
||||||
|
for k := range m.vars {
|
||||||
|
delete(m.vars, k)
|
||||||
|
}
|
||||||
|
freeMessages.Put(m)
|
||||||
}
|
}
|
||||||
|
|||||||
+220
@@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package email contains support for E-mail messages sent by Amsterdam.
|
||||||
|
package email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"maps"
|
||||||
|
"net/smtp"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.erbosoft.com/amy/amsterdam/config"
|
||||||
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
|
"github.com/CloudyKit/jet/v6"
|
||||||
|
"github.com/CloudyKit/jet/v6/loaders/embedfs"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed templates/*
|
||||||
|
var emailTemplates embed.FS
|
||||||
|
|
||||||
|
// email_renderer is a separate Jet instance for making E-mail messages.
|
||||||
|
var emailRenderer *jet.Set
|
||||||
|
|
||||||
|
// disclaimerLines is the disclaimer from the configuration broken into lines.
|
||||||
|
var disclaimerLines []string
|
||||||
|
|
||||||
|
// signatureLines is the signature from the configuration broken into lines.
|
||||||
|
var signatureLines []string
|
||||||
|
|
||||||
|
// The mail host and port.
|
||||||
|
var mailHost string
|
||||||
|
|
||||||
|
// The SMTP authentication to use.
|
||||||
|
var auth smtp.Auth
|
||||||
|
|
||||||
|
// formatMessage takes a message and turns it into serialized bytes for sending.
|
||||||
|
func formatMessage(m *amMessage) ([]byte, error) {
|
||||||
|
if m.template != "" {
|
||||||
|
// Render the template for the message, which may reset Subject.
|
||||||
|
templ, err := emailRenderer.GetTemplate(m.template)
|
||||||
|
if err == nil {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = templ.Execute(&buf, m.vars, Message(m))
|
||||||
|
if err == nil {
|
||||||
|
m.text = buf.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return make([]byte, 0), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
user, err := database.AmGetUser(m.uid)
|
||||||
|
if err == nil {
|
||||||
|
// Build the final headers.
|
||||||
|
hdrs := make(map[string]string)
|
||||||
|
maps.Copy(hdrs, m.headers)
|
||||||
|
hdrs["From"] = m.from
|
||||||
|
hdrs["To"] = strings.Join(m.to, ", ")
|
||||||
|
hdrs["Cc"] = strings.Join(m.cc, ", ")
|
||||||
|
hdrs["Bcc"] = strings.Join(m.bcc, ", ")
|
||||||
|
hdrs["Subject"] = m.subject
|
||||||
|
hdrs["Content-Type"] = "text/plain; charset=UTF-8"
|
||||||
|
me, _ := os.Hostname()
|
||||||
|
hdrs["X-Amsterdam-Server-Info"] = fmt.Sprintf("%s (Amsterdam/%s)", me, config.AMSTERDAM_VERSION)
|
||||||
|
hdrs["X-Amsterdam-Sender-Info"] = fmt.Sprintf("uid %d, name %s, ip [%s]", m.uid, user.Username, m.ip)
|
||||||
|
for i, v := range disclaimerLines {
|
||||||
|
hdrs[fmt.Sprintf("X-Disclaimer-%d", i)] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the header keys tro make for a better presentation.
|
||||||
|
keys := make([]string, 0, len(hdrs))
|
||||||
|
for k := range hdrs {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
slices.Sort(keys)
|
||||||
|
|
||||||
|
// Build the actual message.
|
||||||
|
var out bytes.Buffer
|
||||||
|
for _, k := range keys {
|
||||||
|
fmt.Fprintf(&out, "%s: %s\r\n", k, hdrs[k])
|
||||||
|
}
|
||||||
|
out.WriteString("\r\n")
|
||||||
|
for _, l := range strings.Split(m.text, "\n") {
|
||||||
|
fmt.Fprintf(&out, "%s\r\n", l)
|
||||||
|
}
|
||||||
|
out.WriteString("--\r\n")
|
||||||
|
for _, l := range signatureLines {
|
||||||
|
fmt.Fprintf(&out, "%s\r\n", l)
|
||||||
|
}
|
||||||
|
return out.Bytes(), nil
|
||||||
|
}
|
||||||
|
return make([]byte, 0), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// transmitMessage handles the sending of the message.
|
||||||
|
func transmitMessage(m *amMessage, body []byte) {
|
||||||
|
cl, err := smtp.Dial(mailHost)
|
||||||
|
if err == nil {
|
||||||
|
defer cl.Close()
|
||||||
|
me, _ := os.Hostname()
|
||||||
|
if err = cl.Hello(me); err == nil {
|
||||||
|
if config.GlobalConfig.Email.Tls == "starttls" {
|
||||||
|
if ok, _ := cl.Extension("STARTTLS"); ok {
|
||||||
|
err = cl.StartTLS(nil)
|
||||||
|
} else {
|
||||||
|
log.Infof("server %s does not support STARTTLS", mailHost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if err = cl.Auth(auth); err == nil {
|
||||||
|
if err = cl.Mail(m.fromAddr); err == nil {
|
||||||
|
for _, addr := range m.toAddrs {
|
||||||
|
if err = cl.Rcpt(addr); err != nil {
|
||||||
|
log.Errorf("failed to set recipient address: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
var w io.WriteCloser
|
||||||
|
w, err = cl.Data()
|
||||||
|
if err == nil {
|
||||||
|
_, err = w.Write(body)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to write message data: %v", err)
|
||||||
|
}
|
||||||
|
err = w.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to close and send: %v", err)
|
||||||
|
}
|
||||||
|
err = cl.Quit()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to quit session: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Errorf("failed to start writing data: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Errorf("failed to set sender: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Errorf("failed to authenticate to server: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Errorf("failed to start TLS handshake: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Errorf("error sending HELO to server: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Errorf("unable to contact host %s via SMTP: %v", mailHost, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// senderLoop collects E-mail messages from the channel and pushes them out.
|
||||||
|
func senderLoop(sent chan *amMessage, done chan bool) {
|
||||||
|
for m := range sent {
|
||||||
|
body, err := formatMessage(m)
|
||||||
|
if err == nil {
|
||||||
|
transmitMessage(m, body)
|
||||||
|
} else {
|
||||||
|
log.Errorf("unable to format message: %v", err)
|
||||||
|
}
|
||||||
|
go recycleMessage(m)
|
||||||
|
}
|
||||||
|
done <- true // signal done for synchronization
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendChan is the channel we put E-mail messages on to be sent.
|
||||||
|
var sendChan chan *amMessage
|
||||||
|
|
||||||
|
// doneChan is the channel that gets signaled when the senderLoop breaks.
|
||||||
|
var doneChan chan bool
|
||||||
|
|
||||||
|
// SetupMailSender starts the mail-sending goroutine.
|
||||||
|
func SetupMailSender() {
|
||||||
|
// Initialize mail host and authentication.
|
||||||
|
mailHost = fmt.Sprintf("%s:%d", config.GlobalConfig.Email.Host, config.GlobalConfig.Email.Port)
|
||||||
|
switch config.GlobalConfig.Email.AuthType {
|
||||||
|
case "plain":
|
||||||
|
auth = smtp.PlainAuth("", config.GlobalConfig.Email.User, config.GlobalConfig.Email.Password,
|
||||||
|
config.GlobalConfig.Email.Host)
|
||||||
|
default:
|
||||||
|
panic("Unknown auth type: " + config.GlobalConfig.Email.AuthType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the configured disclaimer and signature.
|
||||||
|
disclaimerLines = strings.Split(config.GlobalConfig.Email.Disclaimer, "\n")
|
||||||
|
signatureLines = strings.Split(config.GlobalConfig.Email.Signature, "\n")
|
||||||
|
|
||||||
|
// Initialize the template engine.
|
||||||
|
emailRenderer = jet.NewSet(
|
||||||
|
embedfs.NewLoader("templates/", emailTemplates),
|
||||||
|
jet.DevelopmentMode(true),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start the sender loop.
|
||||||
|
sendChan = make(chan *amMessage, 16)
|
||||||
|
doneChan = make(chan bool)
|
||||||
|
go senderLoop(sendChan, doneChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndMailServer shuts down the mail-sending goroutine.
|
||||||
|
func EndMailServer() {
|
||||||
|
close(sendChan) // will break the loop in senderLoop
|
||||||
|
<-doneChan // wait for routine to complete
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{{ .SetSubject("Amsterdam Email Confirmation") }}
|
||||||
|
Welcome to the Amsterdam conferencing system! In order to fully activate your
|
||||||
|
account after you register or change your E-mail address, you must provide a
|
||||||
|
confirmation number to the system. Please enter this number into the "Confirm
|
||||||
|
E-mail Address" dialog on the system when indicated.
|
||||||
|
|
||||||
|
Your confirmation number for your account "{{ username }}" is {{ confnum }}.
|
||||||
|
|
||||||
|
Access the E-mail verification dialog at <http://example.com/verifyemail>.
|
||||||
|
|
||||||
|
Thank you, and enjoy the Amsterdam conferencing system!
|
||||||
|
|
||||||
|
- The Management
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{{ .SetSubject("Amsterdam Password Changed") }}
|
||||||
|
The password for your account "{{ username }}" has been changed. The new
|
||||||
|
password is "{{ password }}".
|
||||||
|
|
||||||
|
You should log into Amsterdam immediately and change the password to something
|
||||||
|
else. You can change the password for your account, once you are logged in,
|
||||||
|
by clicking on the "Profile" link in the top bar.
|
||||||
|
|
||||||
|
If you did NOT request a password change on your account, please notify the
|
||||||
|
system administrator IMMEDIATELY.
|
||||||
|
|
||||||
|
- The Management
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{{ .SetSubject("Amsterdam Password Reminder Message") }}
|
||||||
|
Here is the password reminder for your account "{{ username }}" as you requested:
|
||||||
|
|
||||||
|
{{ reminder }}
|
||||||
|
|
||||||
|
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 }}
|
||||||
|
|
||||||
|
Your password will be changed and a new password will be E-mailed to you
|
||||||
|
at this address.
|
||||||
|
|
||||||
|
If you did NOT request a password reminder, then this message was sent
|
||||||
|
by someone attempting to access your account without your knowledge. Do
|
||||||
|
not panic! Nothing has happened to your account or password yet, but
|
||||||
|
please do notify the system administrator.
|
||||||
|
|
||||||
|
- The Management
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/database"
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
@@ -67,15 +66,21 @@ func Login(ctxt ui.AmContext) (string, any, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
action := dlg.WhichButton(ctxt)
|
action := dlg.WhichButton(ctxt)
|
||||||
if action == "cancel" {
|
if action == "cancel" { // Cancel button pressed
|
||||||
return "redirect", target, nil
|
return "redirect", target, nil
|
||||||
}
|
}
|
||||||
if action == "remind" {
|
if action == "remind" { // Password Reminder button pressed
|
||||||
// TODO: send password reminder
|
user, uerr := database.AmGetUserByName(dlg.Field("user").Value)
|
||||||
|
if uerr == nil {
|
||||||
|
_ = user
|
||||||
|
// TODO: send password reminder
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
dlg.Field("pass").Value = ""
|
dlg.Field("pass").Value = ""
|
||||||
return dlg.RenderError(ctxt, "Password reminder has been sent to your E-mail address.")
|
return dlg.RenderError(ctxt, "Password reminder has been sent to your E-mail address.")
|
||||||
}
|
}
|
||||||
if action == "login" {
|
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(dlg.Field("user").Value, dlg.Field("pass").Value, ctxt.RemoteIP())
|
||||||
if uerr != nil {
|
if uerr != nil {
|
||||||
@@ -89,7 +94,8 @@ func Login(ctxt ui.AmContext) (string, any, error) {
|
|||||||
// TODO: bounce to E-mail verify if we can do so
|
// TODO: bounce to E-mail verify if we can do so
|
||||||
return "redirect", target, nil
|
return "redirect", target, nil
|
||||||
}
|
}
|
||||||
err = errors.New("no known button click on POST to login function")
|
dlg.Field("pass").Value = ""
|
||||||
|
return dlg.RenderError(ctxt, "No known button click on POST to login function.")
|
||||||
}
|
}
|
||||||
return ui.ErrorPage(ctxt, err)
|
return ui.ErrorPage(ctxt, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,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"
|
||||||
|
"git.erbosoft.com/amy/amsterdam/email"
|
||||||
"git.erbosoft.com/amy/amsterdam/ui"
|
"git.erbosoft.com/amy/amsterdam/ui"
|
||||||
"github.com/labstack/echo-contrib/session"
|
"github.com/labstack/echo-contrib/session"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
@@ -60,6 +61,8 @@ func main() {
|
|||||||
panic(fmt.Sprintf("Database open failure: %v", err))
|
panic(fmt.Sprintf("Database open failure: %v", err))
|
||||||
}
|
}
|
||||||
defer database.ClosedownDb()
|
defer database.ClosedownDb()
|
||||||
|
email.SetupMailSender()
|
||||||
|
defer email.EndMailServer()
|
||||||
ui.SetupTemplates()
|
ui.SetupTemplates()
|
||||||
ui.SetupSessionManager()
|
ui.SetupSessionManager()
|
||||||
ui.SetupLeftMenus()
|
ui.SetupLeftMenus()
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package util contains utility definitions.
|
||||||
|
package util
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
// freeListElem is an element of the free list.
|
||||||
|
type freeListElem[T any] struct {
|
||||||
|
next *freeListElem[T]
|
||||||
|
prev *freeListElem[T]
|
||||||
|
data *T
|
||||||
|
}
|
||||||
|
|
||||||
|
// FreeList defines a free list.
|
||||||
|
type FreeList[T any] struct {
|
||||||
|
New func() *T
|
||||||
|
mutex sync.Mutex
|
||||||
|
listptr *freeListElem[T]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put adds a value to the free list.
|
||||||
|
func (l *FreeList[T]) Put(value *T) {
|
||||||
|
l.mutex.Lock()
|
||||||
|
defer l.mutex.Unlock()
|
||||||
|
ne := freeListElem[T]{data: value}
|
||||||
|
if l.listptr == nil {
|
||||||
|
ne.next = &ne
|
||||||
|
ne.prev = &ne
|
||||||
|
l.listptr = &ne
|
||||||
|
} else {
|
||||||
|
ne.next = l.listptr
|
||||||
|
ne.prev = l.listptr.prev
|
||||||
|
ne.next.prev = &ne
|
||||||
|
ne.prev.next = &ne
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get removes a value from the free list. If there are no values and New is specified, is calls that.
|
||||||
|
func (l *FreeList[T]) Get() *T {
|
||||||
|
l.mutex.Lock()
|
||||||
|
defer l.mutex.Unlock()
|
||||||
|
var rc *T = nil
|
||||||
|
if l.listptr == nil {
|
||||||
|
if l.New != nil {
|
||||||
|
rc = l.New()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
elt := l.listptr
|
||||||
|
rc = elt.data
|
||||||
|
l.listptr = elt.next
|
||||||
|
if l.listptr == elt {
|
||||||
|
l.listptr = nil
|
||||||
|
} else {
|
||||||
|
elt.prev.next = elt.next
|
||||||
|
elt.next.prev = elt.prev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rc
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user