fix(security): list items need write access to their list (FLAW-27)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 59s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 59s
Creating, checking, editing, deleting, reordering and clearing list items did not check the list at all: any logged-in user with a list or item ID could change it, viewers of shared lists as well. All item endpoints and converting a thought into a list item now require owner or editor rights and that the item belongs to the list (404 without access, 403 for viewers). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
11c14fa9ff
commit
3900c6104e
5 files changed
+125
No files matched your search
@@ -2,12 +2,14 @@ package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/sandrodach/mindsift/internal/auth"
|
||||
"github.com/sandrodach/mindsift/internal/domain"
|
||||
"github.com/sandrodach/mindsift/internal/storage"
|
||||
)
|
||||
|
||||
// --- REALTIME LISTS & SHARING ---
|
||||
@@ -138,6 +140,9 @@ func (s *Server) handleDeleteListShare(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) handleCreateListItem(w http.ResponseWriter, r *http.Request) {
|
||||
userID := auth.GetUserID(r.Context())
|
||||
listID := chi.URLParam(r, "id")
|
||||
if !s.listItemsWriteAllowed(w, r, "") {
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Content string `json:"content"`
|
||||
Category string `json:"category"`
|
||||
@@ -162,6 +167,9 @@ func (s *Server) handleToggleListItem(w http.ResponseWriter, r *http.Request) {
|
||||
userID := auth.GetUserID(r.Context())
|
||||
listID := chi.URLParam(r, "id")
|
||||
itemID := chi.URLParam(r, "itemId")
|
||||
if !s.listItemsWriteAllowed(w, r, itemID) {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
IsCompleted bool `json:"isCompleted"`
|
||||
@@ -190,6 +198,9 @@ func (s *Server) handleToggleListItem(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) handleUpdateListItem(w http.ResponseWriter, r *http.Request) {
|
||||
listID := chi.URLParam(r, "id")
|
||||
itemID := chi.URLParam(r, "itemId")
|
||||
if !s.listItemsWriteAllowed(w, r, itemID) {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Content *string `json:"content"`
|
||||
@@ -214,6 +225,9 @@ func (s *Server) handleUpdateListItem(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) handleDeleteListItem(w http.ResponseWriter, r *http.Request) {
|
||||
listID := chi.URLParam(r, "id")
|
||||
itemID := chi.URLParam(r, "itemId")
|
||||
if !s.listItemsWriteAllowed(w, r, itemID) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err := s.store.DeleteListItem(itemID)
|
||||
if err != nil {
|
||||
@@ -230,6 +244,9 @@ func (s *Server) handleDeleteListItem(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *Server) handleClearCompletedItems(w http.ResponseWriter, r *http.Request) {
|
||||
listID := chi.URLParam(r, "id")
|
||||
if !s.listItemsWriteAllowed(w, r, "") {
|
||||
return
|
||||
}
|
||||
removed, err := s.store.ClearCompletedListItems(listID)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
@@ -245,6 +262,9 @@ func (s *Server) handleClearCompletedItems(w http.ResponseWriter, r *http.Reques
|
||||
|
||||
func (s *Server) handleReorderListItems(w http.ResponseWriter, r *http.Request) {
|
||||
listID := chi.URLParam(r, "id")
|
||||
if !s.listItemsWriteAllowed(w, r, "") {
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Items []domain.ListItemReorderItem `json:"items"`
|
||||
}
|
||||
@@ -264,3 +284,18 @@ func (s *Server) handleReorderListItems(w http.ResponseWriter, r *http.Request)
|
||||
})
|
||||
writeJSON(w, http.StatusOK, map[string]bool{"success": true})
|
||||
}
|
||||
|
||||
// listItemsWriteAllowed answers 404 (no access, or item not in this list) or 403 (viewer) and returns
|
||||
// false; every request changing list items goes through it.
|
||||
func (s *Server) listItemsWriteAllowed(w http.ResponseWriter, r *http.Request, itemID string) bool {
|
||||
err := s.store.EnsureListItemsWritable(auth.GetUserID(r.Context()), chi.URLParam(r, "id"), itemID)
|
||||
switch {
|
||||
case err == nil:
|
||||
return true
|
||||
case errors.Is(err, storage.ErrListReadOnly):
|
||||
writeError(w, http.StatusForbidden, err.Error())
|
||||
default:
|
||||
writeError(w, http.StatusNotFound, err.Error())
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in new issue
Block a user