added custom validator and validation to put settings api

This commit is contained in:
nayanthulkar28 2024-07-02 18:12:55 +05:30
parent 22890a17a1
commit 9b83ebb700
6 changed files with 125 additions and 72 deletions

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"`