From 96e02fd117b428e0652c1562852ed3d49417fa5b Mon Sep 17 00:00:00 2001 From: "gerrelt_m@hotmail.com" Date: Mon, 13 Nov 2023 21:33:42 +0100 Subject: [PATCH 1/3] Kingosticks change: Obtain spclient access token using login5 instead of keymaster (Fixes librespot-org#1179) --- core/src/session.rs | 10 +++++ core/src/spclient.rs | 102 ++++++++++++++++++++++++++++++++++++++----- protocol/build.rs | 7 +++ 3 files changed, 107 insertions(+), 12 deletions(-) diff --git a/core/src/session.rs b/core/src/session.rs index 69125e17d..066e2670e 100755 --- a/core/src/session.rs +++ b/core/src/session.rs @@ -77,6 +77,7 @@ struct SessionData { client_brand_name: String, client_model_name: String, connection_id: String, + auth_blob: Vec, time_delta: i64, invalid: bool, user_data: UserData, @@ -174,6 +175,7 @@ impl Session { info!("Authenticated as \"{}\" !", reusable_credentials.username); self.set_username(&reusable_credentials.username); + self.set_auth_blob(&reusable_credentials.auth_data); if let Some(cache) = self.cache() { if store_credentials { let cred_changed = cache @@ -471,6 +473,14 @@ impl Session { self.0.data.write().user_data.canonical_username = username.to_owned(); } + pub fn auth_blob(&self) -> Vec { + self.0.data.read().auth_blob.clone() + } + + pub fn set_auth_blob(&self, auth_blob: &Vec,) { + self.0.data.write().auth_blob = auth_blob.clone(); + } + pub fn country(&self) -> String { self.0.data.read().user_data.country.clone() } diff --git a/core/src/spclient.rs b/core/src/spclient.rs index d6c5ffb1b..8c1d740d6 100644 --- a/core/src/spclient.rs +++ b/core/src/spclient.rs @@ -32,6 +32,7 @@ use crate::{ }, connect::PutStateRequest, extended_metadata::BatchedEntityRequest, + login5::{LoginRequest, LoginResponse,}, }, token::Token, version::spotify_version, @@ -43,6 +44,7 @@ component! { accesspoint: Option = None, strategy: RequestStrategy = RequestStrategy::default(), client_token: Option = None, + auth_token: Option = None, } } @@ -147,6 +149,90 @@ impl SpClient { Ok(()) } + + async fn auth_token_request(&self, message: &M) -> Result { + let client_token = self.client_token().await?; + let body = message.write_to_bytes()?; + + let request = Request::builder() + .method(&Method::POST) + .uri("https://login5.spotify.com/v3/login") + .header(ACCEPT, HeaderValue::from_static("application/x-protobuf")) + .header(CLIENT_TOKEN, HeaderValue::from_str(&client_token)?) + .body(Body::from(body))?; + + self.session().http_client().request_body(request).await + } + + pub async fn auth_token(&self) -> Result { + let auth_token = self.lock(|inner| { + if let Some(token) = &inner.auth_token { + if token.is_expired() { + inner.auth_token = None; + } + } + inner.auth_token.clone() + }); + + if let Some(auth_token) = auth_token { + return Ok(auth_token); + } + + let client_id = match OS { + "macos" | "windows" => self.session().client_id(), + _ => SessionConfig::default().client_id, + }; + + let mut login_request = LoginRequest::new(); + login_request.client_info.mut_or_insert_default().client_id = client_id; + login_request.client_info.mut_or_insert_default().device_id = self.session().device_id().to_string(); + + let stored_credential = login_request.mut_stored_credential(); + stored_credential.username = self.session().username().to_string(); + stored_credential.data = self.session().auth_blob().clone(); + + let mut response = self.auth_token_request(&login_request).await?; + let mut count = 0; + const MAX_TRIES: u8 = 3; + + let token_response = loop { + count += 1; + + let message = LoginResponse::parse_from_bytes(&response)?; + // TODO: Handle hash cash stuff + if message.has_ok() { + break message.ok().to_owned(); + } + + if count < MAX_TRIES { + response = self.auth_token_request(&login_request).await?; + } else { + return Err(Error::failed_precondition(format!( + "Unable to solve any of {MAX_TRIES} hash cash challenges" + ))); + } + }; + + let auth_token = Token { + access_token: token_response.access_token.clone(), + expires_in: Duration::from_secs( + token_response + .access_token_expires_in + .try_into() + .unwrap_or(3600), + ), + token_type: "Bearer".to_string(), + scopes: vec![], + timestamp: Instant::now(), + }; + self.lock(|inner| { + inner.auth_token = Some(auth_token.clone()); + }); + + trace!("Got auth token: {:?}", auth_token); + + Ok(auth_token) + } async fn client_token_request(&self, message: &M) -> Result { let body = message.write_to_bytes()?; @@ -462,11 +548,7 @@ impl SpClient { .body(Body::from(body.to_owned()))?; // Reconnection logic: keep getting (cached) tokens because they might have expired. - let token = self - .session() - .token_provider() - .get_token("playlist-read") - .await?; + let auth_token = self.auth_token().await?; let headers_mut = request.headers_mut(); if let Some(ref hdrs) = headers { @@ -474,15 +556,11 @@ impl SpClient { } headers_mut.insert( AUTHORIZATION, - HeaderValue::from_str(&format!("{} {}", token.token_type, token.access_token,))?, + HeaderValue::from_str(&format!("{} {}", auth_token.token_type, auth_token.access_token,))?, ); - if let Ok(client_token) = self.client_token().await { - headers_mut.insert(CLIENT_TOKEN, HeaderValue::from_str(&client_token)?); - } else { - // currently these endpoints seem to work fine without it - warn!("Unable to get client token. Trying to continue without..."); - } + let client_token = self.client_token().await?; + headers_mut.insert(CLIENT_TOKEN, HeaderValue::from_str(&client_token)?); last_response = self.session().http_client().request_body(request).await; diff --git a/protocol/build.rs b/protocol/build.rs index e1378d378..8a0a8138b 100644 --- a/protocol/build.rs +++ b/protocol/build.rs @@ -28,6 +28,13 @@ fn compile() { proto_dir.join("playlist_permission.proto"), proto_dir.join("playlist4_external.proto"), proto_dir.join("spotify/clienttoken/v0/clienttoken_http.proto"), + proto_dir.join("spotify/login5/v3/challenges/code.proto"), + proto_dir.join("spotify/login5/v3/challenges/hashcash.proto"), + proto_dir.join("spotify/login5/v3/client_info.proto"), + proto_dir.join("spotify/login5/v3/credentials/credentials.proto"), + proto_dir.join("spotify/login5/v3/identifiers/identifiers.proto"), + proto_dir.join("spotify/login5/v3/login5.proto"), + proto_dir.join("spotify/login5/v3/user_info.proto"), proto_dir.join("storage-resolve.proto"), proto_dir.join("user_attributes.proto"), // TODO: remove these legacy protobufs when we are on the new API completely From ba08273dd0296ea593b16de0847ae9a89c62447e Mon Sep 17 00:00:00 2001 From: "gerrelt_m@hotmail.com" Date: Fri, 1 Dec 2023 20:37:17 +0100 Subject: [PATCH 2/3] Kingosticks change: Obtain spclient access token using login5 instead of keymaster (Fixes librespot-org#1179) --- core/src/session.rs | 4 ++-- core/src/spclient.rs | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/core/src/session.rs b/core/src/session.rs index 066e2670e..9d5e2cf6c 100755 --- a/core/src/session.rs +++ b/core/src/session.rs @@ -77,7 +77,7 @@ struct SessionData { client_brand_name: String, client_model_name: String, connection_id: String, - auth_blob: Vec, + auth_blob: Vec, time_delta: i64, invalid: bool, user_data: UserData, @@ -477,7 +477,7 @@ impl Session { self.0.data.read().auth_blob.clone() } - pub fn set_auth_blob(&self, auth_blob: &Vec,) { + pub fn set_auth_blob(&self, auth_blob: &Vec) { self.0.data.write().auth_blob = auth_blob.clone(); } diff --git a/core/src/spclient.rs b/core/src/spclient.rs index 8c1d740d6..6e5499863 100644 --- a/core/src/spclient.rs +++ b/core/src/spclient.rs @@ -32,7 +32,7 @@ use crate::{ }, connect::PutStateRequest, extended_metadata::BatchedEntityRequest, - login5::{LoginRequest, LoginResponse,}, + login5::{LoginRequest, LoginResponse}, }, token::Token, version::spotify_version, @@ -149,7 +149,7 @@ impl SpClient { Ok(()) } - + async fn auth_token_request(&self, message: &M) -> Result { let client_token = self.client_token().await?; let body = message.write_to_bytes()?; @@ -185,7 +185,8 @@ impl SpClient { let mut login_request = LoginRequest::new(); login_request.client_info.mut_or_insert_default().client_id = client_id; - login_request.client_info.mut_or_insert_default().device_id = self.session().device_id().to_string(); + login_request.client_info.mut_or_insert_default().device_id = + self.session().device_id().to_string(); let stored_credential = login_request.mut_stored_credential(); stored_credential.username = self.session().username().to_string(); @@ -556,7 +557,10 @@ impl SpClient { } headers_mut.insert( AUTHORIZATION, - HeaderValue::from_str(&format!("{} {}", auth_token.token_type, auth_token.access_token,))?, + HeaderValue::from_str(&format!( + "{} {}", + auth_token.token_type, auth_token.access_token, + ))?, ); let client_token = self.client_token().await?; From f81531836f4bd1f9abe299006818d13ae6ec20a8 Mon Sep 17 00:00:00 2001 From: "gerrelt_m@hotmail.com" Date: Mon, 4 Dec 2023 20:37:05 +0100 Subject: [PATCH 3/3] Kingosticks change: Obtain spclient access token using login5 instead of keymaster (Fixes librespot-org#1179) --- core/src/session.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/session.rs b/core/src/session.rs index 9d5e2cf6c..a1b7a282d 100755 --- a/core/src/session.rs +++ b/core/src/session.rs @@ -477,8 +477,8 @@ impl Session { self.0.data.read().auth_blob.clone() } - pub fn set_auth_blob(&self, auth_blob: &Vec) { - self.0.data.write().auth_blob = auth_blob.clone(); + pub fn set_auth_blob(&self, auth_blob: &[u8]) { + self.0.data.write().auth_blob = auth_blob.to_owned(); } pub fn country(&self) -> String {