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"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.lohmar.co.uk/lexton-it/NextWks/core/email"
|
||||
)
|
||||
|
||||
// Handler bundles admin HTTP handlers and their dependencies.
|
||||
|
|
@ -12,15 +14,17 @@ type Handler struct {
|
|||
store *UserStore
|
||||
groupStore *GroupStore
|
||||
syncWriter *SyncWriter
|
||||
emailer *email.Sender
|
||||
logger *slog.Logger
|
||||
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{
|
||||
store: store,
|
||||
groupStore: groupStore,
|
||||
syncWriter: syncWriter,
|
||||
emailer: emailer,
|
||||
logger: logger,
|
||||
configPath: configPath,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -283,6 +283,21 @@ func (h *Handler) createUsersHandler(w http.ResponseWriter, r *http.Request) {
|
|||
// Sync to Authelia YAML
|
||||
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
|
||||
resultRows := make([]templates.CreateUserResultRow, 0, len(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/config"
|
||||
"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/version"
|
||||
)
|
||||
|
|
@ -70,8 +71,11 @@ func main() {
|
|||
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
|
||||
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
|
||||
sessionStore := auth.NewSessionStore(database.DB)
|
||||
|
|
|
|||
Loading…
Reference in a new issue