From 26c9a72def51b55ae039ff7f5fcf154a5cebd7ab Mon Sep 17 00:00:00 2001 From: Josh Patra Date: Sun, 1 Feb 2026 12:00:38 -0500 Subject: [PATCH] Remove server API key fallback for client requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SECURITY FIX: Stop using server API key when clients don't provide auth Before: If client sent no auth → proxy used server API key → gave them access After: If client sends no auth → proxy sends no auth → Jellyfin rejects (401) This ensures: - Unauthenticated users can't piggyback on server credentials - All actions are properly attributed to the actual user - Jellyfin's auth system works as intended - Server API key only used for internal operations (images, library detection) Updated test to reflect new behavior: GetJsonAsync without client headers should NOT add any authentication. --- allstarr.Tests/JellyfinProxyServiceTests.cs | 11 +++----- .../Services/Jellyfin/JellyfinProxyService.cs | 27 ++++++------------- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/allstarr.Tests/JellyfinProxyServiceTests.cs b/allstarr.Tests/JellyfinProxyServiceTests.cs index 6d268db..088581f 100644 --- a/allstarr.Tests/JellyfinProxyServiceTests.cs +++ b/allstarr.Tests/JellyfinProxyServiceTests.cs @@ -85,7 +85,7 @@ public class JellyfinProxyServiceTests } [Fact] - public async Task GetJsonAsync_IncludesAuthHeader() + public async Task GetJsonAsync_WithoutClientHeaders_SendsNoAuth() { // Arrange HttpRequestMessage? captured = null; @@ -102,13 +102,10 @@ public class JellyfinProxyServiceTests // Act await _service.GetJsonAsync("Items"); - // Assert + // Assert - Should NOT include auth when no client headers provided Assert.NotNull(captured); - Assert.True(captured!.Headers.Contains("Authorization")); - var authHeader = captured.Headers.GetValues("Authorization").First(); - Assert.Contains("MediaBrowser", authHeader); - Assert.Contains(_settings.ApiKey!, authHeader); - Assert.Contains(_settings.ClientName!, authHeader); + Assert.False(captured!.Headers.Contains("Authorization")); + Assert.False(captured.Headers.Contains("X-Emby-Authorization")); } [Fact] diff --git a/allstarr/Services/Jellyfin/JellyfinProxyService.cs b/allstarr/Services/Jellyfin/JellyfinProxyService.cs index 308dfd3..b21554c 100644 --- a/allstarr/Services/Jellyfin/JellyfinProxyService.cs +++ b/allstarr/Services/Jellyfin/JellyfinProxyService.cs @@ -205,18 +205,11 @@ public class JellyfinProxyService _logger.LogWarning("✗ No client headers provided for {Url}", url); } - // Use API key if no valid client auth was found + // DO NOT use server API key as fallback - let Jellyfin handle unauthenticated requests + // If client doesn't provide auth, they get what they deserve (401 from Jellyfin) if (!authHeaderAdded) { - if (!string.IsNullOrEmpty(_settings.ApiKey)) - { - request.Headers.Add("Authorization", GetAuthorizationHeader()); - _logger.LogInformation("→ Using API key for {Url}", url); - } - else - { - _logger.LogWarning("✗ No authentication available for {Url} - request will fail", url); - } + _logger.LogInformation("No client auth provided for {Url} - forwarding without auth", url); } request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); @@ -333,16 +326,12 @@ public class JellyfinProxyService } } - // For non-auth requests without headers, use API key - // For auth requests, client MUST provide their own client info - if (!authHeaderAdded && !endpoint.Contains("Authenticate", StringComparison.OrdinalIgnoreCase)) + // DO NOT use server credentials as fallback + // Exception: For auth endpoints, client provides their own credentials in the body + // For all other endpoints, if client doesn't provide auth, let Jellyfin reject it + if (!authHeaderAdded) { - var clientAuthHeader = $"MediaBrowser Client=\"{_settings.ClientName}\", " + - $"Device=\"{_settings.DeviceName}\", " + - $"DeviceId=\"{_settings.DeviceId}\", " + - $"Version=\"{_settings.ClientVersion}\""; - request.Headers.TryAddWithoutValidation("X-Emby-Authorization", clientAuthHeader); - _logger.LogDebug("Using server API key for non-auth request"); + _logger.LogInformation("No client auth provided for POST {Url} - forwarding without auth", url); } request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));