add support for sending emails to peers without linked user accounts if their user-identifier is a valid email address

This commit is contained in:
Christoph Haas
2025-10-12 14:31:01 +02:00
parent c7724b620a
commit 298c9405f6
3 changed files with 52 additions and 20 deletions

View File

@@ -72,6 +72,7 @@ mail:
auth_type: plain auth_type: plain
from: Wireguard Portal <noreply@wireguard.local> from: Wireguard Portal <noreply@wireguard.local>
link_only: false link_only: false
allow_peer_email: false
auth: auth:
oidc: [] oidc: []
@@ -380,7 +381,9 @@ Controls how WireGuard Portal collects and reports usage statistics, including p
## Mail ## Mail
Options for configuring email notifications or sending peer configurations via email. Options for configuring email notifications or sending peer configurations via email.
By default, emails will only be sent to peers that have a valid user record linked.
To send emails to all peers that have a valid email-address as user-identifier, set `allow_peer_email` to `true`.
### `host` ### `host`
- **Default:** `127.0.0.1` - **Default:** `127.0.0.1`
@@ -418,6 +421,12 @@ Options for configuring email notifications or sending peer configurations via e
- **Default:** `false` - **Default:** `false`
- **Description:** If `true`, emails only contain a link to WireGuard Portal, rather than attaching the full configuration. - **Description:** If `true`, emails only contain a link to WireGuard Portal, rather than attaching the full configuration.
### `allow_peer_email`
- **Default:** `false`
- **Description:** If `true`, and a peer has no valid user record linked, but the user-identifier of the peer is a valid email address, emails will be sent to that email address.
If false, and the peer has no valid user record linked, emails will not be sent.
If a peer has linked a valid user, the email address is always taken from the user record.
--- ---
## Auth ## Auth

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io" "io"
"log/slog" "log/slog"
"net/mail"
"github.com/h44z/wg-portal/internal/config" "github.com/h44z/wg-portal/internal/config"
"github.com/h44z/wg-portal/internal/domain" "github.com/h44z/wg-portal/internal/domain"
@@ -101,29 +102,15 @@ func (m Manager) SendPeerEmail(ctx context.Context, linkOnly bool, style string,
} }
if peer.UserIdentifier == "" { if peer.UserIdentifier == "" {
slog.Debug("skipping peer email", return fmt.Errorf("peer %s has no user linked, no email is sent", peerId)
"peer", peerId,
"reason", "no user linked")
continue
} }
user, err := m.users.GetUser(ctx, peer.UserIdentifier) email, user := m.resolveEmail(ctx, peer)
if err != nil { if email == "" {
slog.Debug("skipping peer email", return fmt.Errorf("peer %s has no valid email address, no email is sent", peerId)
"peer", peerId,
"reason", "unable to fetch user",
"error", err)
continue
} }
if user.Email == "" { err = m.sendPeerEmail(ctx, linkOnly, style, &user, peer)
slog.Debug("skipping peer email",
"peer", peerId,
"reason", "user has no mail address")
continue
}
err = m.sendPeerEmail(ctx, linkOnly, style, user, peer)
if err != nil { if err != nil {
return fmt.Errorf("failed to send peer email for %s: %w", peerId, err) return fmt.Errorf("failed to send peer email for %s: %w", peerId, err)
} }
@@ -194,3 +181,37 @@ func (m Manager) sendPeerEmail(
return nil return nil
} }
func (m Manager) resolveEmail(ctx context.Context, peer *domain.Peer) (string, domain.User) {
user, err := m.users.GetUser(ctx, peer.UserIdentifier)
if err != nil {
if m.cfg.Mail.AllowPeerEmail {
_, err := mail.ParseAddress(string(peer.UserIdentifier)) // test if the user identifier is a valid email address
if err == nil {
slog.Debug("peer email: using user-identifier as email",
"peer", peer.Identifier, "email", peer.UserIdentifier)
return string(peer.UserIdentifier), domain.User{}
} else {
slog.Debug("peer email: skipping peer email",
"peer", peer.Identifier,
"reason", "peer has no user linked and user-identifier is not a valid email address")
return "", domain.User{}
}
} else {
slog.Debug("peer email: skipping peer email",
"peer", peer.Identifier,
"reason", "user has no user linked")
return "", domain.User{}
}
}
if user.Email == "" {
slog.Debug("peer email: skipping peer email",
"peer", peer.Identifier,
"reason", "user has no mail address")
return "", domain.User{}
}
slog.Debug("peer email: using user email", "peer", peer.Identifier, "email", user.Email)
return user.Email, *user
}

View File

@@ -41,4 +41,6 @@ type MailConfig struct {
From string `yaml:"from"` From string `yaml:"from"`
// LinkOnly specifies whether emails should only contain a link to WireGuard Portal or attach the full configuration // LinkOnly specifies whether emails should only contain a link to WireGuard Portal or attach the full configuration
LinkOnly bool `yaml:"link_only"` LinkOnly bool `yaml:"link_only"`
// AllowPeerEmail specifies whether emails should be sent to peers which have no valid user account linked, but an email address is set as "user".
AllowPeerEmail bool `yaml:"allow_peer_email"`
} }