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]
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]

View File

@@ -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"));