feat(email): SMTP welcome email on user creation
This commit is contained in:
parent
8bbdaf54ac
commit
3b3cc7baa4
4 changed files with 101 additions and 2 deletions
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.lohmar.co.uk/lexton-it/NextWks/core/email"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler bundles admin HTTP handlers and their dependencies.
|
// Handler bundles admin HTTP handlers and their dependencies.
|
||||||
|
|
@ -12,15 +14,17 @@ type Handler struct {
|
||||||
store *UserStore
|
store *UserStore
|
||||||
groupStore *GroupStore
|
groupStore *GroupStore
|
||||||
syncWriter *SyncWriter
|
syncWriter *SyncWriter
|
||||||
|
emailer *email.Sender
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
configPath string
|
configPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandler(store *UserStore, groupStore *GroupStore, syncWriter *SyncWriter, logger *slog.Logger, configPath string) *Handler {
|
func NewHandler(store *UserStore, groupStore *GroupStore, syncWriter *SyncWriter, emailer *email.Sender, logger *slog.Logger, configPath string) *Handler {
|
||||||
return &Handler{
|
return &Handler{
|
||||||
store: store,
|
store: store,
|
||||||
groupStore: groupStore,
|
groupStore: groupStore,
|
||||||
syncWriter: syncWriter,
|
syncWriter: syncWriter,
|
||||||
|
emailer: emailer,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
configPath: configPath,
|
configPath: configPath,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -283,6 +283,21 @@ func (h *Handler) createUsersHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// Sync to Authelia YAML
|
// Sync to Authelia YAML
|
||||||
h.syncWriter.Sync()
|
h.syncWriter.Sync()
|
||||||
|
|
||||||
|
// Send welcome emails
|
||||||
|
for _, r := range results {
|
||||||
|
if r.Error == "" && r.GeneratedPassword != "" {
|
||||||
|
email := ""
|
||||||
|
for _, input := range req.Users {
|
||||||
|
if input.Username == r.Username {
|
||||||
|
email = input.Email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if email != "" {
|
||||||
|
go h.emailer.SendWelcome(email, r.Username, r.GeneratedPassword, "") // workspace URL from config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Convert to template results
|
// Convert to template results
|
||||||
resultRows := make([]templates.CreateUserResultRow, 0, len(results))
|
resultRows := make([]templates.CreateUserResultRow, 0, len(results))
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
|
|
|
||||||
76
src/core/email/email.go
Normal file
76
src/core/email/email.go
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
package email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/smtp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sender handles SMTP email delivery.
|
||||||
|
type Sender struct {
|
||||||
|
host string
|
||||||
|
port string
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
from string
|
||||||
|
logger *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSender creates an email sender from SMTP config.
|
||||||
|
func NewSender(host string, port int, username, password, from string, logger *slog.Logger) *Sender {
|
||||||
|
return &Sender{
|
||||||
|
host: host,
|
||||||
|
port: fmt.Sprintf("%d", port),
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
from: from,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendWelcome sends a welcome email with the generated password.
|
||||||
|
func (s *Sender) SendWelcome(to, username, password, workspaceURL string) error {
|
||||||
|
subject := "Welcome to Next Workspace"
|
||||||
|
body := fmt.Sprintf(`Hello %s,
|
||||||
|
|
||||||
|
Your Next Workspace account has been created.
|
||||||
|
|
||||||
|
Workspace: %s
|
||||||
|
Username: %s
|
||||||
|
Password: %s
|
||||||
|
|
||||||
|
Please log in and change your password.
|
||||||
|
|
||||||
|
Best regards,
|
||||||
|
Next Workspace`, username, workspaceURL, username, password)
|
||||||
|
|
||||||
|
return s.send(to, subject, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendAlert sends a generic alert email.
|
||||||
|
func (s *Sender) SendAlert(to, subject, message string) error {
|
||||||
|
return s.send(to, subject, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Sender) send(to, subject, body string) error {
|
||||||
|
if s.host == "" || s.username == "" {
|
||||||
|
s.logger.Warn("email not configured, skipping send", "to", to)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n%s",
|
||||||
|
s.from, to, subject, body)
|
||||||
|
|
||||||
|
addr := fmt.Sprintf("%s:%s", s.host, s.port)
|
||||||
|
auth := smtp.PlainAuth("", s.username, s.password, s.host)
|
||||||
|
|
||||||
|
err := smtp.SendMail(addr, auth, s.from, strings.Split(to, ","), []byte(msg))
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("send email failed", "to", to, "error", err)
|
||||||
|
return fmt.Errorf("send email: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("email sent", "to", to, "subject", subject)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"git.lohmar.co.uk/lexton-it/NextWks/core/auth"
|
"git.lohmar.co.uk/lexton-it/NextWks/core/auth"
|
||||||
"git.lohmar.co.uk/lexton-it/NextWks/core/config"
|
"git.lohmar.co.uk/lexton-it/NextWks/core/config"
|
||||||
"git.lohmar.co.uk/lexton-it/NextWks/core/db"
|
"git.lohmar.co.uk/lexton-it/NextWks/core/db"
|
||||||
|
"git.lohmar.co.uk/lexton-it/NextWks/core/email"
|
||||||
"git.lohmar.co.uk/lexton-it/NextWks/core/ui"
|
"git.lohmar.co.uk/lexton-it/NextWks/core/ui"
|
||||||
"git.lohmar.co.uk/lexton-it/NextWks/core/version"
|
"git.lohmar.co.uk/lexton-it/NextWks/core/version"
|
||||||
)
|
)
|
||||||
|
|
@ -70,8 +71,11 @@ func main() {
|
||||||
logger.Info("fixed user roles", "count", fixed)
|
logger.Info("fixed user roles", "count", fixed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create email sender
|
||||||
|
emailSender := email.NewSender(cfg.SMTP.Host, cfg.SMTP.Port, cfg.SMTP.Username, cfg.SMTP.Password, cfg.SMTP.From, logger)
|
||||||
|
|
||||||
// Create admin handler
|
// Create admin handler
|
||||||
adminHandler := admin.NewHandler(userStore, admin.NewGroupStore(database.DB), syncWriter, logger, *configPath)
|
adminHandler := admin.NewHandler(userStore, admin.NewGroupStore(database.DB), syncWriter, emailSender, logger, *configPath)
|
||||||
|
|
||||||
// Initialize session store and OIDC auth
|
// Initialize session store and OIDC auth
|
||||||
sessionStore := auth.NewSessionStore(database.DB)
|
sessionStore := auth.NewSessionStore(database.DB)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue