refactor HTTP error.

This commit is contained in:
Miroslav Šedivý 2021-09-16 20:16:51 +02:00
parent d46c5d9d30
commit 4fa11e6a2a
15 changed files with 166 additions and 102 deletions

View File

@ -2,7 +2,6 @@ package members
import ( import (
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"net/http" "net/http"
@ -18,13 +17,13 @@ type MemberBulkUpdatePayload struct {
func (h *MembersHandler) membersBulkUpdate(w http.ResponseWriter, r *http.Request) { func (h *MembersHandler) membersBulkUpdate(w http.ResponseWriter, r *http.Request) {
bytes, err := io.ReadAll(r.Body) bytes, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpBadRequest(w).WithInternalErr(err).Msg("unable to read post body")
return return
} }
header := &MemberBulkUpdatePayload{} header := &MemberBulkUpdatePayload{}
if err := json.Unmarshal(bytes, &header); err != nil { if err := json.Unmarshal(bytes, &header); err != nil {
utils.HttpInternalServerError(w, err) utils.HttpBadRequest(w).WithInternalErr(err).Msg("unable to unmarshal payload")
return return
} }
@ -32,7 +31,7 @@ func (h *MembersHandler) membersBulkUpdate(w http.ResponseWriter, r *http.Reques
// TODO: Bulk select? // TODO: Bulk select?
profile, err := h.members.Select(memberId) profile, err := h.members.Select(memberId)
if err != nil { if err != nil {
utils.HttpInternalServerError(w, fmt.Sprintf("member %s: %v", memberId, err)) utils.HttpInternalServerError(w, err).WithInternalMsg("unable to select member profile").Msgf("failed to update member %s", memberId)
return return
} }
@ -41,12 +40,12 @@ func (h *MembersHandler) membersBulkUpdate(w http.ResponseWriter, r *http.Reques
} }
if err := json.Unmarshal(bytes, &body); err != nil { if err := json.Unmarshal(bytes, &body); err != nil {
utils.HttpInternalServerError(w, fmt.Sprintf("member %s: %v", memberId, err)) utils.HttpBadRequest(w).WithInternalErr(err).Msgf("unable to unmarshal payload for member %s", memberId)
return return
} }
if err := h.members.UpdateProfile(memberId, body.Profile); err != nil { if err := h.members.UpdateProfile(memberId, body.Profile); err != nil {
utils.HttpInternalServerError(w, fmt.Sprintf("member %s: %v", memberId, err)) utils.HttpInternalServerError(w, err).WithInternalMsg("unable to update member profile").Msgf("failed to update member %s", memberId)
return return
} }
} }

View File

@ -39,7 +39,7 @@ func (h *MembersHandler) membersList(w http.ResponseWriter, r *http.Request) {
entries, err := h.members.SelectAll(limit, offset) entries, err := h.members.SelectAll(limit, offset)
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).Send()
return return
} }
@ -72,21 +72,21 @@ func (h *MembersHandler) membersCreate(w http.ResponseWriter, r *http.Request) {
} }
if data.Username == "" { if data.Username == "" {
utils.HttpBadRequest(w, "username cannot be empty") utils.HttpBadRequest(w).Msg("username cannot be empty")
return return
} }
if data.Password == "" { if data.Password == "" {
utils.HttpBadRequest(w, "password cannot be empty") utils.HttpBadRequest(w).Msg("password cannot be empty")
return return
} }
id, err := h.members.Insert(data.Username, data.Password, data.Profile) id, err := h.members.Insert(data.Username, data.Password, data.Profile)
if err != nil { if err != nil {
if errors.Is(err, types.ErrMemberAlreadyExists) { if errors.Is(err, types.ErrMemberAlreadyExists) {
utils.HttpUnprocessableEntity(w, err) utils.HttpUnprocessableEntity(w).Msg("member already exists")
} else { } else {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).Send()
} }
return return
} }
@ -113,7 +113,7 @@ func (h *MembersHandler) membersUpdateProfile(w http.ResponseWriter, r *http.Req
} }
if err := h.members.UpdateProfile(member.ID, profile); err != nil { if err := h.members.UpdateProfile(member.ID, profile); err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).Send()
return return
} }
@ -129,7 +129,7 @@ func (h *MembersHandler) membersUpdatePassword(w http.ResponseWriter, r *http.Re
} }
if err := h.members.UpdatePassword(member.ID, data.Password); err != nil { if err := h.members.UpdatePassword(member.ID, data.Password); err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).Send()
return return
} }
@ -140,7 +140,7 @@ func (h *MembersHandler) membersDelete(w http.ResponseWriter, r *http.Request) {
member := GetMember(r) member := GetMember(r)
if err := h.members.Delete(member.ID); err != nil { if err := h.members.Delete(member.ID); err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).Send()
return return
} }

View File

@ -71,9 +71,9 @@ func (h *MembersHandler) ExtractMember(next http.Handler) http.Handler {
profile, err := h.members.Select(memberId) profile, err := h.members.Select(memberId)
if err != nil { if err != nil {
if errors.Is(err, types.ErrMemberDoesNotExist) { if errors.Is(err, types.ErrMemberDoesNotExist) {
utils.HttpNotFound(w, err) utils.HttpNotFound(w).Msg("member not found")
} else { } else {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).Send()
} }
return return

View File

@ -28,18 +28,18 @@ func (h *RoomHandler) boradcastStart(w http.ResponseWriter, r *http.Request) {
} }
if data.URL == "" { if data.URL == "" {
utils.HttpBadRequest(w, "missing broadcast URL") utils.HttpBadRequest(w).Msg("missing broadcast URL")
return return
} }
broadcast := h.capture.Broadcast() broadcast := h.capture.Broadcast()
if broadcast.Started() { if broadcast.Started() {
utils.HttpUnprocessableEntity(w, "server is already broadcasting") utils.HttpUnprocessableEntity(w).Msg("server is already broadcasting")
return return
} }
if err := broadcast.Start(data.URL); err != nil { if err := broadcast.Start(data.URL); err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).Send()
return return
} }
@ -56,7 +56,7 @@ func (h *RoomHandler) boradcastStart(w http.ResponseWriter, r *http.Request) {
func (h *RoomHandler) boradcastStop(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) boradcastStop(w http.ResponseWriter, r *http.Request) {
broadcast := h.capture.Broadcast() broadcast := h.capture.Broadcast()
if !broadcast.Started() { if !broadcast.Started() {
utils.HttpUnprocessableEntity(w, "server is not broadcasting") utils.HttpUnprocessableEntity(w).Msg("server is not broadcasting")
return return
} }

View File

@ -18,7 +18,7 @@ type ClipboardPayload struct {
func (h *RoomHandler) clipboardGetText(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) clipboardGetText(w http.ResponseWriter, r *http.Request) {
data, err := h.desktop.ClipboardGetText() data, err := h.desktop.ClipboardGetText()
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).Send()
return return
} }
@ -40,7 +40,7 @@ func (h *RoomHandler) clipboardSetText(w http.ResponseWriter, r *http.Request) {
}) })
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).Send()
return return
} }
@ -50,7 +50,7 @@ func (h *RoomHandler) clipboardSetText(w http.ResponseWriter, r *http.Request) {
func (h *RoomHandler) clipboardGetImage(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) clipboardGetImage(w http.ResponseWriter, r *http.Request) {
bytes, err := h.desktop.ClipboardGetBinary("image/png") bytes, err := h.desktop.ClipboardGetBinary("image/png")
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).Send()
return return
} }
@ -64,7 +64,7 @@ func (h *RoomHandler) clipboardGetImage(w http.ResponseWriter, r *http.Request)
func (h *RoomHandler) clipboardSetImage(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) clipboardSetImage(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(MAX_UPLOAD_SIZE) err := r.ParseMultipartForm(MAX_UPLOAD_SIZE)
if err != nil { if err != nil {
utils.HttpBadRequest(w, "failed to parse multipart form") utils.HttpBadRequest(w).WithInternalErr(err).Msg("failed to parse multipart form")
return return
} }
@ -73,7 +73,7 @@ func (h *RoomHandler) clipboardSetImage(w http.ResponseWriter, r *http.Request)
file, header, err := r.FormFile("file") file, header, err := r.FormFile("file")
if err != nil { if err != nil {
utils.HttpBadRequest(w, "no file received") utils.HttpBadRequest(w).WithInternalErr(err).Msg("no file received")
return return
} }
@ -81,20 +81,20 @@ func (h *RoomHandler) clipboardSetImage(w http.ResponseWriter, r *http.Request)
mime := header.Header.Get("Content-Type") mime := header.Header.Get("Content-Type")
if !strings.HasPrefix(mime, "image/") { if !strings.HasPrefix(mime, "image/") {
utils.HttpBadRequest(w, "file must be image") utils.HttpBadRequest(w).Msg("file must be image")
return return
} }
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
_, err = buffer.ReadFrom(file) _, err = buffer.ReadFrom(file)
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).WithInternalMsg("unable to read from uploaded file").Send()
return return
} }
err = h.desktop.ClipboardSetBinary("image/png", buffer.Bytes()) err = h.desktop.ClipboardSetBinary("image/png", buffer.Bytes())
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).WithInternalMsg("unable set image to clipboard").Send()
return return
} }
@ -104,7 +104,7 @@ func (h *RoomHandler) clipboardSetImage(w http.ResponseWriter, r *http.Request)
func (h *RoomHandler) clipboardGetTargets(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) clipboardGetTargets(w http.ResponseWriter, r *http.Request) {
targets, err := h.desktop.ClipboardGetTargets() targets, err := h.desktop.ClipboardGetTargets()
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).Send()
return return
} }

View File

@ -36,7 +36,7 @@ func (h *RoomHandler) controlStatus(w http.ResponseWriter, r *http.Request) {
func (h *RoomHandler) controlRequest(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) controlRequest(w http.ResponseWriter, r *http.Request) {
host := h.sessions.GetHost() host := h.sessions.GetHost()
if host != nil { if host != nil {
utils.HttpUnprocessableEntity(w, "there is already a host") utils.HttpUnprocessableEntity(w).Msg("there is already a host")
return return
} }
@ -49,7 +49,7 @@ func (h *RoomHandler) controlRequest(w http.ResponseWriter, r *http.Request) {
func (h *RoomHandler) controlRelease(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) controlRelease(w http.ResponseWriter, r *http.Request) {
session := auth.GetSession(r) session := auth.GetSession(r)
if !session.IsHost() { if !session.IsHost() {
utils.HttpUnprocessableEntity(w, "session is not the host") utils.HttpUnprocessableEntity(w).Msg("session is not the host")
return return
} }
@ -71,12 +71,12 @@ func (h *RoomHandler) controlGive(w http.ResponseWriter, r *http.Request) {
target, ok := h.sessions.Get(sessionId) target, ok := h.sessions.Get(sessionId)
if !ok { if !ok {
utils.HttpNotFound(w, "target session was not found") utils.HttpNotFound(w).Msg("target session was not found")
return return
} }
if !target.Profile().CanHost { if !target.Profile().CanHost {
utils.HttpBadRequest(w, "target session is not allowed to host") utils.HttpBadRequest(w).Msg("target session is not allowed to host")
return return
} }

View File

@ -90,7 +90,7 @@ func (h *RoomHandler) uploadMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session := auth.GetSession(r) session := auth.GetSession(r)
if !session.IsHost() && (!session.Profile().CanHost || !h.sessions.ImplicitHosting()) { if !session.IsHost() && (!session.Profile().CanHost || !h.sessions.ImplicitHosting()) {
utils.HttpForbidden(w, "without implicit hosting, only host can upload files") utils.HttpForbidden(w).Msg("without implicit hosting, only host can upload files")
} else { } else {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
} }

View File

@ -29,7 +29,7 @@ func (h *RoomHandler) keyboardMapSet(w http.ResponseWriter, r *http.Request) {
}) })
if err != nil { if err != nil {
utils.HttpInternalServerError(w, "unable to change keyboard map") utils.HttpInternalServerError(w, err).Send()
return return
} }
@ -40,7 +40,7 @@ func (h *RoomHandler) keyboardMapGet(w http.ResponseWriter, r *http.Request) {
data, err := h.desktop.GetKeyboardMap() data, err := h.desktop.GetKeyboardMap()
if err != nil { if err != nil {
utils.HttpInternalServerError(w, "unable to get keyboard map") utils.HttpInternalServerError(w, err).Send()
return return
} }

View File

@ -20,7 +20,7 @@ func (h *RoomHandler) screenConfiguration(w http.ResponseWriter, r *http.Request
size := h.desktop.GetScreenSize() size := h.desktop.GetScreenSize()
if size == nil { if size == nil {
utils.HttpInternalServerError(w, "unable to get screen configuration") utils.HttpInternalServerError(w, nil).WithInternalMsg("unable to get screen configuration").Send()
return return
} }
@ -42,7 +42,7 @@ func (h *RoomHandler) screenConfigurationChange(w http.ResponseWriter, r *http.R
Height: data.Height, Height: data.Height,
Rate: data.Rate, Rate: data.Rate,
}); err != nil { }); err != nil {
utils.HttpUnprocessableEntity(w, err) utils.HttpUnprocessableEntity(w).WithInternalErr(err).Msg("cannot set screen size")
return return
} }
@ -83,7 +83,7 @@ func (h *RoomHandler) screenShotGet(w http.ResponseWriter, r *http.Request) {
img := h.desktop.GetScreenshotImage() img := h.desktop.GetScreenshotImage()
bytes, err := utils.CreateJPGImage(img, quality) bytes, err := utils.CreateJPGImage(img, quality)
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).Send()
return return
} }
@ -96,13 +96,13 @@ func (h *RoomHandler) screenShotGet(w http.ResponseWriter, r *http.Request) {
func (h *RoomHandler) screenCastGet(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) screenCastGet(w http.ResponseWriter, r *http.Request) {
screencast := h.capture.Screencast() screencast := h.capture.Screencast()
if !screencast.Enabled() { if !screencast.Enabled() {
utils.HttpBadRequest(w, "screencast pipeline is not enabled") utils.HttpBadRequest(w).Msg("screencast pipeline is not enabled")
return return
} }
bytes, err := screencast.Image() bytes, err := screencast.Image()
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).Send()
return return
} }

View File

@ -10,13 +10,15 @@ import (
"demodesk/neko/internal/utils" "demodesk/neko/internal/utils"
) )
// TODO: Extract file uploading to custom utility.
// maximum upload size of 32 MB // maximum upload size of 32 MB
const maxUploadSize = 32 << 20 const maxUploadSize = 32 << 20
func (h *RoomHandler) uploadDrop(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) uploadDrop(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(maxUploadSize) err := r.ParseMultipartForm(maxUploadSize)
if err != nil { if err != nil {
utils.HttpBadRequest(w, "failed to parse multipart form") utils.HttpBadRequest(w).WithInternalErr(err).Msg("failed to parse multipart form")
return return
} }
@ -25,25 +27,25 @@ func (h *RoomHandler) uploadDrop(w http.ResponseWriter, r *http.Request) {
X, err := strconv.Atoi(r.FormValue("x")) X, err := strconv.Atoi(r.FormValue("x"))
if err != nil { if err != nil {
utils.HttpBadRequest(w, "no X coordinate received") utils.HttpBadRequest(w).WithInternalErr(err).Msg("no X coordinate received")
return return
} }
Y, err := strconv.Atoi(r.FormValue("y")) Y, err := strconv.Atoi(r.FormValue("y"))
if err != nil { if err != nil {
utils.HttpBadRequest(w, "no Y coordinate received") utils.HttpBadRequest(w).WithInternalErr(err).Msg("no Y coordinate received")
return return
} }
req_files := r.MultipartForm.File["files"] req_files := r.MultipartForm.File["files"]
if len(req_files) == 0 { if len(req_files) == 0 {
utils.HttpBadRequest(w, "no files received") utils.HttpBadRequest(w).Msg("no files received")
return return
} }
dir, err := os.MkdirTemp("", "neko-drop-*") dir, err := os.MkdirTemp("", "neko-drop-*")
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).WithInternalMsg("unable to create temporary directory").Send()
return return
} }
@ -53,7 +55,7 @@ func (h *RoomHandler) uploadDrop(w http.ResponseWriter, r *http.Request) {
srcFile, err := req_file.Open() srcFile, err := req_file.Open()
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).WithInternalMsg("unable to open uploaded file").Send()
return return
} }
@ -61,7 +63,7 @@ func (h *RoomHandler) uploadDrop(w http.ResponseWriter, r *http.Request) {
dstFile, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) dstFile, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).WithInternalMsg("unable to open destination file").Send()
return return
} }
@ -69,7 +71,7 @@ func (h *RoomHandler) uploadDrop(w http.ResponseWriter, r *http.Request) {
_, err = io.Copy(dstFile, srcFile) _, err = io.Copy(dstFile, srcFile)
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).WithInternalMsg("unable to copy uploaded file to destination file").Send()
return return
} }
@ -77,7 +79,7 @@ func (h *RoomHandler) uploadDrop(w http.ResponseWriter, r *http.Request) {
} }
if !h.desktop.DropFiles(X, Y, files) { if !h.desktop.DropFiles(X, Y, files) {
utils.HttpInternalServerError(w, "unable to drop files") utils.HttpInternalServerError(w, nil).WithInternalMsg("unable to drop files").Send()
return return
} }
@ -87,7 +89,7 @@ func (h *RoomHandler) uploadDrop(w http.ResponseWriter, r *http.Request) {
func (h *RoomHandler) uploadDialogPost(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) uploadDialogPost(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(maxUploadSize) err := r.ParseMultipartForm(maxUploadSize)
if err != nil { if err != nil {
utils.HttpBadRequest(w, "failed to parse multipart form") utils.HttpBadRequest(w).WithInternalErr(err).Msg("failed to parse multipart form")
return return
} }
@ -95,19 +97,19 @@ func (h *RoomHandler) uploadDialogPost(w http.ResponseWriter, r *http.Request) {
defer r.MultipartForm.RemoveAll() defer r.MultipartForm.RemoveAll()
if !h.desktop.IsFileChooserDialogOpened() { if !h.desktop.IsFileChooserDialogOpened() {
utils.HttpBadRequest(w, "open file chooser dialog first") utils.HttpUnprocessableEntity(w).Msg("file chooser dialog is not open")
return return
} }
req_files := r.MultipartForm.File["files"] req_files := r.MultipartForm.File["files"]
if len(req_files) == 0 { if len(req_files) == 0 {
utils.HttpBadRequest(w, "no files received") utils.HttpInternalServerError(w, err).WithInternalMsg("unable to copy uploaded file to destination file").Send()
return return
} }
dir, err := os.MkdirTemp("", "neko-dialog-*") dir, err := os.MkdirTemp("", "neko-dialog-*")
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).WithInternalMsg("unable to create temporary directory").Send()
return return
} }
@ -116,7 +118,7 @@ func (h *RoomHandler) uploadDialogPost(w http.ResponseWriter, r *http.Request) {
srcFile, err := req_file.Open() srcFile, err := req_file.Open()
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).WithInternalMsg("unable to open uploaded file").Send()
return return
} }
@ -124,7 +126,7 @@ func (h *RoomHandler) uploadDialogPost(w http.ResponseWriter, r *http.Request) {
dstFile, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) dstFile, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).WithInternalMsg("unable to open destination file").Send()
return return
} }
@ -132,13 +134,13 @@ func (h *RoomHandler) uploadDialogPost(w http.ResponseWriter, r *http.Request) {
_, err = io.Copy(dstFile, srcFile) _, err = io.Copy(dstFile, srcFile)
if err != nil { if err != nil {
utils.HttpInternalServerError(w, err) utils.HttpInternalServerError(w, err).WithInternalMsg("unable to copy uploaded file to destination file").Send()
return return
} }
} }
if err := h.desktop.HandleFileChooserDialog(dir); err != nil { if err := h.desktop.HandleFileChooserDialog(dir); err != nil {
utils.HttpInternalServerError(w, "unable to handle file chooser dialog") utils.HttpInternalServerError(w, err).WithInternalMsg("unable to handle file chooser dialog").Send()
return return
} }
@ -147,7 +149,7 @@ func (h *RoomHandler) uploadDialogPost(w http.ResponseWriter, r *http.Request) {
func (h *RoomHandler) uploadDialogClose(w http.ResponseWriter, r *http.Request) { func (h *RoomHandler) uploadDialogClose(w http.ResponseWriter, r *http.Request) {
if !h.desktop.IsFileChooserDialogOpened() { if !h.desktop.IsFileChooserDialogOpened() {
utils.HttpBadRequest(w, "file chooser dialog is not open") utils.HttpUnprocessableEntity(w).Msg("file chooser dialog is not open")
return return
} }

View File

@ -76,9 +76,9 @@ func (api *ApiManagerCtx) Authenticate(next http.Handler) http.Handler {
} }
if errors.Is(err, types.ErrSessionLoginDisabled) { if errors.Is(err, types.ErrSessionLoginDisabled) {
utils.HttpForbidden(w, err) utils.HttpForbidden(w).Msg("login is disabled for this session")
} else { } else {
utils.HttpUnauthorized(w, err) utils.HttpUnauthorized(w).WithInternalErr(err).Send()
} }
return return

View File

@ -28,7 +28,7 @@ func (api *ApiManagerCtx) Login(w http.ResponseWriter, r *http.Request) {
session, token, err := api.members.Login(data.Username, data.Password) session, token, err := api.members.Login(data.Username, data.Password)
if err != nil { if err != nil {
utils.HttpUnauthorized(w, err) utils.HttpUnauthorized(w).WithInternalErr(err).Send()
return return
} }
@ -52,7 +52,7 @@ func (api *ApiManagerCtx) Logout(w http.ResponseWriter, r *http.Request) {
err := api.members.Logout(session.ID()) err := api.members.Logout(session.ID())
if err != nil { if err != nil {
utils.HttpUnauthorized(w, err) utils.HttpUnauthorized(w).WithInternalErr(err).Send()
return return
} }

View File

@ -25,7 +25,7 @@ func AdminsOnly(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session := GetSession(r) session := GetSession(r)
if !session.Profile().IsAdmin { if !session.Profile().IsAdmin {
utils.HttpForbidden(w) utils.HttpForbidden(w).Msg("session is not admin")
} else { } else {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
} }
@ -36,7 +36,7 @@ func HostsOnly(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session := GetSession(r) session := GetSession(r)
if !session.IsHost() { if !session.IsHost() {
utils.HttpForbidden(w, "only host can do this") utils.HttpForbidden(w).Msg("session is not host")
} else { } else {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
} }
@ -47,7 +47,7 @@ func CanWatchOnly(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session := GetSession(r) session := GetSession(r)
if !session.Profile().CanWatch { if !session.Profile().CanWatch {
utils.HttpForbidden(w) utils.HttpForbidden(w).Msg("session cannot watch")
} else { } else {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
} }
@ -58,7 +58,7 @@ func CanHostOnly(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session := GetSession(r) session := GetSession(r)
if !session.Profile().CanHost { if !session.Profile().CanHost {
utils.HttpForbidden(w) utils.HttpForbidden(w).Msg("session cannot host")
} else { } else {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
} }
@ -69,7 +69,7 @@ func CanAccessClipboardOnly(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session := GetSession(r) session := GetSession(r)
if !session.Profile().CanAccessClipboard { if !session.Profile().CanAccessClipboard {
utils.HttpForbidden(w) utils.HttpForbidden(w).Msg("session cannot access clipboard")
} else { } else {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
} }

View File

@ -13,7 +13,6 @@ import (
"demodesk/neko/internal/config" "demodesk/neko/internal/config"
"demodesk/neko/internal/types" "demodesk/neko/internal/types"
"demodesk/neko/internal/utils"
) )
type HttpManagerCtx struct { type HttpManagerCtx struct {
@ -55,13 +54,13 @@ func New(WebSocketManager types.WebSocketManager, ApiManager types.ApiManager, c
if _, err := os.Stat(config.Static + r.URL.Path); !os.IsNotExist(err) { if _, err := os.Stat(config.Static + r.URL.Path); !os.IsNotExist(err) {
fs.ServeHTTP(w, r) fs.ServeHTTP(w, r)
} else { } else {
utils.HttpNotFound(w) http.NotFound(w, r)
} }
}) })
} }
router.NotFound(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { router.NotFound(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
utils.HttpNotFound(w) http.NotFound(w, r)
})) }))
return &HttpManagerCtx{ return &HttpManagerCtx{

View File

@ -9,16 +9,12 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
type ErrResponse struct {
Message string `json:"message"`
}
func HttpJsonRequest(w http.ResponseWriter, r *http.Request, res interface{}) bool { func HttpJsonRequest(w http.ResponseWriter, r *http.Request, res interface{}) bool {
if err := json.NewDecoder(r.Body).Decode(res); err != nil { if err := json.NewDecoder(r.Body).Decode(res); err != nil {
if err == io.EOF { if err == io.EOF {
HttpBadRequest(w, "no data provided") HttpBadRequest(w).WithInternalErr(err).Msg("no data provided")
} else { } else {
HttpBadRequest(w, err) HttpBadRequest(w).WithInternalErr(err).Msg("unable to parse provided data")
} }
return false return false
@ -27,21 +23,15 @@ func HttpJsonRequest(w http.ResponseWriter, r *http.Request, res interface{}) bo
return true return true
} }
func HttpJsonResponse(w http.ResponseWriter, status int, res interface{}) { func HttpJsonResponse(w http.ResponseWriter, code int, res interface{}) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status) w.WriteHeader(code)
if err := json.NewEncoder(w).Encode(res); err != nil { if err := json.NewEncoder(w).Encode(res); err != nil {
log.Err(err).Str("module", "http").Msg("sending http json response failed") log.Err(err).Str("module", "http").Msg("sending http json response failed")
} }
} }
func HttpError(w http.ResponseWriter, status int, res interface{}) {
HttpJsonResponse(w, status, &ErrResponse{
Message: fmt.Sprint(res),
})
}
func HttpSuccess(w http.ResponseWriter, res ...interface{}) { func HttpSuccess(w http.ResponseWriter, res ...interface{}) {
if len(res) == 0 { if len(res) == 0 {
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
@ -50,34 +40,108 @@ func HttpSuccess(w http.ResponseWriter, res ...interface{}) {
} }
} }
func HttpBadRequest(w http.ResponseWriter, res ...interface{}) { // HTTPError is an error with a message and an HTTP status code.
defHttpError(w, http.StatusBadRequest, "bad request", res...) type HTTPError struct {
Code int `json:"code"`
Message string `json:"message"`
InternalErr error `json:"-"`
InternalMsg string `json:"-"`
w http.ResponseWriter `json:"-"`
} }
func HttpUnauthorized(w http.ResponseWriter, res ...interface{}) { func (e *HTTPError) Error() string {
defHttpError(w, http.StatusUnauthorized, "invalid or missing access token", res...) if e.InternalMsg != "" {
return e.InternalMsg
}
return fmt.Sprintf("%d: %s", e.Code, e.Message)
} }
func HttpForbidden(w http.ResponseWriter, res ...interface{}) { func (e *HTTPError) Cause() error {
defHttpError(w, http.StatusForbidden, "access token does not have the required scope", res...) if e.InternalErr != nil {
return e.InternalErr
}
return e
} }
func HttpNotFound(w http.ResponseWriter, res ...interface{}) { // WithInternalErr adds internal error information to the error
defHttpError(w, http.StatusNotFound, "resource not found", res...) func (e *HTTPError) WithInternalErr(err error) *HTTPError {
e.InternalErr = err
return e
} }
func HttpUnprocessableEntity(w http.ResponseWriter, res ...interface{}) { // WithInternalMsg adds internal message information to the error
defHttpError(w, http.StatusUnprocessableEntity, "unprocessable entity", res...) func (e *HTTPError) WithInternalMsg(msg string) *HTTPError {
e.InternalMsg = msg
return e
} }
func HttpInternalServerError(w http.ResponseWriter, res ...interface{}) { // WithInternalMsg adds internal formated message information to the error
defHttpError(w, http.StatusInternalServerError, "internal server error", res...) func (e *HTTPError) WithInternalMsgf(fmtStr string, args ...interface{}) *HTTPError {
e.InternalMsg = fmt.Sprintf(fmtStr, args...)
return e
} }
func defHttpError(w http.ResponseWriter, status int, text string, res ...interface{}) { // Sends error with custom formated message
if len(res) == 0 { func (e *HTTPError) Msgf(fmtSt string, args ...interface{}) {
HttpError(w, status, text) e.Message = fmt.Sprintf(fmtSt, args...)
} else { e.Send()
HttpError(w, status, res[0]) }
// Sends error with custom message
func (e *HTTPError) Msg(str string) {
e.Message = str
e.Send()
}
// Sends error with default status text
func (e *HTTPError) Send() {
if e.Message == "" {
e.Message = http.StatusText(e.Code)
}
logger := log.Error().
Err(e.InternalErr).
Str("module", "http").
Int("code", e.Code)
message := e.Message
if e.InternalMsg != "" {
message = e.InternalMsg
}
logger.Msg(message)
HttpJsonResponse(e.w, e.Code, e)
}
func HttpError(w http.ResponseWriter, code int) *HTTPError {
return &HTTPError{
Code: code,
w: w,
} }
} }
func HttpBadRequest(w http.ResponseWriter) *HTTPError {
return HttpError(w, http.StatusBadRequest)
}
func HttpUnauthorized(w http.ResponseWriter) *HTTPError {
return HttpError(w, http.StatusUnauthorized)
}
func HttpForbidden(w http.ResponseWriter) *HTTPError {
return HttpError(w, http.StatusForbidden)
}
func HttpNotFound(w http.ResponseWriter) *HTTPError {
return HttpError(w, http.StatusNotFound)
}
func HttpUnprocessableEntity(w http.ResponseWriter) *HTTPError {
return HttpError(w, http.StatusUnprocessableEntity)
}
func HttpInternalServerError(w http.ResponseWriter, err error) *HTTPError {
return HttpError(w, http.StatusInternalServerError).WithInternalErr(err)
}