Compare commits

...

3 commits

Author SHA1 Message Date
Nayan Thulkar 40b5a883f5
Merge 9b83ebb700 into 550cd3e1f8 2024-09-12 00:21:16 +05:30
Bishop Clark 550cd3e1f8
Update README.md (#2034)
'software', when used as a noun, is not a 'countable' type, and it does not get an article like 'a'.  It's like 'traffic'.
2024-09-06 15:49:53 +05:30
nayanthulkar28 9b83ebb700 added custom validator and validation to put settings api 2024-07-02 18:12:55 +05:30
7 changed files with 126 additions and 73 deletions

View file

@ -48,7 +48,7 @@ __________________
## Developers
listmonk is a free and open source software licensed under AGPLv3. If you are interested in contributing, refer to the [developer setup](https://listmonk.app/docs/developer-setup). The backend is written in Go and the frontend is Vue with Buefy for UI.
listmonk is free and open source software licensed under AGPLv3. If you are interested in contributing, refer to the [developer setup](https://listmonk.app/docs/developer-setup). The backend is written in Go and the frontend is Vue with Buefy for UI.
## License

View file

@ -18,6 +18,7 @@ import (
"github.com/Masterminds/sprig/v3"
"github.com/gdgvda/cron"
"github.com/go-playground/validator/v10"
"github.com/jmoiron/sqlx"
"github.com/jmoiron/sqlx/types"
"github.com/knadh/goyesql/v2"
@ -789,6 +790,8 @@ func initHTTPServer(app *App) *echo.Echo {
srv.Static(ko.String("upload.filesystem.upload_uri"), ko.String("upload.filesystem.upload_path"))
}
srv.Validator = &CustomValidator{Validator: validator.New()}
// Register all HTTP handlers.
initHTTPHandlers(srv, app)

View file

@ -13,6 +13,7 @@ import (
"github.com/gdgvda/cron"
"github.com/gofrs/uuid/v5"
"github.com/jinzhu/copier"
"github.com/jmoiron/sqlx/types"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/providers/rawbytes"
@ -78,12 +79,16 @@ func handleGetSettings(c echo.Context) error {
// handleUpdateSettings returns settings from the DB.
func handleUpdateSettings(c echo.Context) error {
var (
app = c.Get("app").(*App)
set models.Settings
app = c.Get("app").(*App)
req, set models.Settings
)
// Unmarshal and marshal the fields once to sanitize the settings blob.
if err := c.Bind(&set); err != nil {
if err := c.Bind(&req); err != nil {
return err
}
if err := c.Validate(req); err != nil {
return err
}
@ -93,10 +98,13 @@ func handleUpdateSettings(c echo.Context) error {
return err
}
set = cur
copier.CopyWithOption(&set, &req, copier.Option{IgnoreEmpty: true})
// There should be at least one SMTP block that's enabled.
has := false
for i, s := range set.SMTP {
if s.Enabled {
if *s.Enabled {
has = true
}
@ -157,6 +165,24 @@ func handleUpdateSettings(c echo.Context) error {
}
}
// handle bounce postmark
if set.BouncePostmark.Enabled == nil {
set.BouncePostmark.Enabled = cur.BouncePostmark.Enabled
}
if set.BouncePostmark.Username == "" {
set.BouncePostmark.Username = cur.BouncePostmark.Username
}
if set.BouncePostmark.Password == "" {
set.BouncePostmark.Password = cur.BouncePostmark.Password
}
// handle bounce actions
for name, action := range cur.BounceActions {
if _, ok := set.BounceActions[name]; !ok {
set.BounceActions[name] = action
}
}
// Validate and sanitize postback Messenger names. Duplicates are disallowed
// and "email" is a reserved name.
names := map[string]bool{emailMsgr: true}
@ -188,20 +214,6 @@ func handleUpdateSettings(c echo.Context) error {
names[name] = true
}
// S3 password?
if set.UploadS3AwsSecretAccessKey == "" {
set.UploadS3AwsSecretAccessKey = cur.UploadS3AwsSecretAccessKey
}
if set.SendgridKey == "" {
set.SendgridKey = cur.SendgridKey
}
if set.BouncePostmark.Password == "" {
set.BouncePostmark.Password = cur.BouncePostmark.Password
}
if set.SecurityCaptchaSecret == "" {
set.SecurityCaptchaSecret = cur.SecurityCaptchaSecret
}
for n, v := range set.UploadExtensions {
set.UploadExtensions[n] = strings.ToLower(strings.TrimPrefix(strings.TrimSpace(v), "."))
}
@ -217,7 +229,7 @@ func handleUpdateSettings(c echo.Context) error {
set.DomainBlocklist = doms
// Validate slow query caching cron.
if set.CacheSlowQueries {
if *set.CacheSlowQueries {
if _, err := cron.ParseStandard(set.CacheSlowQueriesInterval); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidData")+": slow query cron: "+err.Error())
}

19
cmd/validator.go Normal file
View file

@ -0,0 +1,19 @@
package main
import (
"net/http"
"github.com/go-playground/validator/v10"
"github.com/labstack/echo/v4"
)
type CustomValidator struct {
Validator *validator.Validate
}
func (c *CustomValidator) Validate(i interface{}) error {
if err := c.Validator.Struct(i); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
return nil
}

6
go.mod
View file

@ -7,8 +7,10 @@ require (
github.com/disintegration/imaging v1.6.2
github.com/emersion/go-message v0.16.0
github.com/gdgvda/cron v0.2.0
github.com/go-playground/validator/v10 v10.22.0
github.com/gofrs/uuid/v5 v5.0.0
github.com/gorilla/feeds v1.1.1
github.com/jinzhu/copier v0.4.0
github.com/jmoiron/sqlx v1.3.5
github.com/knadh/go-pop3 v0.3.0
github.com/knadh/goyesql/v2 v2.2.0
@ -40,6 +42,9 @@ require (
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
@ -47,6 +52,7 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect

13
go.sum
View file

@ -18,8 +18,17 @@ github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gdgvda/cron v0.2.0 h1:oX8qdLZq4tC5StnCsZsTNs2BIzaRjcjmPZ4o+BArKX4=
github.com/gdgvda/cron v0.2.0/go.mod h1:VEwidZXB255kESB5DcUGRWTYZS8KkOBYD1YBn8Wiyx8=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@ -39,6 +48,8 @@ github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
@ -80,6 +91,8 @@ github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zG
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=

View file

@ -8,35 +8,35 @@ type Settings struct {
AppFaviconURL string `json:"app.favicon_url"`
AppFromEmail string `json:"app.from_email"`
AppNotifyEmails []string `json:"app.notify_emails"`
EnablePublicSubPage bool `json:"app.enable_public_subscription_page"`
EnablePublicArchive bool `json:"app.enable_public_archive"`
EnablePublicArchiveRSSContent bool `json:"app.enable_public_archive_rss_content"`
SendOptinConfirmation bool `json:"app.send_optin_confirmation"`
CheckUpdates bool `json:"app.check_updates"`
EnablePublicSubPage *bool `json:"app.enable_public_subscription_page"`
EnablePublicArchive *bool `json:"app.enable_public_archive"`
EnablePublicArchiveRSSContent *bool `json:"app.enable_public_archive_rss_content"`
SendOptinConfirmation *bool `json:"app.send_optin_confirmation"`
CheckUpdates *bool `json:"app.check_updates"`
AppLang string `json:"app.lang"`
AppBatchSize int `json:"app.batch_size"`
AppConcurrency int `json:"app.concurrency"`
AppMaxSendErrors int `json:"app.max_send_errors"`
AppMessageRate int `json:"app.message_rate"`
CacheSlowQueries bool `json:"app.cache_slow_queries"`
CacheSlowQueries *bool `json:"app.cache_slow_queries"`
CacheSlowQueriesInterval string `json:"app.cache_slow_queries_interval"`
AppMessageSlidingWindow bool `json:"app.message_sliding_window"`
AppMessageSlidingWindow *bool `json:"app.message_sliding_window"`
AppMessageSlidingWindowDuration string `json:"app.message_sliding_window_duration"`
AppMessageSlidingWindowRate int `json:"app.message_sliding_window_rate"`
PrivacyIndividualTracking bool `json:"privacy.individual_tracking"`
PrivacyUnsubHeader bool `json:"privacy.unsubscribe_header"`
PrivacyAllowBlocklist bool `json:"privacy.allow_blocklist"`
PrivacyAllowPreferences bool `json:"privacy.allow_preferences"`
PrivacyAllowExport bool `json:"privacy.allow_export"`
PrivacyAllowWipe bool `json:"privacy.allow_wipe"`
PrivacyIndividualTracking *bool `json:"privacy.individual_tracking"`
PrivacyUnsubHeader *bool `json:"privacy.unsubscribe_header"`
PrivacyAllowBlocklist *bool `json:"privacy.allow_blocklist"`
PrivacyAllowPreferences *bool `json:"privacy.allow_preferences"`
PrivacyAllowExport *bool `json:"privacy.allow_export"`
PrivacyAllowWipe *bool `json:"privacy.allow_wipe"`
PrivacyExportable []string `json:"privacy.exportable"`
PrivacyRecordOptinIP bool `json:"privacy.record_optin_ip"`
PrivacyRecordOptinIP *bool `json:"privacy.record_optin_ip"`
DomainBlocklist []string `json:"privacy.domain_blocklist"`
SecurityEnableCaptcha bool `json:"security.enable_captcha"`
SecurityEnableCaptcha *bool `json:"security.enable_captcha"`
SecurityCaptchaKey string `json:"security.captcha_key"`
SecurityCaptchaSecret string `json:"security.captcha_secret"`
@ -57,62 +57,62 @@ type Settings struct {
SMTP []struct {
UUID string `json:"uuid"`
Enabled bool `json:"enabled"`
Host string `json:"host"`
Enabled *bool `json:"enabled" validate:"required"`
Host string `json:"host" validate:"required"`
HelloHostname string `json:"hello_hostname"`
Port int `json:"port"`
AuthProtocol string `json:"auth_protocol"`
Username string `json:"username"`
Port int `json:"port" validate:"required"`
AuthProtocol string `json:"auth_protocol" validate:"required"`
Username string `json:"username" validate:"required"`
Password string `json:"password,omitempty"`
EmailHeaders []map[string]string `json:"email_headers"`
MaxConns int `json:"max_conns"`
MaxMsgRetries int `json:"max_msg_retries"`
IdleTimeout string `json:"idle_timeout"`
WaitTimeout string `json:"wait_timeout"`
TLSType string `json:"tls_type"`
TLSSkipVerify bool `json:"tls_skip_verify"`
} `json:"smtp"`
MaxConns int `json:"max_conns" validate:"required"`
MaxMsgRetries int `json:"max_msg_retries" validate:"required"`
IdleTimeout string `json:"idle_timeout" validate:"required"`
WaitTimeout string `json:"wait_timeout" validate:"required"`
TLSType string `json:"tls_type" validate:"required"`
TLSSkipVerify *bool `json:"tls_skip_verify" validate:"required"`
} `json:"smtp" validate:"omitempty,dive"`
Messengers []struct {
UUID string `json:"uuid"`
Enabled bool `json:"enabled"`
Enabled *bool `json:"enabled" validate:"required"`
Name string `json:"name"`
RootURL string `json:"root_url"`
Username string `json:"username"`
RootURL string `json:"root_url" validate:"required"`
Username string `json:"username" validate:"required"`
Password string `json:"password,omitempty"`
MaxConns int `json:"max_conns"`
Timeout string `json:"timeout"`
MaxMsgRetries int `json:"max_msg_retries"`
} `json:"messengers"`
MaxConns int `json:"max_conns" validate:"required"`
Timeout string `json:"timeout" validate:"required"`
MaxMsgRetries int `json:"max_msg_retries" validate:"required"`
} `json:"messengers" validate:"omitempty,dive"`
BounceEnabled bool `json:"bounce.enabled"`
BounceEnableWebhooks bool `json:"bounce.webhooks_enabled"`
BounceEnabled *bool `json:"bounce.enabled"`
BounceEnableWebhooks *bool `json:"bounce.webhooks_enabled"`
BounceActions map[string]struct {
Count int `json:"count"`
Action string `json:"action"`
} `json:"bounce.actions"`
SESEnabled bool `json:"bounce.ses_enabled"`
SendgridEnabled bool `json:"bounce.sendgrid_enabled"`
Count int `json:"count" validate:"required"`
Action string `json:"action" validate:"required"`
} `json:"bounce.actions" validate:"omitempty,dive"`
SESEnabled *bool `json:"bounce.ses_enabled"`
SendgridEnabled *bool `json:"bounce.sendgrid_enabled"`
SendgridKey string `json:"bounce.sendgrid_key"`
BouncePostmark struct {
Enabled bool `json:"enabled"`
Enabled *bool `json:"enabled"`
Username string `json:"username"`
Password string `json:"password"`
} `json:"bounce.postmark"`
BounceBoxes []struct {
UUID string `json:"uuid"`
Enabled bool `json:"enabled"`
Type string `json:"type"`
Host string `json:"host"`
Port int `json:"port"`
AuthProtocol string `json:"auth_protocol"`
ReturnPath string `json:"return_path"`
Username string `json:"username"`
Enabled *bool `json:"enabled" validate:"required"`
Type string `json:"type" validate:"required"`
Host string `json:"host" validate:"required"`
Port int `json:"port" validate:"required"`
AuthProtocol string `json:"auth_protocol" validate:"required"`
ReturnPath string `json:"return_path" validate:"required"`
Username string `json:"username" validate:"required"`
Password string `json:"password,omitempty"`
TLSEnabled bool `json:"tls_enabled"`
TLSSkipVerify bool `json:"tls_skip_verify"`
ScanInterval string `json:"scan_interval"`
} `json:"bounce.mailboxes"`
TLSEnabled *bool `json:"tls_enabled" validate:"required"`
TLSSkipVerify *bool `json:"tls_skip_verify" validate:"required"`
ScanInterval string `json:"scan_interval" validate:"required"`
} `json:"bounce.mailboxes" validate:"omitempty,dive"`
AdminCustomCSS string `json:"appearance.admin.custom_css"`
AdminCustomJS string `json:"appearance.admin.custom_js"`