diff --git a/internal/api/media.go b/internal/api/media.go index ef6a3b3c..cca4afec 100644 --- a/internal/api/media.go +++ b/internal/api/media.go @@ -14,6 +14,8 @@ import ( "strconv" "strings" + "github.com/go-chi/chi/v5" + "git.fabledsword.com/bvandeusen/minstrel/internal/apierror" "git.fabledsword.com/bvandeusen/minstrel/internal/auth" "git.fabledsword.com/bvandeusen/minstrel/internal/coverart" @@ -163,15 +165,21 @@ func (h *handlers) streamAuthOk(r *http.Request, trackID string) bool { // permissive middleware the route is wrapped with) OR a signed token // (UPnP slice). streamAuthOk gates both paths. func (h *handlers) handleGetStream(w http.ResponseWriter, r *http.Request) { + // Auth check before the DB lookup: a 404 ahead of the auth check + // would let unauth callers probe which track IDs exist via the + // 404/401 response differential. streamAuthOk is keyed on the + // path's id directly (the HMAC token is signed over the same id + // string, so we don't need the resolved row yet). + rawID := chi.URLParam(r, "id") + if !h.streamAuthOk(r, rawID) { + writeErr(w, apierror.ErrUnauthorized) + return + } track, apiErr := resolveByID(r, "id", dbq.New(h.pool).GetTrackByID, "track") if apiErr != nil { writeErr(w, apiErr) return } - if !h.streamAuthOk(r, uuidToString(track.ID)) { - writeErr(w, apierror.ErrUnauthorized) - return - } f, err := os.Open(track.FilePath) if err != nil { // File vanished (scanner indexed it, filesystem lost it). Treat as