1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-08-26 10:36:23 -07:00

Add warning event to gcutil.LogRequest, improve error messages

This commit is contained in:
Eggbertx 2025-03-07 23:14:27 -08:00
parent 7b86650733
commit 3b12039f38
7 changed files with 107 additions and 89 deletions

View file

@ -4,7 +4,6 @@ import (
"context"
"database/sql"
"errors"
"fmt"
"net/http"
"os"
"path"
@ -146,18 +145,10 @@ func getAllPostsToDelete(postIDs []any, fileOnly bool) ([]delPost, []any, error)
return posts, postIDsAny, rows.Close()
}
func serveError(writer http.ResponseWriter, errStr string, statusCode int, wantsJSON bool, errEv *zerolog.Event) {
if errEv != nil {
errEv.Msg(errStr)
}
writer.WriteHeader(statusCode)
server.ServeError(writer, errStr, wantsJSON, nil)
}
func deletePosts(checkedPosts []int, writer http.ResponseWriter, request *http.Request) {
// Delete post(s) or thread(s)
infoEv, errEv := gcutil.LogRequest(request)
defer gcutil.LogDiscard(infoEv, errEv)
infoEv, warnEv, errEv := gcutil.LogRequest(request)
defer gcutil.LogDiscard(infoEv, warnEv, errEv)
password := request.FormValue("password")
passwordMD5 := gcutil.Md5Sum(password)
@ -167,59 +158,71 @@ func deletePosts(checkedPosts []int, writer http.ResponseWriter, request *http.R
contentType = "application/json"
}
writer.Header().Set("Content-Type", contentType)
gcutil.LogBool("wantsJSON", wantsJSON, infoEv, errEv)
gcutil.LogBool("wantsJSON", wantsJSON, infoEv, warnEv, errEv)
if len(checkedPosts) < 1 {
serveError(writer, "No posts selected", http.StatusBadRequest, wantsJSON, errEv.Err(nil).Caller())
warnEv.Msg("No posts selected")
server.ServeError(writer, server.NewServerError("No posts selected", http.StatusBadRequest), wantsJSON, nil)
return
}
staff, err := gcsql.GetStaffFromRequest(request)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
serveError(writer, "Unable to get staff info", http.StatusInternalServerError, wantsJSON, errEv.Err(err).Caller())
errEv.Err(err).Caller().Msg("Unable to get staff info")
server.ServeError(writer, server.NewServerError("Unable to get staff info", http.StatusInternalServerError), wantsJSON, nil)
return
}
posts := make([]any, len(checkedPosts))
for i, p := range checkedPosts {
posts[i] = p
}
fileOnly := request.FormValue("fileonly") == "on"
gcutil.LogBool("fileOnly", fileOnly, infoEv, errEv)
gcutil.LogInt("affectedPosts", len(posts), infoEv, errEv)
gcutil.LogBool("fileOnly", fileOnly, infoEv, warnEv, errEv)
gcutil.LogInt("affectedPosts", len(posts), infoEv, warnEv, errEv)
if staff.Rank > 0 {
gcutil.LogStr("staff", staff.Username, infoEv, errEv)
} else {
sumsMatch, err := validatePostPasswords(posts, passwordMD5)
if err != nil {
serveError(writer, "Unable to validate post password checksums",
http.StatusInternalServerError, wantsJSON, errEv.Err(err).Caller())
errEv.Err(err).Caller().Msg("Unable to validate post password checksums")
server.ServeError(writer,
server.NewServerError("Unable to validate post password checksums", http.StatusInternalServerError),
wantsJSON, nil)
return
}
if !sumsMatch {
serveError(writer, "One or more post passwords do not match", http.StatusUnauthorized, wantsJSON, errEv.Caller())
warnEv.Msg("One or more post passwords do not match")
server.ServeError(writer,
server.NewServerError("One or more post passwords do not match", http.StatusUnauthorized),
wantsJSON, nil)
return
}
}
delPosts, affectedPostIDs, err := getAllPostsToDelete(posts, fileOnly)
if err != nil {
serveError(writer, "Unable to get post info for one or more checked posts",
http.StatusInternalServerError, wantsJSON, errEv.Err(err).Caller())
errEv.Err(err).Caller().Msg("Unable to get post info for one or more checked posts")
server.ServeError(writer,
server.NewServerError("Unable to get post info for one or more checked posts", http.StatusInternalServerError),
wantsJSON, nil)
return
}
boardid, err := strconv.Atoi(request.FormValue("boardid"))
boardid, err := strconv.Atoi(request.PostFormValue("boardid"))
if err != nil {
serveError(writer, "Invalid boardid value", http.StatusBadRequest, wantsJSON, errEv.Err(err).Caller().
Str("boardid", request.FormValue("boardid")))
warnEv.Str("boardid", request.PostFormValue("boardid")).Msg("Invalid boardid value")
server.ServeError(writer, server.NewServerError("Invalid boardid value", http.StatusBadRequest), wantsJSON, nil)
return
}
errEv.Int("boardid", boardid)
board, err := gcsql.GetBoardDir(boardid)
if err != nil {
serveError(writer, "Unable to get board from boardid", http.StatusInternalServerError,
wantsJSON, errEv.Err(err).Caller())
errEv.Err(err).Caller().Msg("Unable to get board from boardid")
server.ServeError(writer,
server.NewServerError("Unable to get board from boardid", http.StatusInternalServerError),
wantsJSON, nil)
return
}
@ -228,13 +231,14 @@ func deletePosts(checkedPosts []int, writer http.ResponseWriter, request *http.R
return
}
if !fileOnly && !markPostsAsDeleted(affectedPostIDs, request, writer, errEv) {
// markPostsAsDeleted logs any errors
return
}
if err = building.BuildBoards(false, boardid); err != nil {
// BuildBoards logs any errors
serveError(writer, fmt.Sprintf("Unable to rebuild /%s/", board),
http.StatusInternalServerError, wantsJSON, nil)
server.ServeError(writer, server.NewServerError("Unable to rebuild /"+board+"/", http.StatusInternalServerError), wantsJSON, nil)
return
}
if fileOnly {
infoEv.Msg("file(s) deleted")
@ -289,26 +293,27 @@ func markPostsAsDeleted(posts []any, request *http.Request, writer http.Response
wantsJSON := serverutil.IsRequestingJSON(request)
if err != nil {
errEv.Err(err).Caller().Msg("Unable to start deletion transaction")
serveError(writer, "Unable to delete posts", http.StatusInternalServerError, wantsJSON, errEv.Err(err).Caller())
server.ServeError(writer, server.NewServerError("Unable to start deletion transaction", http.StatusInternalServerError), wantsJSON, nil)
return false
}
defer tx.Rollback()
const postsError = "Unable to delete post(s)"
const threadsError = "Unable to delete thread(s)"
if _, err = gcsql.Exec(opts, deletePostsSQL, posts...); err != nil {
serveError(writer, postsError, http.StatusInternalServerError, wantsJSON, errEv.Err(err).Caller())
errEv.Err(err).Caller().Msg("Unable to mark post(s) as deleted")
server.ServeError(writer, server.NewServerError(postsError, http.StatusInternalServerError), wantsJSON, nil)
return false
}
if _, err = gcsql.Exec(opts, deleteThreadSQL, posts...); err != nil {
errEv.Err(err).Caller().Msg("Unable to mark thread(s) as deleted")
serveError(writer, threadsError, http.StatusInternalServerError, wantsJSON, errEv.Err(err).Caller())
server.ServeError(writer, server.NewServerError(threadsError, http.StatusInternalServerError), wantsJSON, nil)
return false
}
if err = tx.Commit(); err != nil {
errEv.Err(err).Caller().Msg("Unable to commit deletion transaction")
serveError(writer, "Unable to finalize deletion", http.StatusInternalServerError, wantsJSON, nil)
server.ServeError(writer, server.NewServerError("Unable to finalize deletion", http.StatusInternalServerError), wantsJSON, nil)
return false
}
@ -336,7 +341,7 @@ func deletePostFiles(posts []delPost, deleteIDs []any, permDelete bool, request
var tmpErr error
for _, post := range posts {
if tmpErr = post.deleteFile(permDelete); tmpErr != nil {
gcutil.LogWarning().Err(tmpErr).Caller().
gcutil.LogError(tmpErr).Err(tmpErr).Caller().
Int("postID", post.postID).
Int("opID", post.opID).
Str("filename", post.filename).
@ -353,14 +358,18 @@ func deletePostFiles(posts []delPost, deleteIDs []any, permDelete bool, request
}
}
if err != nil {
serveError(writer, "Received 1 or more errors while trying to delete post files",
http.StatusInternalServerError, wantsJSON, errEv.Array("errors", errArr))
errEv.Array("errors", errArr).Caller().Msg("Received 1 or more errors while trying to delete post files")
server.ServeError(writer,
server.NewServerError("Received 1 or more errors while trying to delete post files", http.StatusInternalServerError),
wantsJSON, nil)
return false
}
_, err = gcsql.ExecTimeoutSQL(nil, deleteFilesSQL, deleteIDs...)
if err != nil {
serveError(writer, "Unable to delete file entries from database",
http.StatusInternalServerError, wantsJSON, errEv.Err(err).Caller())
errEv.Err(err).Caller().Msg("Unable to delete file entries from database")
server.ServeError(writer,
server.NewServerError("Unable to delete file entries from database", http.StatusInternalServerError),
wantsJSON, nil)
return false
}
return true

View file

@ -24,21 +24,24 @@ import (
func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.ResponseWriter, request *http.Request) {
password := request.PostFormValue("password")
wantsJSON := serverutil.IsRequestingJSON(request)
infoEv, errEv := gcutil.LogRequest(request)
defer gcutil.LogDiscard(infoEv, errEv)
infoEv, warnEv, errEv := gcutil.LogRequest(request)
defer gcutil.LogDiscard(infoEv, warnEv, errEv)
if editBtn == "Edit post" {
var err error
if len(checkedPosts) == 0 {
warnEv.Msg("No post selected")
server.ServeError(writer, server.NewServerError("You need to select one post to edit", http.StatusBadRequest), wantsJSON, nil)
return
} else if len(checkedPosts) > 1 {
warnEv.Msg("Multiple posts selected")
server.ServeError(writer, server.NewServerError("You can only edit one post at a time", http.StatusBadRequest), wantsJSON, nil)
return
}
rank := manage.GetStaffRank(request)
if password == "" && rank == 0 {
warnEv.Msg("No password provided for post editing")
server.ServeError(writer, server.NewServerError("Password required for post editing", http.StatusUnauthorized), wantsJSON, nil)
return
}
@ -54,6 +57,7 @@ func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.Res
errEv.Int("postID", post.ID)
if post.Password != passwordMD5 && rank == 0 {
errEv.Msg("Wrong password")
server.ServeError(writer, server.NewServerError("Wrong password", http.StatusUnauthorized), wantsJSON, nil)
return
}
@ -65,6 +69,7 @@ func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.Res
return
}
if strings.Contains(string(post.Message), `<span class="dice-roll">`) && !config.GetBoardConfig(board.Dir).AllowDiceRerolls {
warnEv.Msg("Dice rerolls are not allowed on this board")
server.ServeError(writer, server.NewServerError("Unable to edit post, dice rerolls are not allowed on this board", http.StatusForbidden), wantsJSON, nil)
return
}

View file

@ -26,17 +26,15 @@ func moveThread(checkedPosts []int, moveBtn string, doMove string, writer http.R
passwordMD5 = gcutil.Md5Sum(password)
}
wantsJSON := serverutil.IsRequestingJSON(request)
infoEv, errEv := gcutil.LogRequest(request)
defer func() {
errEv.Discard()
infoEv.Discard()
}()
infoEv, warnEv, errEv := gcutil.LogRequest(request)
defer gcutil.LogDiscard(infoEv, warnEv, errEv)
rank := manage.GetStaffRank(request)
if password == "" && rank == 0 {
errEv.Msg("Thread move request rejected, non-staff didn't provide a password")
writer.WriteHeader(http.StatusBadRequest)
server.ServeError(writer, "Password required for post moving", wantsJSON, nil)
warnEv.Msg("Thread move request rejected, non-staff didn't provide a password")
server.ServeError(writer,
server.NewServerError("Password required for post moving", http.StatusBadRequest),
wantsJSON, nil)
return
}
@ -44,32 +42,37 @@ func moveThread(checkedPosts []int, moveBtn string, doMove string, writer http.R
// user clicked on move thread button on board or thread page
if len(checkedPosts) == 0 {
server.ServeError(writer, "You need to select one thread to move.", wantsJSON, nil)
warnEv.Msg("Thread move request rejected, no posts selected")
server.ServeError(writer,
server.NewServerError("You need to select one thread to move.", http.StatusBadRequest),
wantsJSON, nil)
return
} else if len(checkedPosts) > 1 {
server.ServeError(writer, "You can only move one thread at a time.", wantsJSON, nil)
warnEv.Msg("Thread move request rejected, more than one post selected")
server.ServeError(writer, server.NewServerError("You can only move one thread at a time.", http.StatusBadRequest), wantsJSON, nil)
return
}
post, err := gcsql.GetPostFromID(checkedPosts[0], true)
gcutil.LogInt("postid", checkedPosts[0], errEv, infoEv)
gcutil.LogInt("postid", checkedPosts[0], errEv, warnEv, infoEv)
if err != nil {
errEv.Err(err).Caller().Msg("Error getting post from ID")
server.ServeError(writer, err.Error(), wantsJSON, nil)
server.ServeError(writer, server.NewServerError(err.Error(), http.StatusInternalServerError), wantsJSON, nil)
return
}
if !post.IsTopPost {
server.ServeError(writer, "You appear to be trying to move a post that is not the top post in the thread", wantsJSON, map[string]any{
"postid": checkedPosts[0],
})
warnEv.Msg("Thread move request rejected, selected post is not top post")
server.ServeError(writer,
server.NewServerError("You appear to be trying to move a post that is not the top post in the thread", http.StatusBadRequest),
wantsJSON, map[string]any{"postid": checkedPosts[0]})
return
}
srcBoardID, err := strconv.Atoi(request.PostFormValue("boardid"))
if err != nil {
errEv.Err(err).Caller().
warnEv.Err(err).Caller().
Str("srcBoardIDstr", request.PostFormValue("boardid")).Send()
server.ServeError(writer, fmt.Sprintf("Invalid or missing boarid: %q", request.PostFormValue("boardid")), wantsJSON, map[string]any{
server.ServeError(writer, server.NewServerError("Invalid or missing boarid", http.StatusBadRequest), wantsJSON, map[string]any{
"boardid": srcBoardID,
})
return
@ -94,7 +97,7 @@ func moveThread(checkedPosts []int, moveBtn string, doMove string, writer http.R
"srcBoard": srcBoard,
}, buf, "text/html"); err != nil {
errEv.Err(err).Caller().Send()
server.ServeError(writer, err.Error(), wantsJSON, nil)
server.ServeError(writer, server.NewServerError(err.Error(), http.StatusInternalServerError), wantsJSON, nil)
return
}
writer.Write(buf.Bytes())
@ -152,8 +155,7 @@ func moveThread(checkedPosts []int, moveBtn string, doMove string, writer http.R
if err != nil {
errEv.Err(err).Caller().
Int("destBoardID", destBoardID).Send()
writer.WriteHeader(http.StatusInternalServerError)
server.ServeError(writer, err.Error(), wantsJSON, map[string]any{
server.ServeError(writer, server.NewServerError(err.Error(), http.StatusInternalServerError), wantsJSON, map[string]any{
"destboardid": destBoardID,
})
return
@ -163,16 +165,15 @@ func moveThread(checkedPosts []int, moveBtn string, doMove string, writer http.R
post, err := gcsql.GetPostFromID(postID, true)
if err != nil {
errEv.Err(err).Caller().Send()
writer.WriteHeader(http.StatusInternalServerError)
server.ServeError(writer, err.Error(), wantsJSON, map[string]any{
server.ServeError(writer, server.NewServerError(err.Error(), http.StatusInternalServerError), wantsJSON, map[string]any{
"postid": postID,
})
return
}
if passwordMD5 != post.Password && rank == 0 {
errEv.Msg("Wrong password")
server.ServeError(writer, "Wrong password", wantsJSON, nil)
warnEv.Msg("Wrong password")
server.ServeError(writer, server.NewServerError("Wrong password", http.StatusUnauthorized), wantsJSON, nil)
return
}

View file

@ -161,19 +161,20 @@ func LogAccess(request *http.Request) *zerolog.Event {
// LogRequest returns info and error level zerolog events with the requester's
// IP, the user-agent string, and the requested path and HTTP method
func LogRequest(request *http.Request) (*zerolog.Event, *zerolog.Event) {
func LogRequest(request *http.Request) (*zerolog.Event, *zerolog.Event, *zerolog.Event) {
infoEv := logger.Info()
warnEv := logger.Warn()
errEv := logger.Error()
if request != nil {
LogStr("IP", GetRealIP(request), infoEv, errEv)
LogStr("path", request.URL.Path, infoEv, errEv)
LogStr("method", request.Method, infoEv, errEv)
LogStr("IP", GetRealIP(request), infoEv, warnEv, errEv)
LogStr("path", request.URL.Path, infoEv, warnEv, errEv)
LogStr("method", request.Method, infoEv, warnEv, errEv)
ua := request.UserAgent()
if ua != "" {
LogStr("userAgent", ua)
}
}
return infoEv, errEv
return infoEv, warnEv, errEv
}
func LogError(err error) *zerolog.Event {

View file

@ -42,13 +42,13 @@ func setupManageFunction(action *Action) bunrouter.HandlerFunc {
request := req.Request
wantsJSON := serverutil.IsRequestingJSON(request)
accessEv := gcutil.LogAccess(request)
infoEv, errEv := gcutil.LogRequest(request)
defer gcutil.LogDiscard(infoEv, accessEv, errEv)
infoEv, warnEv, errEv := gcutil.LogRequest(request)
defer gcutil.LogDiscard(infoEv, accessEv, warnEv, errEv)
gcutil.LogStr("action", action.ID, infoEv, accessEv, errEv)
if err = req.Request.ParseForm(); err != nil {
errEv.Err(err).Caller().Msg("Error parsing form data")
server.ServeError(writer, "Error parsing form data: "+err.Error(), wantsJSON, map[string]any{
server.ServeError(writer, server.NewServerError("Error parsing form data: "+err.Error(), http.StatusBadRequest), wantsJSON, map[string]any{
"action": action.ID,
})
return
@ -62,6 +62,7 @@ func setupManageFunction(action *Action) bunrouter.HandlerFunc {
return
}
gcutil.LogStr("staff", staff.Username, infoEv, accessEv, errEv)
gcutil.LogInt("rank", staff.Rank, infoEv, accessEv, errEv)
actionCB := action.Callback
pageTitle := getPageTitle(action.ID, staff)
@ -71,10 +72,7 @@ func setupManageFunction(action *Action) bunrouter.HandlerFunc {
actionCB = loginCallback
} else if staff.Rank < action.Permissions {
writer.WriteHeader(http.StatusForbidden)
gcutil.LogWarning().
Str("ip", gcutil.GetRealIP(request)).
Str("userAgent", request.UserAgent()).
Int("rank", staff.Rank).
warnEv.
Str("action", action.ID).
Int("requiredRank", action.Permissions).
Msg("Staff requested page with insufficient permissions")

View file

@ -36,34 +36,30 @@ var (
func createSession(key, username, password string, request *http.Request, writer http.ResponseWriter) error {
domain := request.Host
infoEv, errEv := gcutil.LogRequest(request)
defer func() {
infoEv.Discard()
errEv.Discard()
}()
infoEv, warnEv, errEv := gcutil.LogRequest(request)
defer gcutil.LogDiscard(infoEv, warnEv, errEv)
gcutil.LogStr("staff", username, infoEv, warnEv, errEv)
if strings.Contains(domain, ":") {
domain, _, err := net.SplitHostPort(domain)
if err != nil {
errEv.Err(err).Caller().Str("host", domain).Send()
warnEv.Err(err).Caller().Str("host", domain).Send()
return server.NewServerError("Invalid request host", http.StatusBadRequest)
}
}
refererResult, err := serverutil.CheckReferer(request)
if err != nil {
errEv.Err(err).Caller().
Str("staff", username).
warnEv.Err(err).Caller().
Str("referer", request.Referer()).
Msg("Error checking referer")
Msg("Invalid referer")
return err
}
if refererResult != serverutil.InternalReferer {
gcutil.LogWarning().
warnEv.
Int("refererResult", int(refererResult)).
Str("referer", request.Referer()).
Str("SiteHost", config.GetSystemCriticalConfig().SiteHost).
Str("staff", username).
Msg("Rejected login from possible spambot")
return serverutil.ErrSpambot
}
@ -80,7 +76,7 @@ func createSession(key, username, password string, request *http.Request, writer
err = bcrypt.CompareHashAndPassword([]byte(staff.PasswordChecksum), []byte(password))
if err == bcrypt.ErrMismatchedHashAndPassword {
// password mismatch
errEv.Caller().Msg("Invalid password")
warnEv.Caller().Msg("Invalid password")
return ErrBadCredentials
}

View file

@ -258,7 +258,9 @@ func getRedirectURL(post *gcsql.Post, board *gcsql.Board, request *http.Request)
// MakePost is called when a user accesses /post. Parse form data, then insert and build
func MakePost(writer http.ResponseWriter, request *http.Request) {
infoEv, errEv := gcutil.LogRequest(request)
infoEv, warnEv, errEv := gcutil.LogRequest(request)
defer gcutil.LogDiscard(infoEv, warnEv, errEv)
err := request.ParseMultipartForm(maxFormBytes)
if err != nil {
errEv.Err(err).Caller().Msg("Error parsing form data")
@ -307,7 +309,9 @@ func MakePost(writer http.ResponseWriter, request *http.Request) {
boardidStr := request.PostFormValue("boardid")
boardID, err := strconv.Atoi(boardidStr)
if err != nil {
errEv.Str("boardid", boardidStr).Caller().Msg("Invalid boardid value")
errEv.Caller().
Str("boardid", boardidStr).
Msg("Invalid boardid value")
server.ServeError(writer, "Invalid form data (invalid boardid)", wantsJSON, map[string]any{
"boardid": boardidStr,
})
@ -325,6 +329,10 @@ func MakePost(writer http.ResponseWriter, request *http.Request) {
}
boardConfig := config.GetBoardConfig(board.Dir)
if boardConfig.Lockdown {
}
// do length-check, formatting, and wordfilters
if err = doFormatting(post, board, request, errEv); err != nil {
server.ServeError(writer, err.Error(), wantsJSON, nil)