jellyfin-news: update auth for Jellyfin v12.2
arnaucube git@arnaucube.com
Sat, 17 Oct 2026 22:27:05 +0200
7 files changed,
21 insertions(+),
7 deletions(-)
M
bin/box
→
bin/box
@@ -14,6 +14,17 @@ # incus network create incusbr0 ipv4.address=auto ipv4.nat=true ipv6.address=none
# incus profile device set default eth0 network incusbr0 # # +# - if the container does not have internet access, at the host do: +# sudo iptables -I DOCKER-USER 1 \ +# -i incusbr0 \ +# -s 10.159.38.0/24 \ +# -j ACCEPT +# +# sudo iptables -I DOCKER-USER 2 \ +# -o incusbr0 \ +# -d 10.159.38.0/24 \ +# -m conntrack --ctstate RELATED,ESTABLISHED \ +# -j ACCEPT set -e
M
nixos/common-configuration.nix
→
nixos/common-configuration.nix
@@ -97,6 +97,7 @@ pdfgrep
keynav screenfetch libnotify + tree # Editors and terminals neovim
M
server-dockers/jellyfin-news/README.md
→
server-dockers/jellyfin-news/README.md
@@ -31,6 +31,8 @@ ```sh
cp .env.example .env ``` +Jellyfin requests authenticate with the supported `Authorization: MediaBrowser Token="…"` header, including on Jellyfin 12 with legacy authentication disabled. Keep using your existing `JELLYFIN_API_KEY`. + ## Run with Docker Compose ```sh
M
server-dockers/jellyfin-news/jellyfin.go
→
server-dockers/jellyfin-news/jellyfin.go
@@ -74,7 +74,7 @@ req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/Items?"+params.Encode(), nil)
if err != nil { return nil, err } - req.Header.Set("X-Emby-Token", c.apiKey) + req.Header.Set("Authorization", `MediaBrowser Token="`+c.apiKey+`"`) resp, err := c.http.Do(req) if err != nil { return nil, fmt.Errorf("query Jellyfin: %w", err)@@ -132,7 +132,7 @@ req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/Items?"+params.Encode(), nil)
if err != nil { return "", err } - req.Header.Set("X-Emby-Token", c.apiKey) + req.Header.Set("Authorization", `MediaBrowser Token="`+c.apiKey+`"`) resp, err := c.http.Do(req) if err != nil { return "", fmt.Errorf("search Jellyfin: %w", err)
M
server-dockers/jellyfin-news/jellyfin_test.go
→
server-dockers/jellyfin-news/jellyfin_test.go
@@ -15,8 +15,8 @@ func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
func TestAddedBetween(t *testing.T) { httpClient := &http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) { - if r.Header.Get("X-Emby-Token") != "secret" { - t.Error("missing API key") + if r.Header.Get("Authorization") != `MediaBrowser Token="secret"` || r.Header.Get("X-Emby-Token") != "" { + t.Error("expected MediaBrowser API key authorization without legacy token header") } if r.URL.Query().Get("IncludeItemTypes") != "Movie,Episode" { t.Error("wrong item filter")@@ -45,7 +45,7 @@
func TestFindPosterTitleMatchesKindNameAndYear(t *testing.T) { client, err := New("http://jellyfin.test", "secret", "user", &http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) { q := r.URL.Query() - if r.URL.Path != "/Items" || (q.Get("SearchTerm") != "Dune" && q.Get("SearchTerm") != "Missing") || q.Get("IncludeItemTypes") != "Movie" || q.Get("Recursive") != "true" || q.Get("UserId") != "user" || r.Header.Get("X-Emby-Token") != "secret" { + if r.URL.Path != "/Items" || (q.Get("SearchTerm") != "Dune" && q.Get("SearchTerm") != "Missing") || q.Get("IncludeItemTypes") != "Movie" || q.Get("Recursive") != "true" || q.Get("UserId") != "user" || r.Header.Get("Authorization") != `MediaBrowser Token="secret"` || r.Header.Get("X-Emby-Token") != "" { t.Errorf("unexpected search request: %s", r.URL) } body := `{"Items":[{"Id":"series","Type":"Series","Name":"Dune"},{"Id":"old","Type":"Movie","Name":"Dune","ProductionYear":1984},{"Id":"new","Type":"Movie","Name":"Dune","ProductionYear":2021}]}`
M
server-dockers/jellyfin-news/posters.go
→
server-dockers/jellyfin-news/posters.go
@@ -217,7 +217,7 @@ req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/Items/"+id+"/Images/Primary?maxWidth=480&maxHeight=720&format=Jpg", nil)
if err != nil { return nil, err } - req.Header.Set("X-Emby-Token", c.apiKey) + req.Header.Set("Authorization", `MediaBrowser Token="`+c.apiKey+`"`) resp, err := c.http.Do(req) if err != nil { return nil, err
M
server-dockers/jellyfin-news/posters_test.go
→
server-dockers/jellyfin-news/posters_test.go
@@ -156,7 +156,7 @@ {"oversized", 200, 502, make([]byte, (5<<20)+1)},
} { t.Run(tc.name, func(t *testing.T) { client, err := New("http://jellyfin.test/base", "secret", "", &http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) { - if r.URL.Path != "/base/Items/movie/Images/Primary" || r.Header.Get("X-Emby-Token") != "secret" || r.URL.Query().Get("maxWidth") != "480" { + if r.URL.Path != "/base/Items/movie/Images/Primary" || r.Header.Get("Authorization") != `MediaBrowser Token="secret"` || r.Header.Get("X-Emby-Token") != "" || r.URL.Query().Get("maxWidth") != "480" { t.Errorf("wrong poster request path, credentials, or size") } return &http.Response{StatusCode: tc.upstream, Body: io.NopCloser(bytes.NewReader(tc.body)), Header: make(http.Header)}, nil