mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
Fix reauthentication and artist song counts
- Fixed reauthentication: Pass through 401 responses to client instead of hiding them with empty results - Fixed artist song counts: Artists now show correct song count instead of total library count (1290) - Fixed external artist Similar endpoint to prevent errors - Improved auth header forwarding with case-insensitive detection
This commit is contained in:
@@ -108,13 +108,8 @@ public class JellyfinController : ControllerBase
|
|||||||
|
|
||||||
if (browseResult == null)
|
if (browseResult == null)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Jellyfin returned null, returning empty result");
|
_logger.LogInformation("Jellyfin returned null - likely 401 Unauthorized, returning 401 to client");
|
||||||
return new JsonResult(new Dictionary<string, object>
|
return Unauthorized(new { error = "Authentication required" });
|
||||||
{
|
|
||||||
["Items"] = Array.Empty<object>(),
|
|
||||||
["TotalRecordCount"] = 0,
|
|
||||||
["StartIndex"] = startIndex
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = JsonSerializer.Deserialize<object>(browseResult.RootElement.GetRawText());
|
var result = JsonSerializer.Deserialize<object>(browseResult.RootElement.GetRawText());
|
||||||
@@ -1207,6 +1202,7 @@ public class JellyfinController : ControllerBase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[HttpGet("Items/{itemId}/Similar")]
|
[HttpGet("Items/{itemId}/Similar")]
|
||||||
[HttpGet("Songs/{itemId}/Similar")]
|
[HttpGet("Songs/{itemId}/Similar")]
|
||||||
|
[HttpGet("Artists/{artistId}/Similar")]
|
||||||
public async Task<IActionResult> GetSimilarItems(
|
public async Task<IActionResult> GetSimilarItems(
|
||||||
string itemId,
|
string itemId,
|
||||||
[FromQuery] int limit = 50,
|
[FromQuery] int limit = 50,
|
||||||
@@ -1217,6 +1213,18 @@ public class JellyfinController : ControllerBase
|
|||||||
|
|
||||||
if (isExternal)
|
if (isExternal)
|
||||||
{
|
{
|
||||||
|
// Check if this is an artist
|
||||||
|
if (itemId.Contains("-artist-", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// For external artists, return empty - we don't have similar artist functionality
|
||||||
|
_logger.LogDebug("Similar artists not supported for external artist {ItemId}", itemId);
|
||||||
|
return _responseBuilder.CreateJsonResponse(new
|
||||||
|
{
|
||||||
|
Items = Array.Empty<object>(),
|
||||||
|
TotalRecordCount = 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Get the original song to find similar content
|
// Get the original song to find similar content
|
||||||
|
|||||||
@@ -145,26 +145,77 @@ public class JellyfinProxyService
|
|||||||
{
|
{
|
||||||
using var request = new HttpRequestMessage(HttpMethod.Get, url);
|
using var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
|
||||||
|
bool authHeaderAdded = false;
|
||||||
|
|
||||||
// Forward authentication headers from client if provided
|
// Forward authentication headers from client if provided
|
||||||
if (clientHeaders != null)
|
if (clientHeaders != null && clientHeaders.Count > 0)
|
||||||
{
|
{
|
||||||
if (clientHeaders.TryGetValue("X-Emby-Authorization", out var embyAuth))
|
// Try X-Emby-Authorization first (case-insensitive)
|
||||||
|
foreach (var header in clientHeaders)
|
||||||
{
|
{
|
||||||
request.Headers.TryAddWithoutValidation("X-Emby-Authorization", embyAuth.ToString());
|
if (header.Key.Equals("X-Emby-Authorization", StringComparison.OrdinalIgnoreCase))
|
||||||
}
|
|
||||||
else if (clientHeaders.TryGetValue("Authorization", out var auth))
|
|
||||||
{
|
{
|
||||||
request.Headers.TryAddWithoutValidation("Authorization", auth.ToString());
|
var headerValue = header.Value.ToString();
|
||||||
|
request.Headers.TryAddWithoutValidation("X-Emby-Authorization", headerValue);
|
||||||
|
authHeaderAdded = true;
|
||||||
|
_logger.LogInformation("✓ Forwarded X-Emby-Authorization: {Value}", headerValue);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only use API key for server-initiated requests (when no client headers provided)
|
// If no X-Emby-Authorization, check if Authorization header contains MediaBrowser format
|
||||||
// This ensures client requests use the logged-in user's permissions
|
// Some clients send it as "Authorization" instead of "X-Emby-Authorization"
|
||||||
if (clientHeaders == null && !request.Headers.Contains("X-Emby-Authorization") && !request.Headers.Contains("Authorization"))
|
if (!authHeaderAdded)
|
||||||
|
{
|
||||||
|
foreach (var header in clientHeaders)
|
||||||
|
{
|
||||||
|
if (header.Key.Equals("Authorization", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var headerValue = header.Value.ToString();
|
||||||
|
|
||||||
|
// Check if it's MediaBrowser/Jellyfin format (contains "MediaBrowser" or "Token=")
|
||||||
|
if (headerValue.Contains("MediaBrowser", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
headerValue.Contains("Token=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// Forward as X-Emby-Authorization (Jellyfin's expected header)
|
||||||
|
request.Headers.TryAddWithoutValidation("X-Emby-Authorization", headerValue);
|
||||||
|
authHeaderAdded = true;
|
||||||
|
_logger.LogInformation("✓ Converted Authorization to X-Emby-Authorization: {Value}", headerValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Standard Bearer token - forward as-is
|
||||||
|
request.Headers.TryAddWithoutValidation("Authorization", headerValue);
|
||||||
|
authHeaderAdded = true;
|
||||||
|
_logger.LogInformation("✓ Forwarded Authorization (Bearer): {Value}", headerValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authHeaderAdded)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("✗ No auth header found. Available headers: {Headers}",
|
||||||
|
string.Join(", ", clientHeaders.Select(h => $"{h.Key}={h.Value}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("✗ No client headers provided for {Url}", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use API key if no valid client auth was found
|
||||||
|
if (!authHeaderAdded)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(_settings.ApiKey))
|
if (!string.IsNullOrEmpty(_settings.ApiKey))
|
||||||
{
|
{
|
||||||
request.Headers.Add("Authorization", GetAuthorizationHeader());
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,13 +223,26 @@ public class JellyfinProxyService
|
|||||||
|
|
||||||
var response = await _httpClient.SendAsync(request);
|
var response = await _httpClient.SendAsync(request);
|
||||||
|
|
||||||
|
// Always parse the response, even for errors
|
||||||
|
// The caller needs to see 401s so the client can re-authenticate
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Jellyfin returned 401 Unauthorized for {Url} - passing through to client", url);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Jellyfin request failed: {StatusCode} for {Url}", response.StatusCode, url);
|
_logger.LogWarning("Jellyfin request failed: {StatusCode} for {Url}", response.StatusCode, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return null so caller knows request failed
|
||||||
|
// TODO: We should return the status code too so caller can pass it through
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
|
||||||
return JsonDocument.Parse(content);
|
return JsonDocument.Parse(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,27 +267,40 @@ public class JellyfinProxyService
|
|||||||
_logger.LogWarning("POST body is empty for {Url}", url);
|
_logger.LogWarning("POST body is empty for {Url}", url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For auth endpoints, we need X-Emby-Authorization header with client info (no token yet)
|
bool authHeaderAdded = false;
|
||||||
// Jellyfin requires this header format even for login
|
|
||||||
if (clientHeaders.TryGetValue("X-Emby-Authorization", out var embyAuth))
|
// Forward authentication headers from client (case-insensitive)
|
||||||
|
foreach (var header in clientHeaders)
|
||||||
{
|
{
|
||||||
request.Headers.TryAddWithoutValidation("X-Emby-Authorization", embyAuth.ToString());
|
if (header.Key.Equals("X-Emby-Authorization", StringComparison.OrdinalIgnoreCase))
|
||||||
_logger.LogDebug("Forwarding X-Emby-Authorization: {Header}", embyAuth.ToString());
|
{
|
||||||
|
request.Headers.TryAddWithoutValidation("X-Emby-Authorization", header.Value.ToString());
|
||||||
|
authHeaderAdded = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else if (clientHeaders.TryGetValue("Authorization", out var auth))
|
|
||||||
{
|
|
||||||
request.Headers.TryAddWithoutValidation("Authorization", auth.ToString());
|
|
||||||
_logger.LogDebug("Forwarding Authorization: {Header}", auth.ToString());
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (!authHeaderAdded)
|
||||||
|
{
|
||||||
|
foreach (var header in clientHeaders)
|
||||||
|
{
|
||||||
|
if (header.Key.Equals("Authorization", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
request.Headers.TryAddWithoutValidation("Authorization", header.Value.ToString());
|
||||||
|
authHeaderAdded = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For login requests without auth headers, provide a minimal client auth header
|
||||||
|
if (!authHeaderAdded)
|
||||||
{
|
{
|
||||||
// For login requests, provide a minimal client auth header (no token)
|
|
||||||
var clientAuthHeader = $"MediaBrowser Client=\"{_settings.ClientName}\", " +
|
var clientAuthHeader = $"MediaBrowser Client=\"{_settings.ClientName}\", " +
|
||||||
$"Device=\"{_settings.DeviceName}\", " +
|
$"Device=\"{_settings.DeviceName}\", " +
|
||||||
$"DeviceId=\"{_settings.DeviceId}\", " +
|
$"DeviceId=\"{_settings.DeviceId}\", " +
|
||||||
$"Version=\"{_settings.ClientVersion}\"";
|
$"Version=\"{_settings.ClientVersion}\"";
|
||||||
request.Headers.TryAddWithoutValidation("X-Emby-Authorization", clientAuthHeader);
|
request.Headers.TryAddWithoutValidation("X-Emby-Authorization", clientAuthHeader);
|
||||||
_logger.LogDebug("Using default X-Emby-Authorization for login: {Header}", clientAuthHeader);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
|
|||||||
Reference in New Issue
Block a user