Remove server API key fallback for client requests

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.
This commit is contained in:
2026-02-01 12:00:38 -05:00
parent f5124bdda2
commit 26c9a72def
2 changed files with 12 additions and 26 deletions

View File

@@ -85,7 +85,7 @@ public class JellyfinProxyServiceTests
} }
[Fact] [Fact]
public async Task GetJsonAsync_IncludesAuthHeader() public async Task GetJsonAsync_WithoutClientHeaders_SendsNoAuth()
{ {
// Arrange // Arrange
HttpRequestMessage? captured = null; HttpRequestMessage? captured = null;
@@ -102,13 +102,10 @@ public class JellyfinProxyServiceTests
// Act // Act
await _service.GetJsonAsync("Items"); await _service.GetJsonAsync("Items");
// Assert // Assert - Should NOT include auth when no client headers provided
Assert.NotNull(captured); Assert.NotNull(captured);
Assert.True(captured!.Headers.Contains("Authorization")); Assert.False(captured!.Headers.Contains("Authorization"));
var authHeader = captured.Headers.GetValues("Authorization").First(); Assert.False(captured.Headers.Contains("X-Emby-Authorization"));
Assert.Contains("MediaBrowser", authHeader);
Assert.Contains(_settings.ApiKey!, authHeader);
Assert.Contains(_settings.ClientName!, authHeader);
} }
[Fact] [Fact]

View File

@@ -205,18 +205,11 @@ public class JellyfinProxyService
_logger.LogWarning("✗ No client headers provided for {Url}", url); _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 (!authHeaderAdded)
{ {
if (!string.IsNullOrEmpty(_settings.ApiKey)) _logger.LogInformation("No client auth provided for {Url} - forwarding without auth", url);
{
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);
}
} }
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
@@ -333,16 +326,12 @@ public class JellyfinProxyService
} }
} }
// For non-auth requests without headers, use API key // DO NOT use server credentials as fallback
// For auth requests, client MUST provide their own client info // Exception: For auth endpoints, client provides their own credentials in the body
if (!authHeaderAdded && !endpoint.Contains("Authenticate", StringComparison.OrdinalIgnoreCase)) // For all other endpoints, if client doesn't provide auth, let Jellyfin reject it
if (!authHeaderAdded)
{ {
var clientAuthHeader = $"MediaBrowser Client=\"{_settings.ClientName}\", " + _logger.LogInformation("No client auth provided for POST {Url} - forwarding without auth", url);
$"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");
} }
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));