Skip to content

Commit f07b00a

Browse files
committed
Store reusable credentials and login with those
1 parent e356eff commit f07b00a

8 files changed

Lines changed: 92 additions & 32 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ out/
33
target/
44
*iml
55
config.toml
6+
credentials.json
67
/cache/
78
.settings/
89
.project

api/src/main/java/xyz/gianlu/librespot/api/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public static void main(String[] args) throws IOException, MercuryClient.Mercury
2121
LogManager.getRootLogger().setLevel(conf.loggingLevel());
2222

2323
SessionWrapper wrapper;
24-
if (conf.authStrategy() == AuthConfiguration.Strategy.ZEROCONF)
24+
if (conf.authStrategy() == AuthConfiguration.Strategy.ZEROCONF && !conf.hasStoredCredentials())
2525
wrapper = SessionWrapper.fromZeroconf(ZeroconfServer.create(conf));
2626
else
2727
wrapper = SessionWrapper.fromSession(new Session.Builder(conf).create());

common/src/main/java/xyz/gianlu/librespot/common/Utils.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@
2020
import java.nio.ByteBuffer;
2121
import java.security.Permission;
2222
import java.security.PermissionCollection;
23-
import java.util.Arrays;
24-
import java.util.List;
25-
import java.util.Map;
26-
import java.util.Random;
23+
import java.util.*;
2724

2825
/**
2926
* @author Gianlu
@@ -285,4 +282,14 @@ public static String mixersToString(List<Mixer> list) {
285282

286283
return builder.toString();
287284
}
285+
286+
@NotNull
287+
public static String toBase64(@NotNull ByteString bytes) {
288+
return Base64.getEncoder().encodeToString(bytes.toByteArray());
289+
}
290+
291+
@NotNull
292+
public static ByteString fromBase64(@NotNull String str) {
293+
return ByteString.copyFrom(Base64.getDecoder().decode(str.getBytes()));
294+
}
288295
}

core/src/main/java/xyz/gianlu/librespot/FileConfiguration.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,18 @@ public Strategy authStrategy() {
346346
return config.getEnum("auth.strategy", Strategy.class);
347347
}
348348

349+
@Override
350+
public boolean storeCredentials() {
351+
return config.get("auth.storeCredentials");
352+
}
353+
354+
@Override
355+
public @Nullable File credentialsFile() {
356+
String path = config.get("auth.credentialsFile");
357+
if (path == null || path.isEmpty()) return null;
358+
return new File(path);
359+
}
360+
349361
@Override
350362
public boolean zeroconfListenAll() {
351363
return config.get("zeroconf.listenAll");

core/src/main/java/xyz/gianlu/librespot/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class Main {
1717
public static void main(String[] args) throws IOException, GeneralSecurityException, Session.SpotifyAuthenticationException, MercuryClient.MercuryException {
1818
AbsConfiguration conf = new FileConfiguration(args);
1919
LogManager.getRootLogger().setLevel(conf.loggingLevel());
20-
if (conf.authStrategy() == AuthConfiguration.Strategy.ZEROCONF) {
20+
if (conf.authStrategy() == AuthConfiguration.Strategy.ZEROCONF && !conf.hasStoredCredentials()) {
2121
ZeroconfServer server = ZeroconfServer.create(conf);
2222
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
2323
try {

core/src/main/java/xyz/gianlu/librespot/core/AuthConfiguration.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import org.jetbrains.annotations.NotNull;
44
import org.jetbrains.annotations.Nullable;
55

6+
import java.io.File;
7+
68
/**
79
* @author Gianlu
810
*/
@@ -19,6 +21,16 @@ public interface AuthConfiguration {
1921
@NotNull
2022
Strategy authStrategy();
2123

24+
boolean storeCredentials();
25+
26+
@Nullable
27+
File credentialsFile();
28+
29+
default boolean hasStoredCredentials() {
30+
File file = credentialsFile();
31+
return storeCredentials() && file != null && file.exists() && file.canRead();
32+
}
33+
2234
enum Strategy {
2335
FACEBOOK, BLOB,
2436
USER_PASS, ZEROCONF

core/src/main/java/xyz/gianlu/librespot/core/Session.java

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package xyz.gianlu.librespot.core;
22

3+
import com.google.gson.JsonObject;
4+
import com.google.gson.JsonParser;
35
import com.google.protobuf.ByteString;
46
import com.spotify.Authentication;
57
import com.spotify.Keyexchange;
@@ -353,6 +355,22 @@ private void authenticatePartial(@NotNull Authentication.LoginCredentials creden
353355
authLock.notifyAll();
354356
}
355357
}
358+
359+
if (conf().storeCredentials()) {
360+
ByteString reusable = apWelcome.getReusableAuthCredentials();
361+
Authentication.AuthenticationType reusableType = apWelcome.getReusableAuthCredentialsType();
362+
363+
JsonObject obj = new JsonObject();
364+
obj.addProperty("username", apWelcome.getCanonicalUsername());
365+
obj.addProperty("credentials", Utils.toBase64(reusable));
366+
obj.addProperty("type", reusableType.name());
367+
368+
File storeFile = conf().credentialsFile();
369+
if (storeFile == null) throw new IllegalArgumentException();
370+
try (FileOutputStream out = new FileOutputStream(storeFile)) {
371+
out.write(obj.toString().getBytes());
372+
}
373+
}
356374
} else if (packet.is(Packet.Type.AuthFailure)) {
357375
throw new SpotifyAuthenticationException(Keyexchange.APLoginFailed.parseFrom(packet.payload));
358376
} else {
@@ -773,33 +791,41 @@ public Builder userPass(@NotNull String username, @NotNull String password) {
773791
*/
774792
@NotNull
775793
public Session create() throws IOException, GeneralSecurityException, SpotifyAuthenticationException, MercuryClient.MercuryException {
794+
if (authConf.storeCredentials()) {
795+
File storeFile = authConf.credentialsFile();
796+
if (storeFile != null && storeFile.exists()) {
797+
JsonObject obj = JsonParser.parseReader(new FileReader(storeFile)).getAsJsonObject();
798+
loginCredentials = Authentication.LoginCredentials.newBuilder()
799+
.setTyp(Authentication.AuthenticationType.valueOf(obj.get("type").getAsString()))
800+
.setUsername(obj.get("username").getAsString())
801+
.setAuthData(Utils.fromBase64(obj.get("credentials").getAsString()))
802+
.build();
803+
}
804+
}
805+
776806
if (loginCredentials == null) {
777-
if (authConf != null) {
778-
String blob = authConf.authBlob();
779-
String username = authConf.authUsername();
780-
String password = authConf.authPassword();
781-
782-
switch (authConf.authStrategy()) {
783-
case FACEBOOK:
784-
facebook();
785-
break;
786-
case BLOB:
787-
if (username == null) throw new IllegalArgumentException("Missing authUsername!");
788-
if (blob == null) throw new IllegalArgumentException("Missing authBlob!");
789-
blob(username, Base64.getDecoder().decode(blob));
790-
break;
791-
case USER_PASS:
792-
if (username == null) throw new IllegalArgumentException("Missing authUsername!");
793-
if (password == null) throw new IllegalArgumentException("Missing authPassword!");
794-
userPass(username, password);
795-
break;
796-
case ZEROCONF:
797-
throw new IllegalStateException("Cannot handle ZEROCONF! Use ZeroconfServer.");
798-
default:
799-
throw new IllegalStateException("Unknown auth authStrategy: " + authConf.authStrategy());
800-
}
801-
} else {
802-
throw new IllegalStateException("Missing credentials!");
807+
String blob = authConf.authBlob();
808+
String username = authConf.authUsername();
809+
String password = authConf.authPassword();
810+
811+
switch (authConf.authStrategy()) {
812+
case FACEBOOK:
813+
facebook();
814+
break;
815+
case BLOB:
816+
if (username == null) throw new IllegalArgumentException("Missing authUsername!");
817+
if (blob == null) throw new IllegalArgumentException("Missing authBlob!");
818+
blob(username, Base64.getDecoder().decode(blob));
819+
break;
820+
case USER_PASS:
821+
if (username == null) throw new IllegalArgumentException("Missing authUsername!");
822+
if (password == null) throw new IllegalArgumentException("Missing authPassword!");
823+
userPass(username, password);
824+
break;
825+
case ZEROCONF:
826+
throw new IllegalStateException("Cannot handle ZEROCONF! Use ZeroconfServer.");
827+
default:
828+
throw new IllegalStateException("Unknown auth authStrategy: " + authConf.authStrategy());
803829
}
804830
}
805831

core/src/main/resources/default.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ strategy = "ZEROCONF" # Strategy (USER_PASS, ZEROCONF, BLOB, FACEBOOK)
88
username = "" # Spotify username (BLOB, USER_PASS only)
99
password = "" # Spotify password (USER_PASS only)
1010
blob = "" # Spotify authentication blob (BLOB only)
11+
storeCredentials = false # Whether to store reusable credentials on disk (not a plain password)
12+
credentialsFile = "" # Credentials file (JSON)
1113

1214
[zeroconf] ### Zeroconf ###
1315
listenPort = -1 # Listen on this port (`-1` for random)

0 commit comments

Comments
 (0)