mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
The image endpoint only served a private playlist's cover when the request carried a token identifying its owner or an admin. But clients fetch cover URLs without credentials — real Jellyfin's image routes are anonymous — so every private playlist rendered the generic placeholder in Jellyfin clients (observed in production), while the same covers displayed fine through the always-authenticated Subsonic/native APIs. Drop the gate and serve playlist covers like album/artist/track artwork: playlist ids are unguessable without credentials, so anonymous access does not meaningfully expose private playlist contents.
59 lines
2.1 KiB
Go
59 lines
2.1 KiB
Go
package e2e
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/navidrome/navidrome/model/request"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
// The image endpoint is public and resolves artwork under an elevated (admin) context. The suite
|
|
// wires a spyArtwork that captures the resolved ArtworkID and the context, so these tests assert
|
|
// resolution and elevation without needing real image processing.
|
|
var _ = Describe("Item images", func() {
|
|
BeforeEach(func() { setupTestDB() })
|
|
|
|
It("resolves an album's Primary image", func() {
|
|
id := albumID("Abbey Road")
|
|
w := get("/Items/" + enc(id) + "/Images/Primary")
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
Expect(w.Body.String()).To(Equal("IMG"))
|
|
Expect(artworkSpy.lastID).To(ContainSubstring(id))
|
|
})
|
|
|
|
It("resolves an artist's Primary image", func() {
|
|
id := artistID("Miles Davis")
|
|
Expect(get("/Items/" + enc(id) + "/Images/Primary").Code).To(Equal(http.StatusOK))
|
|
Expect(artworkSpy.lastID).To(ContainSubstring(id))
|
|
})
|
|
|
|
It("resolves a private playlist's cover under an elevated context", func() {
|
|
// The route carries no user in ctx (public); resolution runs elevated so the visibility
|
|
// filter doesn't eat the cover.
|
|
plID := createPlaylist("Private Mix", nil)
|
|
w := get("/Items/" + enc(plID) + "/Images/Primary")
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
Expect(artworkSpy.lastID).To(ContainSubstring(plID))
|
|
|
|
u, ok := request.UserFrom(artworkSpy.lastCtx)
|
|
Expect(ok).To(BeTrue())
|
|
Expect(u.IsAdmin).To(BeTrue())
|
|
})
|
|
|
|
It("serves images without authentication (public route)", func() {
|
|
id := albumID("IV")
|
|
w := rawReq("GET", "/Items/"+enc(id)+"/Images/Primary", "")
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
Expect(w.Body.String()).To(Equal("IMG"))
|
|
})
|
|
|
|
// Covers are served regardless of playlist visibility — see getItemImage for the rationale.
|
|
It("resolves a private playlist's cover for an unauthenticated caller", func() {
|
|
plID := createPlaylist("Secret Mix", nil) // owned by admin, private
|
|
w := rawReq("GET", "/Items/"+enc(plID)+"/Images/Primary", "")
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
Expect(artworkSpy.lastID).To(ContainSubstring(plID))
|
|
})
|
|
})
|