mirror of
https://github.com/navidrome/navidrome.git
synced 2026-11-02 00:17:49 +01:00
fix(jellyfin): advertise LocalAddress in the public system info handshake
Jellify (and other @jellyfin/sdk clients) that connect by raw address fall back to HTTP when TLS isn't available, then adopt the handshake's LocalAddress as their server base URL. We never populated it, so Jellify's SDK `api` object was undefined and sign-in crashed with "Cannot read property 'configuration' of undefined" (getUserApi(api!) with an undefined api). Over an HTTPS connection Jellify uses connectionType=hostname and never reads LocalAddress, which is why it worked while an HTTPS proxy was in front. Populate LocalAddress from the request — scheme + host (honoring X-Forwarded-*), plus the /jellyfin mount path — matching real Jellyfin, which always sends it. Export server.ServerAddress for the resolution.
This commit is contained in:
parent
24344bff55
commit
ac21c4e19c
3 changed files with 35 additions and 7 deletions
|
|
@ -4,11 +4,13 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/consts"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/server"
|
||||
"github.com/navidrome/navidrome/server/jellyfin/dto"
|
||||
)
|
||||
|
||||
|
|
@ -49,18 +51,29 @@ func (api *Router) serverID(ctx context.Context) string {
|
|||
return api.serverIDVal
|
||||
}
|
||||
|
||||
func (api *Router) publicInfo(ctx context.Context) dto.PublicSystemInfo {
|
||||
func (api *Router) publicInfo(r *http.Request) dto.PublicSystemInfo {
|
||||
return dto.PublicSystemInfo{
|
||||
LocalAddress: localAddress(r),
|
||||
ServerName: api.serverName(),
|
||||
Version: jellyfinVersion,
|
||||
ProductName: "Jellyfin Server",
|
||||
Id: api.serverID(ctx),
|
||||
Id: api.serverID(r.Context()),
|
||||
StartupWizardCompleted: true,
|
||||
}
|
||||
}
|
||||
|
||||
// localAddress reconstructs the base URL the client used to reach this Jellyfin API: scheme and
|
||||
// host from the request (honoring reverse-proxy X-Forwarded-* headers), plus the mount path. Real
|
||||
// Jellyfin advertises this as LocalAddress; a client connecting by raw address (Jellify over HTTP,
|
||||
// when TLS isn't available) adopts it as its server base URL. Without it, Jellify's SDK api
|
||||
// instance is undefined and sign-in crashes ("Cannot read property 'configuration' of undefined").
|
||||
func localAddress(r *http.Request) string {
|
||||
scheme, host := server.ServerAddress(r)
|
||||
return scheme + "://" + host + path.Join(conf.Server.BasePath, consts.URLPathJellyfinAPI)
|
||||
}
|
||||
|
||||
func (api *Router) getPublicSystemInfo(w http.ResponseWriter, r *http.Request) {
|
||||
api.ok(w, r, api.publicInfo(r.Context()))
|
||||
api.ok(w, r, api.publicInfo(r))
|
||||
}
|
||||
|
||||
// ping answers /System/Ping with a bare plain-text server name, not a JSON-quoted string:
|
||||
|
|
|
|||
|
|
@ -36,6 +36,21 @@ var _ = Describe("System", func() {
|
|||
Expect(info.ServerName).To(HavePrefix("Navidrome"))
|
||||
})
|
||||
|
||||
It("advertises a LocalAddress with the request scheme, host and Jellyfin base path", func() {
|
||||
DeferCleanup(configtest.SetupConfig())
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "/System/Info/Public", nil)
|
||||
r.Host = "music.example.com:4599"
|
||||
api.getPublicSystemInfo(w, r)
|
||||
|
||||
var info dto.PublicSystemInfo
|
||||
Expect(json.Unmarshal(w.Body.Bytes(), &info)).To(Succeed())
|
||||
// Jellify connecting over HTTP sets its server base URL from LocalAddress; without it the
|
||||
// SDK `api` is undefined and sign-in crashes. It must include the /jellyfin mount path.
|
||||
Expect(info.LocalAddress).To(Equal("http://music.example.com:4599/jellyfin"))
|
||||
})
|
||||
|
||||
It("responds to ping with the server name as plain text", func() {
|
||||
DeferCleanup(configtest.SetupConfig())
|
||||
conf.Server.Jellyfin.ServerName = ""
|
||||
|
|
|
|||
|
|
@ -202,10 +202,10 @@ func reqToCtx(key any, fn func(req *http.Request) any) func(http.Handler) http.H
|
|||
func serverAddressMiddleware(h http.Handler) http.Handler {
|
||||
// Define a new handler function that will be returned by this middleware function.
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
// Call the serverAddress function to get the scheme and host of the server
|
||||
// Call the ServerAddress function to get the scheme and host of the server
|
||||
// handling the request. If a host is found, modify the request object to use
|
||||
// that host and scheme instead of the original ones.
|
||||
if rScheme, rHost := serverAddress(r); rHost != "" {
|
||||
if rScheme, rHost := ServerAddress(r); rHost != "" {
|
||||
r.Host = rHost
|
||||
r.URL.Scheme = rScheme
|
||||
}
|
||||
|
|
@ -225,10 +225,10 @@ var (
|
|||
xForwardedScheme = http.CanonicalHeaderKey("X-Forwarded-Scheme")
|
||||
)
|
||||
|
||||
// serverAddress is a helper function that returns the scheme and host of the server
|
||||
// ServerAddress is a helper function that returns the scheme and host of the server
|
||||
// handling the given request, as determined by the presence of X-Forwarded-* headers
|
||||
// or the scheme and host of the request URL.
|
||||
func serverAddress(r *http.Request) (scheme, host string) {
|
||||
func ServerAddress(r *http.Request) (scheme, host string) {
|
||||
// Save the original request host for later comparison.
|
||||
origHost := r.Host
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue