From 67fc5cdd95e967564c9d7ea6aa014a19609eeaf6 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Tue, 25 Jul 2023 22:27:02 -0700 Subject: [PATCH 1/7] syntax --- cspot/src/CDNAudioFile.cpp | 2 +- cspot/src/LoginBlob.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cspot/src/CDNAudioFile.cpp b/cspot/src/CDNAudioFile.cpp index f45c3237..2fbd0742 100644 --- a/cspot/src/CDNAudioFile.cpp +++ b/cspot/src/CDNAudioFile.cpp @@ -16,7 +16,7 @@ #include "Utils.h" // for bigNumAdd, bytesToHexString, string... #include "WrappedSemaphore.h" // for WrappedSemaphore #ifdef BELL_ONLY_CJSON -#include "cJSON.h " +#include "cJSON.h" #else #include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json #include "nlohmann/json_fwd.hpp" // for json diff --git a/cspot/src/LoginBlob.cpp b/cspot/src/LoginBlob.cpp index fb2cf919..feb5e8d2 100644 --- a/cspot/src/LoginBlob.cpp +++ b/cspot/src/LoginBlob.cpp @@ -8,7 +8,7 @@ #include "Logger.h" // for CSPOT_LOG #include "protobuf/authentication.pb.h" // for AuthenticationType_AUTHE... #ifdef BELL_ONLY_CJSON -#include "cJSON.h " +#include "cJSON.h" #else #include "nlohmann/detail/json_pointer.hpp" // for json_pointer<>::string_t #include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json From a1ffab5a6764f20b0dc91656d8e9cc8d021b0961 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Tue, 25 Jul 2023 22:35:21 -0700 Subject: [PATCH 2/7] move back to Bell's develop branch --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index ca00bcad..5318c976 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "cspot/bell"] path = cspot/bell url = https://github.com/philippe44/bell - branch = misc + branch = develop From f0d098773b797982cc84c0266970b1f6bc1cf193 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 26 Jul 2023 13:12:51 -0700 Subject: [PATCH 3/7] Catchup with Bell --- cspot/bell | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cspot/bell b/cspot/bell index 36efcfb3..5c46645e 160000 --- a/cspot/bell +++ b/cspot/bell @@ -1 +1 @@ -Subproject commit 36efcfb382401465762b1a05d86f79c86b3395d4 +Subproject commit 5c46645e001c3cfeb947f904fffbccc56642e487 From 751010f248725d96d662f2e4ed24c9befa285c24 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 26 Jul 2023 18:47:30 -0700 Subject: [PATCH 4/7] Bell sync --- cspot/bell | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cspot/bell b/cspot/bell index 5c46645e..3baedafc 160000 --- a/cspot/bell +++ b/cspot/bell @@ -1 +1 @@ -Subproject commit 5c46645e001c3cfeb947f904fffbccc56642e487 +Subproject commit 3baedafca224a610b5fae0a6b616ea2abb76783b From e90d5284e6f88c2ba2044a0ca031962184e57aca Mon Sep 17 00:00:00 2001 From: philippe44 Date: Mon, 25 Sep 2023 16:21:37 -0700 Subject: [PATCH 5/7] bring back user/password authentication with CDN --- cspot/bell | 2 +- cspot/include/CSpotContext.h | 31 +++++++++++++++++++++++++++ cspot/protobuf/authentication.options | 7 +++++- cspot/protobuf/authentication.proto | 29 +++++++++++++++++++++++++ cspot/src/Session.cpp | 10 ++++++++- targets/cli/CommandLineArguments.cpp | 13 +++++++---- targets/cli/CommandLineArguments.h | 8 +++++-- targets/cli/main.cpp | 21 ++++++++++++++++-- 8 files changed, 110 insertions(+), 11 deletions(-) diff --git a/cspot/bell b/cspot/bell index 3baedafc..07e047e8 160000 --- a/cspot/bell +++ b/cspot/bell @@ -1 +1 @@ -Subproject commit 3baedafca224a610b5fae0a6b616ea2abb76783b +Subproject commit 07e047e8aba525e8b301fdb0d093f9dbf0745c63 diff --git a/cspot/include/CSpotContext.h b/cspot/include/CSpotContext.h index ed0cfd8e..2f274ce6 100644 --- a/cspot/include/CSpotContext.h +++ b/cspot/include/CSpotContext.h @@ -6,7 +6,16 @@ #include "LoginBlob.h" #include "MercurySession.h" #include "TimeProvider.h" +#include "Crypto.h" #include "protobuf/metadata.pb.h" +#include "protobuf/authentication.pb.h" // for AuthenticationType_AUTHE... +#ifdef BELL_ONLY_CJSON +#include "cJSON.h" +#else +#include "nlohmann/detail/json_pointer.hpp" // for json_pointer<>::string_t +#include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json +#include "nlohmann/json_fwd.hpp" // for json +#endif namespace cspot { struct Context { @@ -26,6 +35,28 @@ struct Context { std::shared_ptr timeProvider; std::shared_ptr session; + std::string getCredentialsJson() { +#ifdef BELL_ONLY_CJSON + cJSON* json_obj = cJSON_CreateObject(); + cJSON_AddStringToObject(json_obj, "authData", Crypto::base64Encode(config.authData).c_str()); + cJSON_AddNumberToObject(json_obj, "authType", AuthenticationType_AUTHENTICATION_STORED_SPOTIFY_CREDENTIALS); + cJSON_AddStringToObject(json_obj, "username", config.username.c_str()); + + char* str = cJSON_PrintUnformatted(json_obj); + cJSON_Delete(json_obj); + std::string json_objStr(str); + free(str); + + return json_objStr; +#else + nlohmann::json obj; + obj["authData"] = Crypto::base64Encode(config.authData); + obj["authType"] = AuthenticationType_AUTHENTICATION_STORED_SPOTIFY_CREDENTIALS; + obj["username"] = config.username; + + return obj.dump(); +#endif + } static std::shared_ptr createFromBlob( std::shared_ptr blob) { diff --git a/cspot/protobuf/authentication.options b/cspot/protobuf/authentication.options index 33aad19f..2ec38a0c 100644 --- a/cspot/protobuf/authentication.options +++ b/cspot/protobuf/authentication.options @@ -2,4 +2,9 @@ LoginCredentials.username max_size:30, fixed_length:false LoginCredentials.auth_data max_size:512, fixed_length:false SystemInfo.system_information_string max_size:16, fixed_length:false SystemInfo.device_id max_size:50, fixed_length:false -ClientResponseEncrypted.version_string max_size:32, fixed_length:false \ No newline at end of file +ClientResponseEncrypted.version_string max_size:32, fixed_length:false +APWelcome.canonical_username max_size:30, fixed_length:false +APWelcome.reusable_auth_credentials max_size:512, fixed_length:false +APWelcome.lfs_secret max_size:128, fixed_length:false +AccountInfoFacebook.access_token max_size:128, fixed_length:false +AccountInfoFacebook.machine_id max_size:50, fixed_length:false diff --git a/cspot/protobuf/authentication.proto b/cspot/protobuf/authentication.proto index d3896147..079ab290 100644 --- a/cspot/protobuf/authentication.proto +++ b/cspot/protobuf/authentication.proto @@ -37,6 +37,11 @@ enum Os { OS_BCO = 0x16; } +enum AccountType { + Spotify = 0x0; + Facebook = 0x1; +} + enum AuthenticationType { AUTHENTICATION_USER_PASS = 0x0; AUTHENTICATION_STORED_SPOTIFY_CREDENTIALS = 0x1; @@ -62,4 +67,28 @@ message ClientResponseEncrypted { required LoginCredentials login_credentials = 0xa; required SystemInfo system_info = 0x32; optional string version_string = 0x46; +} + +message APWelcome { + required string canonical_username = 0xa; + required AccountType account_type_logged_in = 0x14; + required AccountType credentials_type_logged_in = 0x19; + required AuthenticationType reusable_auth_credentials_type = 0x1e; + required bytes reusable_auth_credentials = 0x28; + optional bytes lfs_secret = 0x32; + optional AccountInfo account_info = 0x3c; + optional AccountInfoFacebook fb = 0x46; +} + +message AccountInfo { + optional AccountInfoSpotify spotify = 0x1; + optional AccountInfoFacebook facebook = 0x2; +} + +message AccountInfoSpotify { +} + +message AccountInfoFacebook { + optional string access_token = 0x1; + optional string machine_id = 0x2; } \ No newline at end of file diff --git a/cspot/src/Session.cpp b/cspot/src/Session.cpp index 74c3f18a..7d76b86a 100644 --- a/cspot/src/Session.cpp +++ b/cspot/src/Session.cpp @@ -17,6 +17,10 @@ #include "PlainConnection.h" // for PlainConnection, timeoutCallback #include "ShannonConnection.h" // for ShannonConnection +#include "pb_decode.h" +#include "NanoPBHelper.h" // for pbPutString, pbEncode, pbDecode +#include "protobuf/authentication.pb.h" + using random_bytes_engine = std::independent_bits_engine; @@ -79,9 +83,13 @@ std::vector Session::authenticate(std::shared_ptr blob) { auto packet = this->shanConn->recvPacket(); switch (packet.command) { case AUTH_SUCCESSFUL_COMMAND: { + APWelcome welcome; CSPOT_LOG(debug, "Authorization successful"); + pbDecode(welcome, APWelcome_fields, packet.data); return std::vector( - {0x1}); // TODO: return actual reusable credentaials to be stored somewhere + welcome.reusable_auth_credentials.bytes, + welcome.reusable_auth_credentials.bytes + welcome.reusable_auth_credentials.size + ); break; } case AUTH_DECLINED_COMMAND: { diff --git a/targets/cli/CommandLineArguments.cpp b/targets/cli/CommandLineArguments.cpp index 9cb2e75b..cd494b16 100644 --- a/targets/cli/CommandLineArguments.cpp +++ b/targets/cli/CommandLineArguments.cpp @@ -6,16 +6,16 @@ #include "protobuf/metadata.pb.h" // for AudioFormat_OGG_VORBIS_160, AudioF... CommandLineArguments::CommandLineArguments(std::string u, std::string p, - bool shouldShowHelp) - : username(u), password(p), shouldShowHelp(shouldShowHelp) {} + std::string c, bool shouldShowHelp) + : username(u), password(p), credentials(c), shouldShowHelp(shouldShowHelp) {} std::shared_ptr CommandLineArguments::parse(int argc, char** argv) { if (argc == 1) { - return std::make_shared("", "", false); + return std::make_shared("", "", "", false); } - auto result = std::make_shared("", "", false); + auto result = std::make_shared("", "", "", false); for (int i = 1; i < argc; i++) { auto stringVal = std::string(argv[i]); @@ -36,6 +36,11 @@ std::shared_ptr CommandLineArguments::parse(int argc, throw std::invalid_argument("expected path after the password flag"); } result->password = std::string(argv[++i]); + } else if (stringVal == "-c" || stringVal == "--credentials") { + if (i >= argc - 1) { + throw std::invalid_argument("expected path after the credentials flag"); + } + result->credentials = std::string(argv[++i]); } else if (stringVal == "-b" || stringVal == "--bitrate") { if (i >= argc - 1) { throw std::invalid_argument("expected path after the bitrate flag"); diff --git a/targets/cli/CommandLineArguments.h b/targets/cli/CommandLineArguments.h index e6648e1b..3be6757c 100644 --- a/targets/cli/CommandLineArguments.h +++ b/targets/cli/CommandLineArguments.h @@ -18,6 +18,10 @@ class CommandLineArguments { * The spotify password. */ std::string password; + /** + * A file to store/read reusable credentials from + */ + std::string credentials; /** * Bitrate setting. */ @@ -31,8 +35,8 @@ class CommandLineArguments { * This is a constructor which initializez all the fields of CommandLineArguments * @param shouldShowHelp determines whether the help text should be printed. */ - CommandLineArguments(std::string username, std::string password, - bool shouldShowHelp); + CommandLineArguments(std::string username, std::string password, + std::string credentials, bool shouldShowHelp); /** * Parses command line arguments, as they are passed to main(). diff --git a/targets/cli/main.cpp b/targets/cli/main.cpp index 7e246ec1..0b479127 100644 --- a/targets/cli/main.cpp +++ b/targets/cli/main.cpp @@ -3,6 +3,8 @@ #include // for invalid_argument #include // for remove_extent_t #include // for vector +#include +#include #include "BellHTTPServer.h" // for BellHTTPServer #include "BellLogger.h" // for setDefaultLogger, AbstractLogger @@ -162,6 +164,14 @@ int main(int argc, char** argv) { loginBlob->loadUserPass(args->username, args->password); loggedInSemaphore->give(); } + // reusable credentials + else if (!args->credentials.empty()) { + std::ifstream file(args->credentials); + std::ostringstream credentials; + credentials << file.rdbuf(); + loginBlob->loadJson(credentials.str()); + loggedInSemaphore->give(); + } // ZeroconfAuthenticator else { zeroconfServer->blob = loginBlob; @@ -182,10 +192,17 @@ int main(int argc, char** argv) { CSPOT_LOG(info, "Creating player"); ctx->session->connectWithRandomAp(); - auto token = ctx->session->authenticate(loginBlob); + ctx->config.authData = ctx->session->authenticate(loginBlob); // Auth successful - if (token.size() > 0) { + if (ctx->config.authData.size() > 0) { + // when credentials file and username are set, then store reusable credentials + if (!args->credentials.empty() && !args->username.empty()) { + std::ofstream file(args->credentials); + file << ctx->getCredentialsJson(); + } + + // Start spirc task auto handler = std::make_shared(ctx); // Start handling mercury messages From dac230159d4fcf028f5c5b942ef922525b2211ba Mon Sep 17 00:00:00 2001 From: philippe44 Date: Mon, 25 Sep 2023 16:53:40 -0700 Subject: [PATCH 6/7] add -c help in main's example --- cspot/bell | 2 +- targets/cli/main.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cspot/bell b/cspot/bell index 07e047e8..62757f03 160000 --- a/cspot/bell +++ b/cspot/bell @@ -1 +1 @@ -Subproject commit 07e047e8aba525e8b301fdb0d093f9dbf0745c63 +Subproject commit 62757f034af2b8c0430dd7fc1a27a2451162c4be diff --git a/targets/cli/main.cpp b/targets/cli/main.cpp index 0b479127..d8597787 100644 --- a/targets/cli/main.cpp +++ b/targets/cli/main.cpp @@ -150,6 +150,7 @@ int main(int argc, char** argv) { std::cout << "-p, --password your spotify password, note that " "if you use facebook login you can set a password in your " "account settings\n"; + std::cout << "-c, --credentials json file to store/load reusable credentials\n"; std::cout << "-b, --bitrate bitrate (320, 160, 96)\n"; std::cout << "\n"; std::cout << "ddd 2022\n"; From 7f80b09501c91d86f457f70d912625e2f69889a8 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Mon, 25 Sep 2023 17:15:41 -0700 Subject: [PATCH 7/7] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e182adff..f1d251c8 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,7 @@ After building the app, the only thing you need to do is to run it through CLI. $ ./cspotcli ``` +If you run it with no parameter, it will use ZeroConf to advertise itself. This means that until at least one **local** Spotify Connect application has discovered and connected it, it will not be registered to Spotify servers. As a consequence, Spotify's WebAPI will not be able to see it. If you want the player to be registered at start-up, you need to either use username/password all the time or at least once to create a credentials file and then re-use that file. Run it with -u/-p/-c once and then run it with -c only. See command's line help. Now open a real Spotify app and you should see a cspot device on your local network. Use it to play audio.