mirror of
https://github.com/h44z/wg-portal.git
synced 2025-04-19 08:55:12 +00:00
191 lines
5.5 KiB
Go
191 lines
5.5 KiB
Go
package mail
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
|
|
"github.com/h44z/wg-portal/internal/config"
|
|
"github.com/h44z/wg-portal/internal/domain"
|
|
)
|
|
|
|
// region dependencies
|
|
|
|
type Mailer interface {
|
|
// Send sends an email with the given subject and body to the given recipients.
|
|
Send(ctx context.Context, subject, body string, to []string, options *domain.MailOptions) error
|
|
}
|
|
|
|
type ConfigFileManager interface {
|
|
// GetInterfaceConfig returns the configuration for the given interface.
|
|
GetInterfaceConfig(ctx context.Context, id domain.InterfaceIdentifier) (io.Reader, error)
|
|
// GetPeerConfig returns the configuration for the given peer.
|
|
GetPeerConfig(ctx context.Context, id domain.PeerIdentifier) (io.Reader, error)
|
|
// GetPeerConfigQrCode returns the QR code for the given peer.
|
|
GetPeerConfigQrCode(ctx context.Context, id domain.PeerIdentifier) (io.Reader, error)
|
|
}
|
|
|
|
type UserDatabaseRepo interface {
|
|
// GetUser returns the user with the given identifier.
|
|
GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error)
|
|
}
|
|
|
|
type WireguardDatabaseRepo interface {
|
|
// GetInterfaceAndPeers returns the interface and all peers for the given interface identifier.
|
|
GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, []domain.Peer, error)
|
|
// GetPeer returns the peer with the given identifier.
|
|
GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error)
|
|
// GetInterface returns the interface with the given identifier.
|
|
GetInterface(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, error)
|
|
}
|
|
|
|
type TemplateRenderer interface {
|
|
// GetConfigMail returns the text and html template for the mail with a link.
|
|
GetConfigMail(user *domain.User, link string) (io.Reader, io.Reader, error)
|
|
// GetConfigMailWithAttachment returns the text and html template for the mail with an attachment.
|
|
GetConfigMailWithAttachment(user *domain.User, cfgName, qrName string) (
|
|
io.Reader,
|
|
io.Reader,
|
|
error,
|
|
)
|
|
}
|
|
|
|
// endregion dependencies
|
|
|
|
type Manager struct {
|
|
cfg *config.Config
|
|
|
|
tplHandler TemplateRenderer
|
|
mailer Mailer
|
|
configFiles ConfigFileManager
|
|
users UserDatabaseRepo
|
|
wg WireguardDatabaseRepo
|
|
}
|
|
|
|
// NewMailManager creates a new mail manager.
|
|
func NewMailManager(
|
|
cfg *config.Config,
|
|
mailer Mailer,
|
|
configFiles ConfigFileManager,
|
|
users UserDatabaseRepo,
|
|
wg WireguardDatabaseRepo,
|
|
) (*Manager, error) {
|
|
tplHandler, err := newTemplateHandler(cfg.Web.ExternalUrl)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to initialize template handler: %w", err)
|
|
}
|
|
|
|
m := &Manager{
|
|
cfg: cfg,
|
|
tplHandler: tplHandler,
|
|
mailer: mailer,
|
|
configFiles: configFiles,
|
|
users: users,
|
|
wg: wg,
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
// SendPeerEmail sends an email to the user linked to the given peers.
|
|
func (m Manager) SendPeerEmail(ctx context.Context, linkOnly bool, peers ...domain.PeerIdentifier) error {
|
|
for _, peerId := range peers {
|
|
peer, err := m.wg.GetPeer(ctx, peerId)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch peer %s: %w", peerId, err)
|
|
}
|
|
|
|
if err := domain.ValidateUserAccessRights(ctx, peer.UserIdentifier); err != nil {
|
|
return err
|
|
}
|
|
|
|
if peer.UserIdentifier == "" {
|
|
slog.Debug("skipping peer email",
|
|
"peer", peerId,
|
|
"reason", "no user linked")
|
|
continue
|
|
}
|
|
|
|
user, err := m.users.GetUser(ctx, peer.UserIdentifier)
|
|
if err != nil {
|
|
slog.Debug("skipping peer email",
|
|
"peer", peerId,
|
|
"reason", "unable to fetch user",
|
|
"error", err)
|
|
continue
|
|
}
|
|
|
|
if user.Email == "" {
|
|
slog.Debug("skipping peer email",
|
|
"peer", peerId,
|
|
"reason", "user has no mail address")
|
|
continue
|
|
}
|
|
|
|
err = m.sendPeerEmail(ctx, linkOnly, user, peer)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to send peer email for %s: %w", peerId, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m Manager) sendPeerEmail(ctx context.Context, linkOnly bool, user *domain.User, peer *domain.Peer) error {
|
|
qrName := "WireGuardQRCode.png"
|
|
configName := peer.GetConfigFileName()
|
|
|
|
var (
|
|
txtMail, htmlMail io.Reader
|
|
err error
|
|
mailOptions domain.MailOptions
|
|
)
|
|
if linkOnly {
|
|
txtMail, htmlMail, err = m.tplHandler.GetConfigMail(user, "deep link TBD")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get mail body: %w", err)
|
|
}
|
|
|
|
} else {
|
|
peerConfig, err := m.configFiles.GetPeerConfig(ctx, peer.Identifier)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch peer config for %s: %w", peer.Identifier, err)
|
|
}
|
|
|
|
peerConfigQr, err := m.configFiles.GetPeerConfigQrCode(ctx, peer.Identifier)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch peer config QR code for %s: %w", peer.Identifier, err)
|
|
}
|
|
|
|
txtMail, htmlMail, err = m.tplHandler.GetConfigMailWithAttachment(user, configName, qrName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get full mail body: %w", err)
|
|
}
|
|
|
|
mailOptions.Attachments = append(mailOptions.Attachments, domain.MailAttachment{
|
|
Name: configName,
|
|
ContentType: "text/plain",
|
|
Data: peerConfig,
|
|
Embedded: false,
|
|
})
|
|
mailOptions.Attachments = append(mailOptions.Attachments, domain.MailAttachment{
|
|
Name: qrName,
|
|
ContentType: "image/png",
|
|
Data: peerConfigQr,
|
|
Embedded: true,
|
|
})
|
|
}
|
|
|
|
txtMailStr, _ := io.ReadAll(txtMail)
|
|
htmlMailStr, _ := io.ReadAll(htmlMail)
|
|
mailOptions.HtmlBody = string(htmlMailStr)
|
|
|
|
err = m.mailer.Send(ctx, "WireGuard VPN Configuration", string(txtMailStr), []string{user.Email}, &mailOptions)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to send mail: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|