This commit is contained in:
parent
7abe44a59d
commit
dfd27a34b6
@ -684,6 +684,7 @@ func legacySMTPConfig(root string, vault *credential.Vault, legacy legacyMigrati
|
||||
RelayPort: legacy.SMTPPort,
|
||||
Username: legacy.SMTPUsername,
|
||||
Security: legacy.SMTPSecurity,
|
||||
From: legacy.SMTPFrom,
|
||||
FromDomain: legacy.SMTPFromDomain,
|
||||
VerificationEnabled: legacy.VerificationEnabled,
|
||||
LoginPolicy: legacyLoginPolicy(legacy),
|
||||
|
||||
@ -87,6 +87,7 @@ type SMTPConfig struct {
|
||||
Username string `json:"username"`
|
||||
Password StoredSecret `json:"password,omitempty"`
|
||||
Security string `json:"security"`
|
||||
From string `json:"from"`
|
||||
FromDomain string `json:"fromDomain"`
|
||||
VerificationEnabled bool `json:"verificationEnabled"`
|
||||
LoginPolicy string `json:"loginPolicy"`
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"git.pcux.cn/xuans/Smail/utils/credential"
|
||||
)
|
||||
|
||||
// publishedConfig 是旧配置模型的只读兼容快照。
|
||||
@ -124,6 +127,56 @@ type MailPorts struct {
|
||||
POP3 int
|
||||
}
|
||||
|
||||
type SMTPRuntimeSettings struct {
|
||||
Relay SMTPConfig
|
||||
Password string
|
||||
VerificationEnabled bool
|
||||
ExternalRecoveryEnabled bool
|
||||
}
|
||||
|
||||
func CurrentSMTPRuntimeSettings() (SMTPRuntimeSettings, error) {
|
||||
settings := SMTPRuntimeSettings{}
|
||||
if manager := CurrentManager(); manager != nil {
|
||||
runtime := manager.Snapshot()
|
||||
if runtime.Generation != 0 {
|
||||
settings.Relay = runtime.SMTP.Relay
|
||||
settings.ExternalRecoveryEnabled = runtime.Email.Policy.ExternalRecoveryEnabled
|
||||
if settings.Relay.Username != "" {
|
||||
vault, err := credential.NewVault(ROOT_PATH)
|
||||
if err != nil {
|
||||
return SMTPRuntimeSettings{}, fmt.Errorf("create SMTP credential vault: %w", err)
|
||||
}
|
||||
password, err := OpenStoredSecret(vault, settings.Relay.Password)
|
||||
if err != nil {
|
||||
return SMTPRuntimeSettings{}, fmt.Errorf("open SMTP relay password: %w", err)
|
||||
}
|
||||
settings.Password = password
|
||||
}
|
||||
settings.VerificationEnabled = settings.Relay.VerificationEnabled
|
||||
return settings, nil
|
||||
}
|
||||
}
|
||||
|
||||
cfg := CurrentConfig()
|
||||
if cfg == nil {
|
||||
return SMTPRuntimeSettings{}, fmt.Errorf("runtime configuration is unavailable")
|
||||
}
|
||||
settings.Relay = SMTPConfig{
|
||||
RelayHost: cfg.SMTPHost,
|
||||
RelayPort: cfg.SMTPPort,
|
||||
Username: cfg.SMTPUsername,
|
||||
Security: cfg.SMTPSecurity,
|
||||
From: cfg.SMTPFrom,
|
||||
FromDomain: cfg.SMTPFromDomain,
|
||||
VerificationEnabled: cfg.VerificationEnabled,
|
||||
LoginPolicy: cfg.LoginPolicy,
|
||||
}
|
||||
settings.Password = cfg.SMTPPassword
|
||||
settings.VerificationEnabled = cfg.VerificationEnabled
|
||||
settings.ExternalRecoveryEnabled = cfg.ExternalRecoveryEnabled
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
func CurrentMailPorts() MailPorts {
|
||||
ports := MailPorts{SMTP: 25, IMAP: 143, POP3: 110}
|
||||
manager := CurrentManager()
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
package config
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.pcux.cn/xuans/Smail/utils/credential"
|
||||
)
|
||||
|
||||
type runtimeAccessSnapshotStore struct {
|
||||
runtime RuntimeConfig
|
||||
@ -123,3 +128,106 @@ func TestCurrentMailPortsUsesDefaultsForUnavailableSnapshot(t *testing.T) {
|
||||
t.Fatalf("mail ports = %+v, want defaults for unavailable snapshot", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentSMTPRuntimeSettingsReadsPartitionSnapshot(t *testing.T) {
|
||||
previousManager := CurrentManager()
|
||||
defer SetRuntimeManager(previousManager)
|
||||
|
||||
runtime := validRuntimeConfig()
|
||||
runtime.SMTP.Relay = SMTPConfig{
|
||||
RelayHost: "smtp.example.com",
|
||||
RelayPort: 587,
|
||||
Security: "starttls",
|
||||
From: "no-reply@example.com",
|
||||
FromDomain: "example.com",
|
||||
VerificationEnabled: true,
|
||||
LoginPolicy: "all",
|
||||
}
|
||||
runtime.Email.Policy.ExternalRecoveryEnabled = true
|
||||
SetRuntimeManager(&ConfigManager{snapshots: runtimeAccessSnapshotStore{runtime: runtime}})
|
||||
|
||||
got, err := CurrentSMTPRuntimeSettings()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.Relay.RelayHost != "smtp.example.com" || got.Relay.RelayPort != 587 || got.Relay.From != "no-reply@example.com" {
|
||||
t.Fatalf("SMTP relay settings = %+v, want partition snapshot", got)
|
||||
}
|
||||
if !got.VerificationEnabled || !got.ExternalRecoveryEnabled {
|
||||
t.Fatalf("SMTP security flags = %+v, want enabled", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentSMTPRuntimeSettingsOpensPartitionPassword(t *testing.T) {
|
||||
previousManager := CurrentManager()
|
||||
previousRoot := ROOT_PATH
|
||||
defer func() {
|
||||
SetRuntimeManager(previousManager)
|
||||
ROOT_PATH = previousRoot
|
||||
}()
|
||||
|
||||
ROOT_PATH = t.TempDir()
|
||||
vault, err := credential.NewVault(ROOT_PATH)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ciphertext, err := vault.Encrypt("relay-password")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := validRuntimeConfig()
|
||||
runtime.SMTP.Relay = SMTPConfig{
|
||||
RelayHost: "smtp.example.com",
|
||||
RelayPort: 465,
|
||||
Username: "relay-user",
|
||||
Password: StoredSecret{Ciphertext: ciphertext, Version: vault.Version(), UpdatedAt: time.Now().UTC()},
|
||||
Security: "tls",
|
||||
From: "no-reply@example.com",
|
||||
FromDomain: "example.com",
|
||||
VerificationEnabled: true,
|
||||
LoginPolicy: "all",
|
||||
}
|
||||
SetRuntimeManager(&ConfigManager{snapshots: runtimeAccessSnapshotStore{runtime: runtime}})
|
||||
|
||||
got, err := CurrentSMTPRuntimeSettings()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.Password != "relay-password" {
|
||||
t.Fatalf("SMTP relay password = %q, want decrypted password", got.Password)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentSMTPRuntimeSettingsFallsBackToLegacyConfig(t *testing.T) {
|
||||
previousManager := CurrentManager()
|
||||
previousInstance := Instance
|
||||
defer func() {
|
||||
SetRuntimeManager(previousManager)
|
||||
Instance = previousInstance
|
||||
PublishConfigSnapshot(previousInstance)
|
||||
}()
|
||||
SetRuntimeManager(nil)
|
||||
|
||||
Instance = &Config{
|
||||
Domain: "example.com",
|
||||
SMTPHost: "smtp.example.com",
|
||||
SMTPPort: 587,
|
||||
SMTPUsername: "relay-user",
|
||||
SMTPPassword: "relay-password",
|
||||
SMTPSecurity: "starttls",
|
||||
SMTPFrom: "no-reply@example.com",
|
||||
SMTPFromDomain: "example.com",
|
||||
VerificationEnabled: true,
|
||||
ExternalRecoveryEnabled: true,
|
||||
LoginPolicy: "all",
|
||||
}
|
||||
PublishConfigSnapshot(Instance)
|
||||
|
||||
got, err := CurrentSMTPRuntimeSettings()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.Password != "relay-password" || got.Relay.Username != "relay-user" || got.Relay.From != "no-reply@example.com" {
|
||||
t.Fatalf("legacy SMTP settings = %+v, password=%q", got.Relay, got.Password)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package config
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/mail"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@ -102,6 +103,12 @@ func validateSMTP(smtp SMTPConfig) error {
|
||||
if !validLoginVerificationPolicy(smtp.LoginPolicy) {
|
||||
return ErrInvalidSMTP
|
||||
}
|
||||
if smtp.From != "" {
|
||||
parsedFrom, err := mail.ParseAddress(smtp.From)
|
||||
if err != nil || parsedFrom.Address != smtp.From || strings.ContainsAny(smtp.From, "\r\n") {
|
||||
return ErrInvalidSMTP
|
||||
}
|
||||
}
|
||||
if !smtp.VerificationEnabled {
|
||||
if smtp.RelayHost != "" && !validSMTPHost(smtp.RelayHost) {
|
||||
return ErrInvalidSMTP
|
||||
@ -136,7 +143,7 @@ func validateSMTP(smtp SMTPConfig) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !validSMTPHost(smtp.RelayHost) || !validPort(smtp.RelayPort) || !validCanonicalDomain(smtp.FromDomain) {
|
||||
if !validSMTPHost(smtp.RelayHost) || !validPort(smtp.RelayPort) || !validCanonicalDomain(smtp.FromDomain) || smtp.From == "" {
|
||||
return ErrInvalidSMTP
|
||||
}
|
||||
if strings.TrimSpace(smtp.Username) != smtp.Username {
|
||||
|
||||
@ -443,6 +443,7 @@ func publicConfigSection(runtime config.RuntimeConfig, section config.Section) (
|
||||
"relayPort": runtime.SMTP.Relay.RelayPort,
|
||||
"username": runtime.SMTP.Relay.Username,
|
||||
"security": runtime.SMTP.Relay.Security,
|
||||
"from": runtime.SMTP.Relay.From,
|
||||
"fromDomain": runtime.SMTP.Relay.FromDomain,
|
||||
"verificationEnabled": runtime.SMTP.Relay.VerificationEnabled,
|
||||
"loginPolicy": runtime.SMTP.Relay.LoginPolicy,
|
||||
|
||||
@ -283,17 +283,12 @@ func startVerificationRuntime() error {
|
||||
runtime.Stop()
|
||||
return fmt.Errorf("start external email repository: %w", err)
|
||||
}
|
||||
cfg := config.CurrentConfig()
|
||||
if cfg == nil {
|
||||
smtpSettings, err := config.CurrentSMTPRuntimeSettings()
|
||||
if err != nil {
|
||||
runtime.Stop()
|
||||
return fmt.Errorf("runtime configuration is unavailable")
|
||||
return err
|
||||
}
|
||||
externalRecoveryEnabled := cfg.ExternalRecoveryEnabled
|
||||
if manager := config.CurrentManager(); manager != nil {
|
||||
runtimeConfig := manager.Snapshot()
|
||||
externalRecoveryEnabled = runtimeConfig.Email.Policy.ExternalRecoveryEnabled
|
||||
}
|
||||
if !cfg.VerificationEnabled && !externalRecoveryEnabled {
|
||||
if !smtpSettings.VerificationEnabled && !smtpSettings.ExternalRecoveryEnabled {
|
||||
recover, recoverErr := recovery.NewService(db.Instance, nil, external, credential)
|
||||
if recoverErr != nil {
|
||||
runtime.Stop()
|
||||
@ -306,19 +301,19 @@ func startVerificationRuntime() error {
|
||||
log.Info("邮箱验证码服务已禁用")
|
||||
return nil
|
||||
}
|
||||
security, err := smtpSecurityMode(cfg.SMTPSecurity)
|
||||
security, err := smtpSecurityMode(smtpSettings.Relay.Security)
|
||||
if err != nil {
|
||||
runtime.Stop()
|
||||
return err
|
||||
}
|
||||
delivery, err := verification.NewSMTPDelivery(verification.SMTPSettings{
|
||||
Host: cfg.SMTPHost,
|
||||
Port: cfg.SMTPPort,
|
||||
Username: cfg.SMTPUsername,
|
||||
Password: cfg.SMTPPassword,
|
||||
Host: smtpSettings.Relay.RelayHost,
|
||||
Port: smtpSettings.Relay.RelayPort,
|
||||
Username: smtpSettings.Relay.Username,
|
||||
Password: smtpSettings.Password,
|
||||
Security: security,
|
||||
From: cfg.SMTPFrom,
|
||||
FromDomain: cfg.SMTPFromDomain,
|
||||
From: smtpSettings.Relay.From,
|
||||
FromDomain: smtpSettings.Relay.FromDomain,
|
||||
})
|
||||
if err != nil {
|
||||
runtime.Stop()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user