From 02ff078adf2aff8956acc982aef284a9f0fd84ee Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sat, 14 Feb 2026 00:12:15 -0500 Subject: [PATCH] fix: validate UUIDs in OPDS middleware before authentication - Add UUID validation in device_auth middleware for OPDS routes - Return 400 Bad Request for invalid device/book IDs instead of 401 - Remove redundant UUID validation from OPDS handlers (middleware handles it) --- internal/handlers/opds.go | 48 ++++++++---------------------- internal/middleware/device_auth.go | 18 +++++++++++ 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/internal/handlers/opds.go b/internal/handlers/opds.go index 15ab21a..2ed10ef 100644 --- a/internal/handlers/opds.go +++ b/internal/handlers/opds.go @@ -54,6 +54,7 @@ func (h *OPDSHandler) getBaseURLs(c echo.Context) (string, string, error) { // GetDeviceCatalog returns the OPDS catalog feed for a device func (h *OPDSHandler) GetDeviceCatalog(c echo.Context) error { deviceID := c.Param("deviceId") + page := c.QueryParam("page") perPage := c.QueryParam("per_page") includeFormat := c.QueryParam("include_format") @@ -217,6 +218,7 @@ func (h *OPDSHandler) GetDeviceCatalog(c echo.Context) error { // SearchDeviceCatalog searches the OPDS catalog for a device func (h *OPDSHandler) SearchDeviceCatalog(c echo.Context) error { deviceID := c.Param("deviceId") + query := c.QueryParam("q") if query == "" { @@ -341,16 +343,9 @@ func (h *OPDSHandler) DownloadBook(c echo.Context) error { format = "epub" } - // Parse IDs - deviceUUID, err := uuid.Parse(deviceID) - if err != nil { - return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid device id"}) - } - - bookUUID, err := uuid.Parse(bookID) - if err != nil { - return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid book id"}) - } + // Parse IDs (middleware guarantees valid UUIDs) + deviceUUID, _ := uuid.Parse(deviceID) + bookUUID, _ := uuid.Parse(bookID) // Verify device exists device, err := h.db.GetDevice(c.Request().Context(), pgtype.UUID{Bytes: deviceUUID, Valid: true}) @@ -484,16 +479,9 @@ func (h *OPDSHandler) GetCoverImage(c echo.Context) error { deviceID := c.Param("deviceId") bookID := c.Param("bookId") - // Parse IDs - deviceUUID, err := uuid.Parse(deviceID) - if err != nil { - return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid device id"}) - } - - bookUUID, err := uuid.Parse(bookID) - if err != nil { - return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid book id"}) - } + // Parse IDs (middleware guarantees valid UUIDs) + deviceUUID, _ := uuid.Parse(deviceID) + bookUUID, _ := uuid.Parse(bookID) // Verify device exists device, err := h.db.GetDevice(c.Request().Context(), pgtype.UUID{Bytes: deviceUUID, Valid: true}) @@ -580,11 +568,8 @@ func (h *OPDSHandler) GetDeviceNavigation(c echo.Context) error { return c.XML(http.StatusInternalServerError, opds.NewErrorFeed("Failed to get config")) } - // Parse device ID - deviceUUID, err := uuid.Parse(deviceID) - if err != nil { - return c.XML(http.StatusBadRequest, opds.NewErrorFeed("Invalid device ID")) - } + // Parse device ID (middleware guarantees valid UUID) + deviceUUID, _ := uuid.Parse(deviceID) // Verify device exists _, err = h.db.GetDevice(c.Request().Context(), pgtype.UUID{Bytes: deviceUUID, Valid: true}) @@ -629,16 +614,9 @@ func (h *OPDSHandler) ListFormats(c echo.Context) error { deviceID := c.Param("deviceId") bookID := c.Param("bookId") - // Parse IDs - deviceUUID, err := uuid.Parse(deviceID) - if err != nil { - return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid device id"}) - } - - bookUUID, err := uuid.Parse(bookID) - if err != nil { - return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid book id"}) - } + // Parse IDs (middleware guarantees valid UUIDs) + deviceUUID, _ := uuid.Parse(deviceID) + bookUUID, _ := uuid.Parse(bookID) // Verify device exists device, err := h.db.GetDevice(c.Request().Context(), pgtype.UUID{Bytes: deviceUUID, Valid: true}) diff --git a/internal/middleware/device_auth.go b/internal/middleware/device_auth.go index 9cebe04..444135b 100644 --- a/internal/middleware/device_auth.go +++ b/internal/middleware/device_auth.go @@ -42,6 +42,24 @@ func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerF var urlToken string var queryToken string + // For OPDS routes, validate deviceId is a valid UUID before authentication + // This allows returning 400 Bad Request for invalid UUIDs instead of 401 + if strings.HasPrefix(c.Request().URL.Path, "/opds/devices/") { + deviceID := c.Param("deviceId") + if deviceID != "" { + if _, err := uuid.Parse(deviceID); err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid device ID"}) + } + } + // Also validate bookId for download, cover, and formats endpoints + bookID := c.Param("bookId") + if bookID != "" { + if _, err := uuid.Parse(bookID); err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid book ID"}) + } + } + } + // Method 1: Try Bearer token header (KOReader, API clients, OPDS) authHeader := c.Request().Header.Get("Authorization") if authHeader != "" {