From bdceb2d832c8cc2f783b715ffea6e724dae888ab Mon Sep 17 00:00:00 2001 From: philippe44 Date: Tue, 25 Jul 2023 19:08:30 -0700 Subject: [PATCH] MacOS auth for Spotify --- .../external/nanopb/extra/FindNanopb.cmake | 2 +- .../spotify/cspot/bell/main/io/HTTPClient.cpp | 14 ++- .../cspot/bell/main/io/include/HTTPClient.h | 13 +- .../spotify/cspot/include/AccessKeyFetcher.h | 6 +- .../spotify/cspot/include/CSpotContext.h | 3 + .../spotify/cspot/src/AccessKeyFetcher.cpp | 117 ++++++++++++------ components/spotify/cspot/src/TrackPlayer.cpp | 2 +- 7 files changed, 106 insertions(+), 51 deletions(-) diff --git a/components/spotify/cspot/bell/external/nanopb/extra/FindNanopb.cmake b/components/spotify/cspot/bell/external/nanopb/extra/FindNanopb.cmake index d8373926..e09b4b19 100644 --- a/components/spotify/cspot/bell/external/nanopb/extra/FindNanopb.cmake +++ b/components/spotify/cspot/bell/external/nanopb/extra/FindNanopb.cmake @@ -298,7 +298,7 @@ function(NANOPB_GENERATE_CPP SRCS HDRS) if(MSVC) unset(CUSTOM_COMMAND_PREFIX) - endif() + endif() endfunction() diff --git a/components/spotify/cspot/bell/main/io/HTTPClient.cpp b/components/spotify/cspot/bell/main/io/HTTPClient.cpp index f91ad907..73c28cbc 100644 --- a/components/spotify/cspot/bell/main/io/HTTPClient.cpp +++ b/components/spotify/cspot/bell/main/io/HTTPClient.cpp @@ -27,7 +27,7 @@ HTTPClient::Response::~Response() { void HTTPClient::Response::rawRequest(const std::string& url, const std::string& method, - const std::string& content, + const std::vector& content, Headers& headers) { urlParser = bell::URLParser::parse(url); @@ -50,6 +50,10 @@ void HTTPClient::Response::rawRequest(const std::string& url, } socketStream << reqEnd; + // Write request body + if (content.size() > 0) { + socketStream.write((const char*)content.data(), content.size()); + } socketStream.flush(); // Parse response @@ -115,7 +119,13 @@ void HTTPClient::Response::readResponseHeaders() { void HTTPClient::Response::get(const std::string& url, Headers headers) { std::string method = "GET"; - return this->rawRequest(url, method, "", headers); + return this->rawRequest(url, method, {}, headers); +} + +void HTTPClient::Response::post(const std::string& url, Headers headers, + const std::vector& body) { + std::string method = "POST"; + return this->rawRequest(url, method, body, headers); } size_t HTTPClient::Response::contentLength() { diff --git a/components/spotify/cspot/bell/main/io/include/HTTPClient.h b/components/spotify/cspot/bell/main/io/include/HTTPClient.h index 052ce6c6..6b86a8fa 100644 --- a/components/spotify/cspot/bell/main/io/include/HTTPClient.h +++ b/components/spotify/cspot/bell/main/io/include/HTTPClient.h @@ -55,8 +55,10 @@ class HTTPClient { void connect(const std::string& url); void rawRequest(const std::string& method, const std::string& url, - const std::string& content, Headers& headers); + const std::vector& content, Headers& headers); void get(const std::string& url, Headers headers = {}); + void post(const std::string& url, Headers headers = {}, + const std::vector& body = {}); std::string_view body(); std::vector bytes(); @@ -102,5 +104,14 @@ class HTTPClient { response->get(url, headers); return response; } + + static std::unique_ptr post(const std::string& url, + Headers headers = {}, + const std::vector& body = {}) { + auto response = std::make_unique(); + response->connect(url); + response->post(url, headers, body); + return response; + } }; } // namespace bell diff --git a/components/spotify/cspot/include/AccessKeyFetcher.h b/components/spotify/cspot/include/AccessKeyFetcher.h index 949c1461..85f44bb6 100644 --- a/components/spotify/cspot/include/AccessKeyFetcher.h +++ b/components/spotify/cspot/include/AccessKeyFetcher.h @@ -1,13 +1,10 @@ #pragma once +#include // or std::atomic #include // for function #include // for shared_ptr #include // for string -#include -namespace bell { -class WrappedSemaphore; -}; namespace cspot { struct Context; @@ -35,7 +32,6 @@ class AccessKeyFetcher { private: std::shared_ptr ctx; - std::shared_ptr updateSemaphore; std::atomic keyPending = false; std::string accessKey; diff --git a/components/spotify/cspot/include/CSpotContext.h b/components/spotify/cspot/include/CSpotContext.h index ae1c77d2..ed0cfd8e 100644 --- a/components/spotify/cspot/include/CSpotContext.h +++ b/components/spotify/cspot/include/CSpotContext.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "LoginBlob.h" @@ -14,6 +15,7 @@ struct Context { AudioFormat audioFormat = AudioFormat::AudioFormat_OGG_VORBIS_160; std::string deviceId; std::string deviceName; + std::vector authData; int volume; std::string username; @@ -33,6 +35,7 @@ struct Context { ctx->session = std::make_shared(ctx->timeProvider); ctx->config.deviceId = blob->getDeviceId(); ctx->config.deviceName = blob->getDeviceName(); + ctx->config.authData = blob->authData; ctx->config.volume = 0; ctx->config.username = blob->getUserName(); diff --git a/components/spotify/cspot/src/AccessKeyFetcher.cpp b/components/spotify/cspot/src/AccessKeyFetcher.cpp index 968522c5..2d9783d4 100644 --- a/components/spotify/cspot/src/AccessKeyFetcher.cpp +++ b/components/spotify/cspot/src/AccessKeyFetcher.cpp @@ -6,14 +6,17 @@ #include // for remove_extent_t #include // for vector -#include "BellLogger.h" // for AbstractLogger -#include "CSpotContext.h" // for Context -#include "Logger.h" // for CSPOT_LOG -#include "MercurySession.h" // for MercurySession, MercurySession::Res... -#include "Packet.h" // for cspot -#include "TimeProvider.h" // for TimeProvider -#include "Utils.h" // for string_format -#include "WrappedSemaphore.h" +#include "BellLogger.h" // for AbstractLogger +#include "CSpotContext.h" // for Context +#include "HTTPClient.h" +#include "Logger.h" // for CSPOT_LOG +#include "MercurySession.h" // for MercurySession, MercurySession::Res... +#include "NanoPBExtensions.h" // for bell::nanopb::encode... +#include "NanoPBHelper.h" // for pbEncode and pbDecode +#include "Packet.h" // for cspot +#include "TimeProvider.h" // for TimeProvider +#include "Utils.h" // for string_format + #ifdef BELL_ONLY_CJSON #include "cJSON.h" #else @@ -21,6 +24,8 @@ #include "nlohmann/json_fwd.hpp" // for json #endif +#include "protobuf/login5.pb.h" // for LoginRequest + using namespace cspot; static std::string CLIENT_ID = @@ -31,9 +36,7 @@ static std::string SCOPES = "recently-played"; // Required access scopes AccessKeyFetcher::AccessKeyFetcher(std::shared_ptr ctx) - : ctx(ctx) { - this->updateSemaphore = std::make_shared(); -} + : ctx(ctx) {} bool AccessKeyFetcher::isExpired() { if (accessKey.empty()) { @@ -65,40 +68,72 @@ void AccessKeyFetcher::updateAccessKey() { keyPending = true; - CSPOT_LOG(info, "Access token expired, fetching new one..."); + // Prepare a protobuf login request + static LoginRequest loginRequest = LoginRequest_init_zero; + static LoginResponse loginResponse = LoginResponse_init_zero; - std::string url = - string_format("hm://keymaster/token/authenticated?client_id=%s&scope=%s", - CLIENT_ID.c_str(), SCOPES.c_str()); - auto timeProvider = this->ctx->timeProvider; + // Assign necessary request fields + loginRequest.client_info.client_id.funcs.encode = &bell::nanopb::encodeString; + loginRequest.client_info.client_id.arg = &CLIENT_ID; - ctx->session->execute( - MercurySession::RequestType::GET, url, - [this, timeProvider](MercurySession::Response& res) { - if (res.fail) - return; - auto accessJSON = - std::string((char*)res.parts[0].data(), res.parts[0].size()); -#ifdef BELL_ONLY_CJSON - cJSON* jsonBody = cJSON_Parse(accessJSON.c_str()); - this->accessKey = - cJSON_GetObjectItem(jsonBody, "accessToken")->valuestring; - int expiresIn = cJSON_GetObjectItem(jsonBody, "expiresIn")->valueint; - cJSON_Delete(jsonBody); -#else - auto jsonBody = nlohmann::json::parse(accessJSON); - this->accessKey = jsonBody["accessToken"]; - int expiresIn = jsonBody["expiresIn"]; -#endif - expiresIn = expiresIn / 2; // Refresh token before it expires + loginRequest.client_info.device_id.funcs.encode = &bell::nanopb::encodeString; + loginRequest.client_info.device_id.arg = &ctx->config.deviceId; - this->expiresAt = - timeProvider->getSyncedTimestamp() + (expiresIn * 1000); - updateSemaphore->give(); - }); + loginRequest.login_method.stored_credential.username.funcs.encode = + &bell::nanopb::encodeString; + loginRequest.login_method.stored_credential.username.arg = + &ctx->config.username; - updateSemaphore->twait(5000); + // Set login method to stored credential + loginRequest.which_login_method = LoginRequest_stored_credential_tag; + loginRequest.login_method.stored_credential.data.funcs.encode = + &bell::nanopb::encodeVector; + loginRequest.login_method.stored_credential.data.arg = &ctx->config.authData; + + // Max retry of 3, can receive different hash cat types + int retryCount = 3; + bool success = false; + + do { + auto encodedRequest = pbEncode(LoginRequest_fields, &loginRequest); + CSPOT_LOG(info, "Access token expired, fetching new one... %d", + encodedRequest.size()); + + // Perform a login5 request, containing the encoded protobuf data + auto response = bell::HTTPClient::post( + "https://login5.spotify.com/v3/login", + {{"Content-Type", "application/x-protobuf"}}, encodedRequest); + + auto responseBytes = response->bytes(); + + // Deserialize the response + pbDecode(loginResponse, LoginResponse_fields, responseBytes); + + if (loginResponse.which_response == LoginResponse_ok_tag) { + // Successfully received an auth token + CSPOT_LOG(info, "Access token sucessfully fetched"); + success = true; + + accessKey = std::string(loginResponse.response.ok.access_token); + + // Expire in ~30 minutes + int expiresIn = 3600 / 2; + + if (loginResponse.response.ok.has_access_token_expires_in) { + int expiresIn = loginResponse.response.ok.access_token_expires_in / 2; + } + + this->expiresAt = + ctx->timeProvider->getSyncedTimestamp() + (expiresIn * 1000); + } else { + CSPOT_LOG(error, "Failed to fetch access token"); + } + + // Free up allocated memory for response + pb_release(LoginResponse_fields, &loginResponse); + + retryCount--; + } while (retryCount >= 0 && !success); - // Mark as not pending for refresh keyPending = false; } diff --git a/components/spotify/cspot/src/TrackPlayer.cpp b/components/spotify/cspot/src/TrackPlayer.cpp index 03155831..eb897b20 100644 --- a/components/spotify/cspot/src/TrackPlayer.cpp +++ b/components/spotify/cspot/src/TrackPlayer.cpp @@ -49,7 +49,7 @@ static long vorbisTellCb(TrackPlayer* self) { TrackPlayer::TrackPlayer(std::shared_ptr ctx, std::shared_ptr trackQueue, EOFCallback eof, TrackLoadedCallback trackLoaded) - : bell::Task("cspot_player", 32 * 1024, 5, 1) { + : bell::Task("cspot_player", 48 * 1024, 5, 1) { this->ctx = ctx; this->eofCallback = eof; this->trackLoaded = trackLoaded;