fix(auth): use the authentication spellings Jellyfin 12.0 leaves enabled
X-Emby-Authorization at the remaining request builders, and api_key= in the player-facing URLs, become Authorization and ApiKey. Jellyfin 12.0 disables X-Emby-Authorization, X-Emby-Token, X-MediaBrowser-Token, the Emby scheme and the api_key query parameter by default — and a migration (DisableLegacyAuthorization) turns them off on servers upgraded from 10.11 as well, so this is not confined to fresh installs. A client using them stops working against an upgraded server rather than degrading. Verified at source level rather than inferred: AuthorizationContext.cs is byte-identical between v10.11.5 and v12.0 apart from whitespace. The only change is the default of the gate that guards the legacy spellings. Authorization with the MediaBrowser scheme, and ApiKey as a query parameter, are ungated in both trees — and the server itself emits ApiKey in both (StreamInfo.cs). So one spelling is correct everywhere and no capability flag is involved. Also adds ServerCompatibility to ServerInfo: an opaque verdict the frontend renders without ever comparing a version number, with three states rather than a boolean. A server newer than this build is usable, not refused; an unreadable version string is not grounds for refusal either. Only a server below the floor is refused. TRACES: UR-085 | DR-286, DR-287 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -54,7 +54,10 @@ impl JellyfinClient {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
/// Build the X-Emby-Authorization header value
|
||||
/// Build the value for the `Authorization` header (the `MediaBrowser`
|
||||
/// scheme — see `HttpClient::build_auth_header`).
|
||||
///
|
||||
/// TRACES: UR-085 | DR-287
|
||||
fn get_auth_header(&self) -> String {
|
||||
format!(
|
||||
"MediaBrowser Client=\"{}\", Version=\"{}\", Device=\"{}\", DeviceId=\"{}\", Token=\"{}\"",
|
||||
@@ -75,7 +78,7 @@ impl JellyfinClient {
|
||||
let response = self
|
||||
.http_client
|
||||
.get(&url)
|
||||
.header("X-Emby-Authorization", self.get_auth_header())
|
||||
.header("Authorization", self.get_auth_header())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@@ -155,7 +158,7 @@ impl JellyfinClient {
|
||||
.http_client
|
||||
.post(&url)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("X-Emby-Authorization", self.get_auth_header())
|
||||
.header("Authorization", self.get_auth_header())
|
||||
.json(body)
|
||||
.send()
|
||||
.await
|
||||
@@ -293,7 +296,7 @@ impl JellyfinClient {
|
||||
let response = self
|
||||
.http_client
|
||||
.post(&url)
|
||||
.header("X-Emby-Authorization", self.get_auth_header())
|
||||
.header("Authorization", self.get_auth_header())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@@ -360,7 +363,7 @@ impl JellyfinClient {
|
||||
let response = self
|
||||
.http_client
|
||||
.post(&url)
|
||||
.header("X-Emby-Authorization", self.get_auth_header())
|
||||
.header("Authorization", self.get_auth_header())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Network request failed: {}", e))?;
|
||||
@@ -503,7 +506,7 @@ impl JellyfinClient {
|
||||
let response = self
|
||||
.http_client
|
||||
.delete(&url)
|
||||
.header("X-Emby-Authorization", self.get_auth_header())
|
||||
.header("Authorization", self.get_auth_header())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Network request failed: {}", e))?;
|
||||
|
||||
@@ -56,6 +56,27 @@ impl HttpClient {
|
||||
Ok(Self { client, config })
|
||||
}
|
||||
|
||||
/// A client that will also talk plain HTTP, for tests only.
|
||||
///
|
||||
/// `new` sets `https_only(true)` and that must stay: it is what stops a
|
||||
/// downgrade putting a session token on the wire in clear. `wiremock` serves
|
||||
/// plain HTTP on loopback, so the alternative to this constructor is either
|
||||
/// weakening the real one or not testing the repository against a server at
|
||||
/// all — and the latter is what DR-281 exists to end.
|
||||
///
|
||||
/// `#[cfg(test)]` so it cannot reach a shipped binary.
|
||||
///
|
||||
/// TRACES: UR-085 | DR-281
|
||||
#[cfg(test)]
|
||||
pub fn new_allowing_plaintext_for_tests(config: HttpConfig) -> Result<Self, String> {
|
||||
let client = Client::builder()
|
||||
.timeout(config.timeout)
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to create HTTP client: {}", e))?;
|
||||
|
||||
Ok(Self { client, config })
|
||||
}
|
||||
|
||||
/// Get device name based on platform
|
||||
fn get_device_name() -> &'static str {
|
||||
#[cfg(target_os = "android")]
|
||||
@@ -78,7 +99,16 @@ impl HttpClient {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
/// Build the X-Emby-Authorization header value
|
||||
/// Build the value for the `Authorization` header.
|
||||
///
|
||||
/// The `MediaBrowser` scheme, which is the non-deprecated one: Jellyfin 12.0
|
||||
/// disables `X-Emby-Authorization` (and the `Emby` scheme, `X-Emby-Token`
|
||||
/// and `X-MediaBrowser-Token`) by default, and a migration turns it off on
|
||||
/// upgraded servers too. `Authorization: MediaBrowser …` is ungated on both
|
||||
/// 10.11.x and 12.x, so this is one value for both generations rather than a
|
||||
/// capability branch.
|
||||
///
|
||||
/// TRACES: UR-085 | DR-287
|
||||
pub fn build_auth_header(access_token: Option<&str>, device_id: &str) -> String {
|
||||
let mut parts = vec![
|
||||
format!("MediaBrowser Client=\"{}\"", APP_NAME),
|
||||
|
||||
Reference in New Issue
Block a user