From aa9bd950f984cbd51c9b35a8f3a28e790278a6f8 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sat, 31 Oct 2026 15:40:21 -0400 Subject: [PATCH] fix(collections): gate the chain-order endpoint by ownership MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PUT /api/collections/:id/view-settings writes the row; it now demands UserOwnsCollection like its sibling config mutations (SetMatchMode, ReorderItems) — otherwise any signed-in user could flip another user's curated listening order. Discovered while documenting the endpoint: the sibling pattern exists precisely because the main PUT/DELETE routes lack it (their gap is noted for follow-up, not expanded here). --- internal/handlers/collections.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/internal/handlers/collections.go b/internal/handlers/collections.go index d8ce4137..dfba00e9 100644 --- a/internal/handlers/collections.go +++ b/internal/handlers/collections.go @@ -618,6 +618,17 @@ func (h *CollectionHandler) SetCollectionChainOrder(c *echo.Context) error { if err != nil { return c.JSON(http.StatusNotFound, map[string]string{"error": "collection not found"}) } + // Same owner gate as the sibling config mutations (SetMatchMode, + // ReorderItems) — my endpoint writes the row, so it must not let a + // non-owner flip someone else's chain order. + if u, ok := c.Get("user").(database.Users); ok { + owns, err := h.collectionService.UserOwnsCollection(c.Request().Context(), collectionID, uuid.UUID(u.ID.Bytes)) + if err != nil || !owns { + return c.JSON(http.StatusForbidden, map[string]string{"error": "only the collection owner can change it"}) + } + } else { + return c.JSON(http.StatusUnauthorized, map[string]string{"error": "no user in context"}) + } settings := map[string]interface{}{} if len(collection.ViewSettings) > 0 {