dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

commit 8bd76f1ccf332018688df3d37351be92ca7a11b8
parent 40952661334b9edaf04586a84dbbad9ecd5a8b89
Author: n0tr1v <n0tr1v@protonmail.com>
Date:   Mon,  5 Jun 2023 01:54:13 -0700

dynamic spam filters

Diffstat:
Acmd/dkf/migrations/136.sql | 9+++++++++
Mpkg/actions/actions.go | 1+
Apkg/database/tableSpamFilters.go | 29+++++++++++++++++++++++++++++
Mpkg/web/handlers/admin.go | 35+++++++++++++++++++++++++++++++++++
Mpkg/web/handlers/api/v1/spamInterceptor.go | 51++++++++++++++++++++++++++++++++++++++++++++++++---
Mpkg/web/handlers/data.go | 11+++++++++++
Mpkg/web/public/views/pages/admin/index.gohtml | 1+
Apkg/web/public/views/pages/admin/spam-filters.gohtml | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpkg/web/web.go | 2++
9 files changed, 217 insertions(+), 3 deletions(-)

diff --git a/cmd/dkf/migrations/136.sql b/cmd/dkf/migrations/136.sql @@ -0,0 +1,9 @@ +-- +migrate Up +CREATE TABLE IF NOT EXISTS spam_filters ( + id INTEGER NOT NULL PRIMARY KEY, + filter VARCHAR(255) NOT NULL, + is_regex TINYINT(1) NOT NULL DEFAULT 0, + action INTEGER NOT NULL DEFAULT 0, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP); + +-- +migrate Down diff --git a/pkg/actions/actions.go b/pkg/actions/actions.go @@ -82,6 +82,7 @@ func Start(c *cli.Context) error { utils.SGo(func() { xmrWatch(db) }) utils.SGo(func() { openBrowser(noBrowser, int64(port)) }) + v1.LoadFilters(db) v1.ChessInstance = v1.NewChess(db) v1.BattleshipInstance = v1.NewBattleship(db) v1.WWInstance = v1.NewWerewolf(db) diff --git a/pkg/database/tableSpamFilters.go b/pkg/database/tableSpamFilters.go @@ -0,0 +1,29 @@ +package database + +import "time" + +type SpamFilter struct { + ID int64 + Action int64 + Filter string + IsRegex bool + CreatedAt time.Time +} + +func (d *DkfDB) GetSpamFilters() (out []SpamFilter, err error) { + err = d.db.Find(&out).Error + return +} + +func (d *DkfDB) CreateOrEditSpamFilter(id int64, filter string, isRegex bool, action int64) (out SpamFilter, err error) { + out.ID = id + out.Filter = filter + out.IsRegex = isRegex + out.Action = action + err = d.db.Save(&out).Error + return +} + +func (d *DkfDB) DeleteSpamFilterByID(id int64) error { + return d.db.Delete(SpamFilter{}, "id = ?", id).Error +} diff --git a/pkg/web/handlers/admin.go b/pkg/web/handlers/admin.go @@ -3,6 +3,7 @@ package handlers import ( dutils "dkforest/pkg/database/utils" "dkforest/pkg/managers" + v1 "dkforest/pkg/web/handlers/api/v1" "github.com/jinzhu/gorm" "net/http" "regexp" @@ -18,6 +19,40 @@ import ( "github.com/skratchdot/open-golang/open" ) +func AdminSpamFiltersHandler(c echo.Context) error { + db := c.Get("database").(*database.DkfDB) + var data adminSpamFiltersData + data.ActiveTab = "spamfilters" + data.SpamFilters, _ = db.GetSpamFilters() + data.SpamFiltersCount = int64(len(data.SpamFilters)) + + if c.Request().Method == http.MethodPost { + btnSubmit := c.Request().PostFormValue("btn_submit") + data.ID = utils.DoParseInt64(c.Request().PostFormValue("id")) + data.Filter = c.Request().PostFormValue("filter") + data.IsRegex = utils.DoParseBool(c.Request().PostFormValue("is_regex")) + data.Action = utils.Clamp(utils.DoParseInt64(c.Request().PostFormValue("action")), 0, 2) + if !utils.ValidateRuneLength(data.Filter, 1, 255) { + data.Error = "filter must be within 1-255 characters" + return c.Render(http.StatusOK, "admin.spam-filter", data) + } + if data.ID == 0 || btnSubmit == "edit" { + if _, err := db.CreateOrEditSpamFilter(data.ID, data.Filter, data.IsRegex, data.Action); err != nil { + logrus.Error(err) + } + v1.LoadFilters(db) + } else if btnSubmit == "delete" { + if err := db.DeleteSpamFilterByID(data.ID); err != nil { + logrus.Error(err) + } + v1.LoadFilters(db) + } + return c.Redirect(http.StatusFound, "/admin/spam-filters") + } + + return c.Render(http.StatusOK, "admin.spam-filters", data) +} + func AdminNewGistHandler(c echo.Context) error { authUser := c.Get("authUser").(*database.User) db := c.Get("database").(*database.DkfDB) diff --git a/pkg/web/handlers/api/v1/spamInterceptor.go b/pkg/web/handlers/api/v1/spamInterceptor.go @@ -14,9 +14,55 @@ import ( type SpamInterceptor struct{} +type Filter struct { + IsRegex bool + Term string + Rgx *regexp.Regexp + Kick bool + Hb bool +} + +var filters []Filter + +func LoadFilters(db *database.DkfDB) { + filters = make([]Filter, 0) + dbFilters, _ := db.GetSpamFilters() + for _, dbFilter := range dbFilters { + f := Filter{IsRegex: dbFilter.IsRegex} + if dbFilter.Action == 1 { + f.Kick = true + } else if dbFilter.Action == 2 { + f.Hb = true + } + if dbFilter.IsRegex { + f.Rgx = regexp.MustCompile(dbFilter.Filter) + } else { + f.Term = dbFilter.Filter + } + filters = append(filters, f) + } +} + func (i SpamInterceptor) InterceptMsg(c *Command) { + lowerCaseMessage := strings.ToLower(c.message) + silentSelfKick := config.SilentSelfKick.Load() + for _, f := range filters { + isMatch := (f.IsRegex && f.Rgx.MatchString(c.message)) || + (!f.IsRegex && strings.Contains(lowerCaseMessage, f.Term)) + if isMatch { + if f.Kick { + _ = dutils.SelfKick(c.db, *c.authUser, silentSelfKick) + } else if f.Hb { + dutils.SelfHellBan(c.db, c.authUser) + return + } + c.err = ErrSpamFilterTriggered + return + } + } + if c.room.IsOfficialRoom() { - if err := checkSpam(c.db, c.origMessage, c.authUser); err != nil { + if err := checkSpam(c.db, c.origMessage, lowerCaseMessage, c.authUser); err != nil { c.err = err return } @@ -35,8 +81,7 @@ func (i SpamInterceptor) InterceptMsg(c *Command) { var ErrSpamFilterTriggered = errors.New("spam filter triggered") -func checkSpam(db *database.DkfDB, origMessage string, authUser *database.User) error { - lowerCaseMessage := strings.ToLower(origMessage) +func checkSpam(db *database.DkfDB, origMessage, lowerCaseMessage string, authUser *database.User) error { silentSelfKick := config.SilentSelfKick.Load() // Kick retard new users diff --git a/pkg/web/handlers/data.go b/pkg/web/handlers/data.go @@ -880,6 +880,17 @@ type adminCreateGistData struct { ErrorName string } +type adminSpamFiltersData struct { + ActiveTab string + SpamFiltersCount int64 + SpamFilters []database.SpamFilter + ID int64 + Filter string + IsRegex bool + Action int64 + Error string +} + type publicProfileData struct { User database.User PublicNotes database.UserPublicNote diff --git a/pkg/web/public/views/pages/admin/index.gohtml b/pkg/web/public/views/pages/admin/index.gohtml @@ -17,6 +17,7 @@ <a href="/admin/gists" class="list-group-item list-group-item-action{{ if eq .Data.ActiveTab "gists" }} active{{ end }}">{{ t "Gists" . }}</a> <a href="/admin/backup" class="list-group-item list-group-item-action{{ if eq .Data.ActiveTab "backup" }} active{{ end }}">{{ t "Backup" . }}</a> <a href="/admin/ddos" class="list-group-item list-group-item-action{{ if eq .Data.ActiveTab "ddos" }} active{{ end }}">{{ t "DDoS" . }}</a> + <a href="/admin/spam-filters" class="list-group-item list-group-item-action{{ if eq .Data.ActiveTab "spamfilters" }} active{{ end }}">{{ t "Spam filters" . }}</a> <!-- <a href="/admin/update" class="list-group-item list-group-item-action{{ if eq .Data.ActiveTab "update" }} active{{ end }}">{{ t "Update" . }}</a>--> </div> </div> diff --git a/pkg/web/public/views/pages/admin/spam-filters.gohtml b/pkg/web/public/views/pages/admin/spam-filters.gohtml @@ -0,0 +1,80 @@ +{{ define "sub-content" }} + <div class="pb-2 mt-4 mb-4 border-bottom"> + <div class="float-right"> + <a href="/admin/spam-filters/new" class="btn btn-success"> + <i class="fa fa-plus fa-fw"></i> New filter + </a> + </div> + <h2>{{ .Data.SpamFiltersCount | comma }} Spam filters</h2> +</div> + +{{ if .Data.Error }} + <div class="alert alert-danger">{{ .Data.Error }}</div> +{{ end }} + +<table class="table table-striped table-sm table-novpadding table-dark"> + <thead> + <tr> + <th>Filter</th> + <th>Is regex</th> + <th>Action</th> + <th>Actions</th> + </tr> + </thead> + <tbody> + {{ range .Data.SpamFilters }} + <form method="post"> + <input type="hidden" name="id" value="{{ .ID }}" /> + <input type="hidden" name="csrf" value="{{ $.CSRF }}" /> + <tr> + <td><input type="text" name="filter" value="{{ .Filter }}" class="form-control form-control-sm" /></td> + <td class="align-middle"> + <div class="form-check form-check-1"> + <div class="checkbox-wrapper form-check-input"> + <input class="my-cbx" type="checkbox" name="is_regex" id="is_regex_{{ .ID }}" value="1"{{ if .IsRegex }} checked{{ end }} /> + <label for="is_regex_{{ .ID }}" class="toggle"><span></span></label> + </div> + <label class="form-check-label" for="is_regex_{{ .ID }}">regex</label> + </div> + </td> + <td> + <select name="action" class="form-control form-control-sm"> + <option value="1"{{ if eq .Action 1 }} selected{{ end }}>Kick</option> + <option value="2"{{ if eq .Action 2 }} selected{{ end }}>Hellban</option> + <option value="0"{{ if eq .Action 0 }} selected{{ end }}>Error</option> + </select> + </td> + <td class="text-right"> + <button type="submit" class="btn btn-sm btn-primary" title="Edit spam filter" name="btn_submit" value="edit">Edit</button> + <button type="submit" class="btn btn-sm btn-danger" title="Delete spam filter" name="btn_submit" value="delete">X</button> + </td> + </tr> + </form> + {{ end }} + <form method="post"> + <input type="hidden" name="csrf" value="{{ .CSRF }}" /> + <tr> + <td><input name="filter" type="text" class="form-control form-control-sm" /></td> + <td class="align-middle"> + <div class="form-check form-check-1"> + <div class="checkbox-wrapper form-check-input"> + <input class="my-cbx" type="checkbox" name="is_regex" id="is_regex" value="1" /> + <label for="is_regex" class="toggle"><span></span></label> + </div> + <label class="form-check-label" for="is_regex">regex</label> + </div> + </td> + <td> + <select name="action" class="form-control form-control-sm"> + <option value="1" selected>Kick</option> + <option value="2">Hellban</option> + <option value="0">Error</option> + </select> + </td> + <td class="text-right"><button type="submit" class="btn btn-success btn-sm">Add</button></td> + </tr> + </form> + </tbody> +</table> + +{{ end }} +\ No newline at end of file diff --git a/pkg/web/web.go b/pkg/web/web.go @@ -246,6 +246,8 @@ func getMainServer(db *database.DkfDB, i18nBundle *i18n.Bundle, renderer *tmp.Te adminGroup.GET("/admin/sessions", handlers.SessionsHandler) adminGroup.GET("/admin/backup", handlers.BackupHandler) adminGroup.POST("/admin/backup", handlers.BackupHandler) + adminGroup.GET("/admin/spam-filters", handlers.AdminSpamFiltersHandler) + adminGroup.POST("/admin/spam-filters", handlers.AdminSpamFiltersHandler) adminGroup.GET("/admin/ddos", handlers.DdosHandler) adminGroup.POST("/admin/ddos", handlers.DdosHandler) adminGroup.GET("/admin/audits", handlers.AdminAuditsHandler)