🏡 index : quine-global/quintodrome.git

package e2e

import (
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/navidrome/navidrome/conf"
	"github.com/navidrome/navidrome/conf/configtest"
	"github.com/navidrome/navidrome/server/jellyfin/dto"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

var _ = Describe("QuickConnect", func() {
	BeforeEach(func() {
		setupTestDB()
		DeferCleanup(configtest.SetupConfig())
		conf.Server.Jellyfin.QuickConnect = true
	})

	clientReq := func(deviceID, method, path, body string) *httptest.ResponseRecorder {
		w := httptest.NewRecorder()
		r := httptest.NewRequest(method, path, strings.NewReader(body))
		r.Header.Set("Authorization", `MediaBrowser Client="Finamp", Device="Pixel 7", DeviceId="`+deviceID+`", Version="1.0"`)
		r.Header.Set("Content-Type", "application/json")
		router.ServeHTTP(w, r)
		return w
	}
	initiate := func() dto.QuickConnectResult {
		var pending dto.QuickConnectResult
		parseInto(clientReq("new-device", "POST", "/QuickConnect/Initiate", ""), &pending)
		Expect(pending.Authenticated).To(BeFalse())
		return pending
	}
	redeem := func(deviceID, secret string) *httptest.ResponseRecorder {
		return clientReq(deviceID, "POST", "/Users/AuthenticateWithQuickConnect", `{"Secret":"`+secret+`"}`)
	}

	It("signs a new client in with a code approved by a signed-in user", func() {
		Expect(rawReq("GET", "/QuickConnect/Enabled", "").Body.String()).To(MatchJSON("true"))
		pending := initiate()

		Expect(postAs(regularUser, "/QuickConnect/Authorize?Code="+pending.Code, "").Code).To(Equal(http.StatusOK))

		var status dto.QuickConnectResult
		parseInto(rawReq("GET", "/QuickConnect/Connect?Secret="+pending.Secret, ""), &status)
		Expect(status.Authenticated).To(BeTrue())

		// Android TV redeems with a different DeviceId than it initiated with.
		w := redeem("other-device", pending.Secret)
		Expect(w.Code).To(Equal(http.StatusOK))
		var res dto.AuthenticationResult
		parseInto(w, &res)
		Expect(res.User.Name).To(Equal(regularUser.UserName))
		Expect(res.SessionInfo).ToNot(BeNil())
		Expect(res.SessionInfo.DeviceId).To(Equal("other-device"))

		r := httptest.NewRequest("GET", "/Users/Me", nil)
		r.Header.Set("X-Emby-Token", res.AccessToken)
		me := httptest.NewRecorder()
		router.ServeHTTP(me, r)
		Expect(me.Code).To(Equal(http.StatusOK))

		Expect(redeem("new-device", pending.Secret).Code).To(Equal(http.StatusNotFound))
	})

	It("lets an admin approve a code for another user", func() {
		pending := initiate()
		Expect(post("/QuickConnect/Authorize?Code="+pending.Code+"&UserId="+enc(regularUser.ID), "").Code).
			To(Equal(http.StatusOK))

		var res dto.AuthenticationResult
		parseInto(redeem("new-device", pending.Secret), &res)
		Expect(res.User.Name).To(Equal(regularUser.UserName))
	})

	It("requires authentication to approve a code", func() {
		pending := initiate()
		Expect(rawReq("POST", "/QuickConnect/Authorize?Code="+pending.Code, "").Code).To(Equal(http.StatusUnauthorized))
	})

	It("answers 401 on every Quick Connect call when disabled", func() {
		conf.Server.Jellyfin.QuickConnect = false
		Expect(rawReq("GET", "/QuickConnect/Enabled", "").Body.String()).To(MatchJSON("false"))
		Expect(clientReq("new-device", "POST", "/QuickConnect/Initiate", "").Code).To(Equal(http.StatusUnauthorized))
		Expect(rawReq("GET", "/QuickConnect/Connect?Secret=x", "").Code).To(Equal(http.StatusUnauthorized))
		Expect(post("/QuickConnect/Authorize?Code=123456", "").Code).To(Equal(http.StatusUnauthorized))
		Expect(redeem("new-device", "x").Code).To(Equal(http.StatusUnauthorized))
	})
})