Skip to content

Commit

Permalink
Bring back username/password to make it work under CDN (#154)
Browse files Browse the repository at this point in the history
* syntax

* move back to Bell's develop branch

* Catchup with Bell

* Bell sync

* bring back user/password authentication with CDN

* add -c help in main's example

* Update README.md
  • Loading branch information
philippe44 authored Sep 27, 2023
1 parent 623759c commit ed6bd21
Show file tree
Hide file tree
Showing 12 changed files with 115 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[submodule "cspot/bell"]
path = cspot/bell
url = https://github.com/philippe44/bell
branch = misc
branch = develop
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion cspot/bell
Submodule bell updated 471 files
31 changes: 31 additions & 0 deletions cspot/include/CSpotContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -26,6 +35,28 @@ struct Context {

std::shared_ptr<TimeProvider> timeProvider;
std::shared_ptr<cspot::MercurySession> 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<Context> createFromBlob(
std::shared_ptr<LoginBlob> blob) {
Expand Down
7 changes: 6 additions & 1 deletion cspot/protobuf/authentication.options
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
29 changes: 29 additions & 0 deletions cspot/protobuf/authentication.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
2 changes: 1 addition & 1 deletion cspot/src/CDNAudioFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion cspot/src/LoginBlob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion cspot/src/Session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::default_random_engine, CHAR_BIT, uint8_t>;

Expand Down Expand Up @@ -79,9 +83,13 @@ std::vector<uint8_t> Session::authenticate(std::shared_ptr<LoginBlob> 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<uint8_t>(
{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: {
Expand Down
13 changes: 9 additions & 4 deletions targets/cli/CommandLineArguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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> CommandLineArguments::parse(int argc,
char** argv) {

if (argc == 1) {
return std::make_shared<CommandLineArguments>("", "", false);
return std::make_shared<CommandLineArguments>("", "", "", false);
}
auto result = std::make_shared<CommandLineArguments>("", "", false);
auto result = std::make_shared<CommandLineArguments>("", "", "", false);
for (int i = 1; i < argc; i++) {
auto stringVal = std::string(argv[i]);

Expand All @@ -36,6 +36,11 @@ std::shared_ptr<CommandLineArguments> 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");
Expand Down
8 changes: 6 additions & 2 deletions targets/cli/CommandLineArguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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().
Expand Down
22 changes: 20 additions & 2 deletions targets/cli/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <stdexcept> // for invalid_argument
#include <type_traits> // for remove_extent_t
#include <vector> // for vector
#include <iostream>
#include <fstream>

#include "BellHTTPServer.h" // for BellHTTPServer
#include "BellLogger.h" // for setDefaultLogger, AbstractLogger
Expand Down Expand Up @@ -148,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";
Expand All @@ -162,6 +165,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;
Expand All @@ -182,10 +193,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<cspot::SpircHandler>(ctx);

// Start handling mercury messages
Expand Down

0 comments on commit ed6bd21

Please sign in to comment.