1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-08-17 10:56:24 -07:00

Make ban adding (sorta) work again

This commit is contained in:
Eggbertx 2022-11-17 16:32:28 -08:00
parent 34688190e2
commit bfac3ef93c
5 changed files with 191 additions and 173 deletions

View file

@ -2,22 +2,50 @@ package gcsql
import (
"database/sql"
"errors"
"regexp"
"strconv"
)
const (
ipBanQueryBase = `SELECT
id, staff_id, board_id, banned_for_post_id, copy_post_text, is_thread_ban,
is_active, ip, issued_at, appeal_at, expires_at, permanent, staff_note,
message, can_appeal
FROM DBPREFIXip_ban`
)
var (
ErrBanAlreadyInserted = errors.New("ban already submitted")
)
type Ban interface {
IsGlobalBan() bool
}
func NewIPBan(ban *IPBan) error {
const query = `INSERT INTO DBPREFIXip_ban
(staff_id, board_id, banned_for_post_id, copy_post_text, is_thread_ban, is_active, ip,
appeal_at, expires_at, permanent, staff_note, message, can_appeal)
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
if ban.ID > 0 {
return ErrBanAlreadyInserted
}
var err error
ban.ID, err = getNextFreeID("DBPREFIXip_ban")
if err != nil {
return err
}
_, err = ExecSQL(query,
ban.StaffID, ban.BoardID, ban.BannedForPostID, ban.CopyPostText, ban.IsThreadBan, ban.IsActive, ban.IP,
ban.AppealAt, ban.ExpiresAt, ban.Permanent, ban.StaffNote, ban.Message, ban.CanAppeal)
return err
}
// CheckIPBan returns the latest active IP ban for the given IP, as well as any errors. If the
// IPBan pointer is nil, the IP has no active bans
func CheckIPBan(ip string, boardID int) (*IPBan, error) {
const query = `SELECT
id, staff_id, board_id, banned_for_post_id, copy_post_text, is_thread_ban,
is_active, ip, issued_at, appeal_at, expires_at, permanent, staff_note,
message, can_appeal
FROM DBPREFIXip_ban WHERE ip = ? AND (board_id IS NULL OR board_id = ?) AND
const query = ipBanQueryBase + ` WHERE ip = ? AND (board_id IS NULL OR board_id = ?) AND
is_active AND (expires_at > CURRENT_TIMESTAMP OR permanent)
ORDER BY id DESC LIMIT 1`
var ban IPBan
@ -31,12 +59,27 @@ func CheckIPBan(ip string, boardID int) (*IPBan, error) {
return &ban, nil
}
func GetIPBanByID(id int) (*IPBan, error) {
const query = ipBanQueryBase + " WHERE id = ?"
var ban IPBan
err := QueryRowSQL(query, interfaceSlice(id), interfaceSlice(
&ban.ID, &ban.StaffID, &ban.BoardID, &ban.BannedForPostID, &ban.CopyPostText, &ban.IsThreadBan,
&ban.IsActive, &ban.IP, &ban.IssuedAt, &ban.AppealAt, &ban.ExpiresAt, &ban.Permanent, &ban.StaffNote,
&ban.Message, &ban.CanAppeal))
if err != nil {
return nil, err
}
return &ban, err
}
func DeleteIPBanByID(id int) error {
const query = `UPDATE DBPREFIXip_ban SET is_active = FALSE WHERE id = ?`
_, err := ExecSQL(query, id)
return err
}
func GetIPBans(boardID int, limit int, onlyActive bool) ([]IPBan, error) {
query := `SELECT
id, staff_id, board_id, banned_for_post_id, copy_post_text, is_thread_ban,
is_active, ip, issued_at, appeal_at, expires_at, permanent, staff_note,
message, can_appeal
FROM DBPREFIXip_ban`
query := ipBanQueryBase
if boardID > 0 {
query += " WHERE board_id = ?"
}

View file

@ -152,6 +152,15 @@ var funcMap = template.FuncMap{
}
return dict, nil
},
"until": func(t time.Time) string {
return t.Sub(time.Now()).String()
},
"dereference": func(a *int) int {
if a == nil {
return 0
}
return *a
},
// Imageboard functions
"bannedForever": func(ban *gcsql.IPBan) bool {
return ban.IsActive && ban.Permanent && !ban.CanAppeal

View file

@ -222,31 +222,35 @@ var actions = []Action{
},
{
ID: "bans",
Title: "IP Bans",
Title: "Bans",
Permissions: ModPerms,
JSONoutput: OptionalJSON,
Callback: func(writer http.ResponseWriter, request *http.Request, staff *gcsql.Staff, wantsJSON bool, infoEv *zerolog.Event, errEv *zerolog.Event) (output interface{}, err error) {
var outputStr string
ip := request.FormValue("ip")
postid := request.FormValue("postid")
ban := gcsql.IPBan{
BoardID: new(int),
}
*ban.BoardID = 32
if request.FormValue("do") == "add" {
//
}
boardIDstr := request.FormValue("boardid")
var boardid int
if boardIDstr != "" {
if boardid, err = strconv.Atoi(boardIDstr); err != nil {
var ban gcsql.IPBan
ban.StaffID = staff.ID
deleteIDStr := request.FormValue("delete")
if deleteIDStr != "" {
// deleting a ban
ban.ID, err = strconv.Atoi(deleteIDStr)
if err != nil {
errEv.Err(err).
Str("boardid", boardIDstr).Caller().Send()
Str("delete", deleteIDStr).
Caller().Send()
return "", err
}
} else if request.FormValue("do") == "add" {
err := ipBanFromRequest(&ban, request, errEv)
if err != nil {
return "", err
}
infoEv.
Str("bannedIP", ban.IP).
Str("expires", ban.ExpiresAt.String()).
Bool("permanent", ban.Permanent).
Str("reason", ban.Message).
Msg("Added IP ban")
}
filterBoardIDstr := request.FormValue("filterboardid")
var filterBoardID int
if filterBoardIDstr != "" {
@ -277,146 +281,16 @@ var actions = []Action{
"systemCritical": config.GetSystemCriticalConfig(),
"banlist": banlist,
"allBoards": gcsql.AllBoards,
"boardid": boardid,
"ip": ip,
"postid": postid,
"ban": ban,
"filterboardid": filterBoardID,
"webroot": config.GetSystemCriticalConfig().WebRoot,
}, manageBansBuffer, "text/html"); err != nil {
errEv.Err(err).Str("template", "manage_bans.html").Caller().Send()
return "", errors.New("Error executing ban management page template: " + err.Error())
}
outputStr += manageBansBuffer.String()
return outputStr, nil
},
},
/* {
ID: "bans",
Title: "Bans",
Permissions: ModPerms,
Callback: func(writer http.ResponseWriter, request *http.Request, staff *gcsql.Staff, wantsJSON bool) (output interface{}, err error) { //TODO whatever this does idk man
var outputStr string
var post gcsql.Post
errorEv := gcutil.LogError(nil).
Str("action", "bans").
Str("staff", staff.Username)
defer errorEv.Discard()
if request.FormValue("do") == "add" {
ip := request.FormValue("ip")
name := request.FormValue("name")
nameIsRegex := (request.FormValue("nameregex") == "on")
checksum := request.FormValue("checksum")
filename := request.FormValue("filename")
durationForm := request.FormValue("duration")
permaban := (durationForm == "" || durationForm == "0" || durationForm == "forever")
duration, err := gcutil.ParseDurationString(durationForm)
if err != nil {
errorEv.Err(err).Send()
return "", err
}
expires := time.Now().Add(duration)
boards := request.FormValue("boards")
reason := html.EscapeString(request.FormValue("reason"))
staffNote := html.EscapeString(request.FormValue("staffnote"))
if filename != "" {
err = gcsql.CreateFileNameBan(filename, nameIsRegex, staff.Username, staffNote, boards)
}
if err != nil {
outputStr += err.Error()
errorEv.Err(err).Send()
err = nil
}
if name != "" {
if err = gcsql.CreateUserNameBan(name, nameIsRegex, staff.Username, permaban, staffNote, boards); err != nil {
errorEv.
Str("banType", "username").
Str("user", name).Send()
return "", err
}
infoEv.
Str("banType", "username").
Str("user", name).
Bool("permaban", permaban).Send()
}
if request.FormValue("fullban") == "on" {
err = gcsql.CreateUserBan(ip, false, staff.Username, boards, expires, permaban, staffNote, reason, true, time.Now())
if err != nil {
errorEv.
Str("banType", "ip").
Str("banIP", ip).
Bool("threadBan", false).
Str("bannedFromBoards", boards).Send()
return "", err
}
infoEv.
Str("banType", "ip").
Str("banIP", ip).
Bool("threadBan", true).
Str("bannedFromBoards", boards).Send()
} else {
if request.FormValue("threadban") == "on" {
err = gcsql.CreateUserBan(ip, true, staff.Username, boards, expires, permaban, staffNote, reason, true, time.Now())
if err != nil {
errorEv.
Str("banType", "ip").
Str("banIP", ip).
Bool("threadBan", true).
Str("bannedFromBoards", boards).Send()
return "", err
}
infoEv.
Str("banType", "ip").
Str("banIP", ip).
Bool("threadBan", true).
Str("bannedFromBoards", boards).Send()
}
if request.FormValue("imageban") == "on" {
err = gcsql.CreateFileBan(checksum, staff.Username, staffNote, boards)
if err != nil {
errorEv.
Str("banType", "fileBan").
Str("checksum", checksum).Send()
return "", err
}
infoEv.
Str("banType", "fileBan").
Str("checksum", checksum).Send()
}
}
}
if request.FormValue("postid") != "" {
var err error
post, err = gcsql.GetSpecificPostByString(request.FormValue("postid"), true)
if err != nil {
errorEv.Err(err).Str("postid", request.FormValue("postid")).Msg("Error getting post")
err = errors.New("Error getting post: " + err.Error())
return "", err
}
}
banlist, err := gcsql.GetAllBans()
if err != nil {
errorEv.Err(err).Msg("Error getting ban list")
err = errors.New("Error getting ban list: " + err.Error())
return "", err
}
manageBansBuffer := bytes.NewBufferString("")
if err = serverutil.MinifyTemplate(gctemplates.ManageBans, map[string]interface{}{
// "systemCritical": config.GetSystemCriticalConfig(),
"banlist": banlist,
"post": post,
}, manageBansBuffer, "text/html"); err != nil {
errEv.Err(err).Str("template", "manage_bans.html").Caller().Send()
return "", errors.New("Error executing ban management page template: " + err.Error())
}
outputStr += manageBansBuffer.String()
return outputStr, nil
}}, */
}},
{
ID: "ipsearch",
Title: "IP Search",

81
pkg/manage/bans.go Normal file
View file

@ -0,0 +1,81 @@
package manage
import (
"errors"
"html"
"net/http"
"strconv"
"time"
"github.com/gochan-org/gochan/pkg/gcsql"
"github.com/gochan-org/gochan/pkg/gcutil"
"github.com/rs/zerolog"
)
func ipBanFromRequest(ban *gcsql.IPBan, request *http.Request, errEv *zerolog.Event) error {
banIDStr := request.FormValue("edit")
if banIDStr != "" && request.FormValue("do") == "edit" {
banID, err := strconv.Atoi(banIDStr)
if err != nil {
errEv.Err(err).
Str("editBanID", banIDStr).
Caller().Send()
return errors.New("invalid 'edit' field value (must be int)")
}
editing, err := gcsql.GetIPBanByID(banID)
if err != nil {
errEv.Err(err).
Int("editBanID", banID).
Caller().Send()
return errors.New("Unable to get ban with id " + banIDStr + " (SQL error)")
}
*ban = *editing
return nil
}
ban.IP = request.FormValue("ip")
ban.Permanent = request.FormValue("permanent") == "on"
if !ban.Permanent {
durationStr := request.FormValue("duration")
duration, err := gcutil.ParseDurationString(durationStr)
if err != nil {
errEv.Err(err).
Str("duration", durationStr).
Caller().Msg("Invalid duration")
return err
}
ban.ExpiresAt = time.Now().Add(duration)
}
ban.CanAppeal = request.FormValue("noappeals") != "on"
if ban.CanAppeal {
appealWaitStr := request.FormValue("appealwait")
if appealWaitStr != "" {
appealDuration, err := gcutil.ParseDurationString(appealWaitStr)
if err != nil {
errEv.Err(err).
Str("appealwait", appealWaitStr).
Caller().Msg("Invalid appeal delay duration string")
return err
}
ban.AppealAt = time.Now().Add(appealDuration)
}
}
ban.IsThreadBan = request.FormValue("threadban") == "on"
boardIDstr := request.FormValue("boardid")
if boardIDstr != "" && boardIDstr != "0" {
boardID, err := strconv.Atoi(boardIDstr)
if err != nil {
errEv.Err(err).
Str("boardid", boardIDstr).
Caller().Send()
return err
}
ban.BoardID = new(int)
*ban.BoardID = boardID
}
ban.Message = html.EscapeString(request.FormValue("reason"))
ban.StaffNote = html.EscapeString(request.FormValue("staffnote"))
ban.IsActive = true
return gcsql.NewIPBan(ban)
}

View file

@ -1,32 +1,43 @@
<form method="POST" action="/manage?action=bans">
<form method="POST" action="{{webPath "manage?action=bans"}}">
<input type="hidden" name="do" value="add" />
<h2>Add IP ban</h2>
<table>
<tr><th>IP address</th><td><input type="text" name="ip" value="{{.post.IP}}" /></td></tr>
<tr><th>Duration</th><td><input type="text" name="duration" /></td></tr>
<tr><th>IP address</th><td><input type="text" name="ip" value="{{.ban.IP}}" style="width: 100%;"/></td></tr>
<tr><th>Duration</th><td><input type="text" name="duration" style="width: 100%;" {{if gt .ban.ID 0}}value="{{until .ban.ExpiresAt}}"{{end}}/></td></tr>
<tr><th></th><td>e.g. '1y2mo3w4d5h6m7s',<br />'1 year 2 months 3 weeks 4 days 5 hours 6 minutes 7 seconds'<br/>Optional if "Permanent" is checked, required otherwise</td></tr>
<tr><th>Permanent</th><td><input type="checkbox" name="permanent" id="permanent" {{with $.permanent}}{{if eq $.permanent "on"}}checked{{end}}{{end}}> (overrides the duration)</td></tr>
<tr><th>Appeal wait time</th><td><input type="text" name="appealwait" id="appealwait"></td></tr>
<tr><th></th><td>Same syntax as above, but optional.</td></tr>
<tr><th>Thread starting ban</th><td><input type="checkbox" name="threadban" /></td></tr>
<tr><th>Permanent</th><td><input type="checkbox" name="permanent" id="permanent" {{if .ban.Permanent}}checked{{end}}> (overrides the duration)</td></tr>
<tr><th>Appeal wait time</th><td><input type="text" name="appealwait" id="appealwait" style="width: 100%;"></td></tr>
<tr><th></th><td>Same syntax as above, but optional.<br />If left blank, they can appeal immediately</td></tr>
<tr><th>No appeals</th><td><input type="checkbox" name="noappeals" /></td> (if checked, Appeal wait time field is ignored)</tr>
<tr><th>Thread starting ban</th><td><input type="checkbox" name="threadban" /> (user can reply to threads but can't make new threads)</td></tr>
{{with $.bannedForPostID}}<tr><th>Banned for post ID</th><td>{{$.bannedForPostID}}</td></tr>{{end}}
<tr><th>Board</th><td><select name="boardid" id="boardid">
<option value="0">All boards</option>
{{- range $b, $board := $.allBoards -}}
<option value="{{$board.ID}}" {{if eq $.boardid $board.ID}}selected{{end}}>/{{$board.Dir}}/ - {{$board.Title}}</option>
<option value="{{$board.ID}}" {{if eq (dereference $.ban.BoardID) $board.ID}}selected{{end}}>/{{$board.Dir}}/ - {{$board.Title}}</option>
{{- end -}}
</select></td></tr>
<tr><th>Reason</th><td><textarea name="reason" rows="5" cols="30" placeholder="Message to be displayed to the banned user"></textarea></td></tr>
<tr><th>Staff note</th><td><textarea name="staffnote" rows="5" cols="30" placeholder="Private note that only staff can see"></textarea></td></tr>
<tr><th>Reason</th><td><textarea name="reason" style="width: 100%;" rows="6" placeholder="Message to be displayed to the banned user"></textarea></td></tr>
<tr><th>Staff note</th><td><textarea name="staffnote" style="width: 100%;" rows="6" placeholder="Private note that only staff can see"></textarea></td></tr>
</table>
<input type="submit" value="Ban user" /> <input type="button" name="docancel" value="Cancel" onclick="window.location = './manage?action=bans'; return false"/>
</form>
<h2 class="manage-header">Banlist</h2>
<h2 id="banlist">Banlist</h2>
<form action="{{webPath "manage?action=bans"}}" method="get">
<input type="hidden" name="action" value="bans">
Filter board: <select name="filterboardid" id="filterboardid" onchange="window.location = '{{webPath "manage?action=bans&filterboardid="}}' + this.value + '#banlist'">
<option value="0">All boards</option>
{{- range $b, $board := $.allBoards -}}
<option value="{{$board.ID}}" {{if eq $.filterboardid $board.ID}}selected{{end}}>/{{$board.Dir}}/ - {{$board.Title}}</option>
{{- end -}}
</select> <input type="submit">
</form>
<table border="1">
<tr><th>IP</th><th>Board</th><th>Reason</th><th>Staff</th><th>Staff note</th><th>Banned post text</th><th>Set</th><th>Expires</th><th>Appeal at</th></tr>
<tr><th>Action</th><th>IP</th><th>Board</th><th>Reason</th><th>Staff</th><th>Staff note</th><th>Banned post text</th><th>Set</th><th>Expires</th><th>Appeal at</th></tr>
{{range $_, $ban := $.banlist -}}
<tr>
<td> <a href="{{webPath "manage?action=bans&edit="}}{{$ban.ID}}">Edit</a> | <a href="{{webPath "manage?action=bans&delete="}}{{$ban.ID}}">Delete</a> </td>
<td>{{$ban.IP}}</td>
<td>{{if not $ban.BoardID}}<i>all</i>{{else}}/{{getBoardDirFromID $ban.BoardID}}/{{end}}</td>
<td>{{$ban.Message}}</td>