fixed settings smtp password clear persistence

This commit is contained in:
Gani Georgiev
2026-04-05 13:46:46 +03:00
parent e9118fa6b6
commit d92a98b100
3 changed files with 102 additions and 1 deletions

View File

@@ -327,6 +327,8 @@ func (s *Settings) MarshalJSON() ([]byte, error) {
copy := s.settings
s.mu.RUnlock()
copy.SMTP.hidePassword = true
sensitiveFields := []*string{
&copy.SMTP.Password,
&copy.S3.Secret,
@@ -346,6 +348,12 @@ func (s *Settings) MarshalJSON() ([]byte, error) {
// -------------------------------------------------------------------
type SMTPConfig struct {
// @todo temp workaround to avoid introducing breaking changes;
// consider refactoring and/or normalizing with the other Settings sensitive fields
//
// hidePassword specifies whether to hide the password field from the struct JSON serialization.
hidePassword bool
Enabled bool `form:"enabled" json:"enabled"`
Port int `form:"port" json:"port"`
Host string `form:"host" json:"host"`
@@ -392,6 +400,27 @@ func (c SMTPConfig) Validate() error {
)
}
// MarshalJSON implements the [json.Marshaler] interface.
func (c SMTPConfig) MarshalJSON() ([]byte, error) {
type alias SMTPConfig
if c.hidePassword {
v := struct {
alias
Password string `json:"password,omitempty"`
}{alias(c), ""}
return json.Marshal(v)
}
v := struct {
alias
Password string `json:"password"`
}{alias(c), c.Password}
return json.Marshal(v)
}
// -------------------------------------------------------------------
type S3Config struct {

View File

@@ -3,6 +3,7 @@ package core_test
import (
"encoding/json"
"fmt"
"os"
"strings"
"testing"
"time"
@@ -10,6 +11,7 @@ import (
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tests"
"github.com/pocketbase/pocketbase/tools/mailer"
"github.com/pocketbase/pocketbase/tools/security"
)
func TestSettingsDelete(t *testing.T) {
@@ -24,6 +26,72 @@ func TestSettingsDelete(t *testing.T) {
}
}
func TestSettings_DBExport(t *testing.T) {
scenarios := []struct {
name string
encryption bool
}{
{"no encryption", false},
{"with encryption", true},
}
encryptionKey := strings.Repeat("a", 32)
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
app, _ := tests.NewTestApp()
defer app.Cleanup()
originalEnv := os.Getenv(app.EncryptionEnv())
defer func() {
os.Setenv(app.EncryptionEnv(), originalEnv)
}()
settings := &core.Settings{}
settings.Meta.AppName = "test_app_name"
settings.Logs.MaxDays = 123
settings.SMTP.Host = "smtp_host"
settings.SMTP.Username = "smtp_username"
settings.SMTP.Password = "" // ensures that empty password is exported
settings.S3.Endpoint = "s3_endpoint"
settings.S3.Secret = "s3_secret"
settings.Backups.Cron = "* * * * *"
settings.Backups.S3.Enabled = true
settings.Backups.S3.Secret = ""
settings.Batch.Timeout = 15
settings.RateLimits.Enabled = true
settings.TrustedProxy.UseLeftmostIP = true
if s.encryption {
os.Setenv(app.EncryptionEnv(), encryptionKey)
}
export, err := settings.DBExport(app)
if err != nil {
t.Fatal(err)
}
var valueStr string
if s.encryption {
decrypted, err := security.Decrypt(export["value"].(string), encryptionKey)
if err != nil {
t.Fatalf("failed to decrypt test value: %v", err)
}
valueStr = string(decrypted)
} else {
valueStr = string(export["value"].([]byte))
}
expected := `{"smtp":{"enabled":false,"port":0,"host":"smtp_host","username":"smtp_username","authMethod":"","tls":false,"localName":"","password":""},"backups":{"cron":"* * * * *","cronMaxKeep":0,"s3":{"enabled":true,"bucket":"","region":"","endpoint":"","accessKey":"","forcePathStyle":false}},"s3":{"enabled":false,"bucket":"","region":"","endpoint":"s3_endpoint","accessKey":"","secret":"s3_secret","forcePathStyle":false},"meta":{"appName":"test_app_name","appURL":"","senderName":"","senderAddress":"","hideControls":false},"rateLimits":{"rules":[],"enabled":true},"trustedProxy":{"headers":[],"useLeftmostIP":true},"batch":{"enabled":false,"maxRequests":0,"timeout":15,"maxBodySize":0},"logs":{"maxDays":123,"minLevel":0,"logIP":false,"logAuthId":false}}`
if valueStr != expected {
t.Fatalf("Expected exported settings\n%s\ngot\n%s", expected, valueStr)
}
})
}
}
func TestSettingsMerge(t *testing.T) {
s1 := &core.Settings{}
s1.Meta.AppURL = "app_url" // should be unset