Skip to content

Commit a85b312

Browse files
AlexProgrammerDEKas-tleonebeastchrisCamotoy
authored
Port to MinecraftAuth (#4779)
Co-authored-by: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Co-authored-by: onebeastchris <github@onechris.mozmail.com> Co-authored-by: Camotoy <20743703+Camotoy@users.noreply.github.com>
1 parent 96c5856 commit a85b312

File tree

17 files changed

+320
-219
lines changed

17 files changed

+320
-219
lines changed

bootstrap/mod/fabric/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ dependencies {
2525
shadow(libs.protocol.connection) { isTransitive = false }
2626
shadow(libs.protocol.common) { isTransitive = false }
2727
shadow(libs.protocol.codec) { isTransitive = false }
28-
shadow(libs.mcauthlib) { isTransitive = false }
28+
shadow(libs.minecraftauth) { isTransitive = false }
2929
shadow(libs.raknet) { isTransitive = false }
3030

3131
// Consequences of shading + relocating mcauthlib: shadow/relocate mcpl!

core/build.gradle.kts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,10 @@ dependencies {
2525

2626
api(libs.bundles.protocol)
2727

28-
api(libs.mcauthlib)
28+
api(libs.minecraftauth)
2929
api(libs.mcprotocollib) {
3030
exclude("io.netty", "netty-all")
31-
exclude("com.github.GeyserMC", "packetlib")
32-
exclude("com.github.GeyserMC", "mcauthlib")
31+
exclude("net.raphimc", "MinecraftAuth")
3332
}
3433

3534
implementation(libs.raknet) {

core/src/main/java/org/geysermc/geyser/Constants.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ public final class Constants {
3939
public static final String GEYSER_DOWNLOAD_LOCATION = "https://geysermc.org/download";
4040
public static final String UPDATE_PERMISSION = "geyser.update";
4141

42+
@Deprecated
4243
static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";
44+
static final String SAVED_AUTH_CHAINS_FILE = "saved-auth-chains.json";
4345

4446
public static final String GEYSER_CUSTOM_NAMESPACE = "geyser_custom";
4547

@@ -54,4 +56,4 @@ public final class Constants {
5456
}
5557
GLOBAL_API_WS_URI = wsUri;
5658
}
57-
}
59+
}

core/src/main/java/org/geysermc/geyser/GeyserImpl.java

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.fasterxml.jackson.core.type.TypeReference;
3030
import com.fasterxml.jackson.databind.DeserializationFeature;
3131
import com.fasterxml.jackson.databind.ObjectMapper;
32+
import com.google.gson.Gson;
3233
import io.netty.channel.epoll.Epoll;
3334
import io.netty.util.NettyRuntime;
3435
import io.netty.util.concurrent.DefaultThreadFactory;
@@ -38,6 +39,8 @@
3839
import lombok.Setter;
3940
import net.kyori.adventure.text.Component;
4041
import net.kyori.adventure.text.format.NamedTextColor;
42+
import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession;
43+
import net.raphimc.minecraftauth.step.msa.StepMsaToken;
4144
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
4245
import org.checkerframework.checker.nullness.qual.NonNull;
4346
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -93,6 +96,7 @@
9396
import org.geysermc.geyser.util.CooldownUtils;
9497
import org.geysermc.geyser.util.DimensionUtils;
9598
import org.geysermc.geyser.util.Metrics;
99+
import org.geysermc.geyser.util.MinecraftAuthLogger;
96100
import org.geysermc.geyser.util.NewsHandler;
97101
import org.geysermc.geyser.util.VersionCheckUtils;
98102
import org.geysermc.geyser.util.WebUtils;
@@ -179,7 +183,7 @@ public class GeyserImpl implements GeyserApi {
179183

180184
private PendingMicrosoftAuthentication pendingMicrosoftAuthentication;
181185
@Getter(AccessLevel.NONE)
182-
private Map<String, String> savedRefreshTokens;
186+
private Map<String, String> savedAuthChains;
183187

184188
@Getter
185189
private static GeyserImpl instance;
@@ -552,37 +556,84 @@ private void startInstance() {
552556

553557
if (config.getRemote().authType() == AuthType.ONLINE) {
554558
// May be written/read to on multiple threads from each GeyserSession as well as writing the config
555-
savedRefreshTokens = new ConcurrentHashMap<>();
559+
savedAuthChains = new ConcurrentHashMap<>();
556560

557-
File tokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
558-
if (tokensFile.exists()) {
561+
// TODO Remove after a while - just a migration help
562+
//noinspection deprecation
563+
File refreshTokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
564+
if (refreshTokensFile.exists()) {
565+
logger.info("Migrating refresh tokens to auth chains...");
566+
TypeReference<Map<String, String>> type = new TypeReference<>() { };
567+
Map<String, String> refreshTokens = null;
568+
try {
569+
refreshTokens = JSON_MAPPER.readValue(refreshTokensFile, type);
570+
} catch (IOException e) {
571+
// ignored - we'll just delete this file :))
572+
}
573+
574+
if (refreshTokens != null) {
575+
List<String> validUsers = config.getSavedUserLogins();
576+
final Gson gson = new Gson();
577+
for (Map.Entry<String, String> entry : refreshTokens.entrySet()) {
578+
String user = entry.getKey();
579+
if (!validUsers.contains(user)) {
580+
continue;
581+
}
582+
583+
// Migrate refresh tokens to auth chains
584+
try {
585+
StepFullJavaSession javaSession = PendingMicrosoftAuthentication.AUTH_FLOW.apply(false, 10);
586+
StepFullJavaSession.FullJavaSession fullJavaSession = javaSession.getFromInput(
587+
MinecraftAuthLogger.INSTANCE,
588+
PendingMicrosoftAuthentication.AUTH_CLIENT,
589+
new StepMsaToken.RefreshToken(entry.getValue())
590+
);
591+
592+
String authChain = gson.toJson(javaSession.toJson(fullJavaSession));
593+
savedAuthChains.put(user, authChain);
594+
} catch (Exception e) {
595+
GeyserImpl.getInstance().getLogger().warning("Could not migrate " + entry.getKey() + " to an auth chain! " +
596+
"They will need to sign in the next time they join Geyser.");
597+
}
598+
599+
// Ensure the new additions are written to the file
600+
scheduleAuthChainsWrite();
601+
}
602+
}
603+
604+
// Finally: Delete it. Goodbye!
605+
refreshTokensFile.delete();
606+
}
607+
608+
File authChainsFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile();
609+
if (authChainsFile.exists()) {
559610
TypeReference<Map<String, String>> type = new TypeReference<>() { };
560611

561-
Map<String, String> refreshTokenFile = null;
612+
Map<String, String> authChainFile = null;
562613
try {
563-
refreshTokenFile = JSON_MAPPER.readValue(tokensFile, type);
614+
authChainFile = JSON_MAPPER.readValue(authChainsFile, type);
564615
} catch (IOException e) {
565616
logger.error("Cannot load saved user tokens!", e);
566617
}
567-
if (refreshTokenFile != null) {
618+
if (authChainFile != null) {
568619
List<String> validUsers = config.getSavedUserLogins();
569620
boolean doWrite = false;
570-
for (Map.Entry<String, String> entry : refreshTokenFile.entrySet()) {
621+
for (Map.Entry<String, String> entry : authChainFile.entrySet()) {
571622
String user = entry.getKey();
572623
if (!validUsers.contains(user)) {
573624
// Perform a write to this file to purge the now-unused name
574625
doWrite = true;
575626
continue;
576627
}
577-
savedRefreshTokens.put(user, entry.getValue());
628+
savedAuthChains.put(user, entry.getValue());
578629
}
579630
if (doWrite) {
580-
scheduleRefreshTokensWrite();
631+
scheduleAuthChainsWrite();
581632
}
582633
}
583634
}
584635
} else {
585-
savedRefreshTokens = null;
636+
savedAuthChains = null;
586637
}
587638

588639
newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);
@@ -829,20 +880,20 @@ public WorldManager getWorldManager() {
829880
}
830881

831882
@Nullable
832-
public String refreshTokenFor(@NonNull String bedrockName) {
833-
return savedRefreshTokens.get(bedrockName);
883+
public String authChainFor(@NonNull String bedrockName) {
884+
return savedAuthChains.get(bedrockName);
834885
}
835886

836-
public void saveRefreshToken(@NonNull String bedrockName, @NonNull String refreshToken) {
887+
public void saveAuthChain(@NonNull String bedrockName, @NonNull String authChain) {
837888
if (!getConfig().getSavedUserLogins().contains(bedrockName)) {
838889
// Do not save this login
839890
return;
840891
}
841892

842893
// We can safely overwrite old instances because MsaAuthenticationService#getLoginResponseFromRefreshToken
843894
// refreshes the token for us
844-
if (!Objects.equals(refreshToken, savedRefreshTokens.put(bedrockName, refreshToken))) {
845-
scheduleRefreshTokensWrite();
895+
if (!Objects.equals(authChain, savedAuthChains.put(bedrockName, authChain))) {
896+
scheduleAuthChainsWrite();
846897
}
847898
}
848899

@@ -852,15 +903,15 @@ private <T> void runIfNonNull(T nullable, Consumer<T> consumer) {
852903
}
853904
}
854905

855-
private void scheduleRefreshTokensWrite() {
906+
private void scheduleAuthChainsWrite() {
856907
scheduledThread.execute(() -> {
857908
// Ensure all writes are handled on the same thread
858-
File savedTokens = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
909+
File savedAuthChains = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile();
859910
TypeReference<Map<String, String>> type = new TypeReference<>() { };
860-
try (FileWriter writer = new FileWriter(savedTokens)) {
911+
try (FileWriter writer = new FileWriter(savedAuthChains)) {
861912
JSON_MAPPER.writerFor(type)
862913
.withDefaultPrettyPrinter()
863-
.writeValue(writer, savedRefreshTokens);
914+
.writeValue(writer, this.savedAuthChains);
864915
} catch (IOException e) {
865916
getLogger().error("Unable to write saved refresh tokens!", e);
866917
}

core/src/main/java/org/geysermc/geyser/item/type/PlayerHeadItem.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
package org.geysermc.geyser.item.type;
2727

28-
import com.github.steveice10.mc.auth.data.GameProfile;
28+
import org.geysermc.mcprotocollib.auth.GameProfile;
2929
import org.checkerframework.checker.nullness.qual.NonNull;
3030
import org.geysermc.geyser.level.block.type.Block;
3131
import org.geysermc.geyser.session.GeyserSession;

core/src/main/java/org/geysermc/geyser/level/block/type/SkullBlock.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
package org.geysermc.geyser.level.block.type;
2727

28-
import com.github.steveice10.mc.auth.data.GameProfile;
28+
import org.geysermc.mcprotocollib.auth.GameProfile;
2929
import org.cloudburstmc.math.vector.Vector3i;
3030
import org.cloudburstmc.nbt.NbtMap;
3131
import org.cloudburstmc.nbt.NbtMapBuilder;

core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,10 @@ public PacketSignal handle(ModalFormResponsePacket packet) {
274274

275275
private boolean couldLoginUserByName(String bedrockUsername) {
276276
if (geyser.getConfig().getSavedUserLogins().contains(bedrockUsername)) {
277-
String refreshToken = geyser.refreshTokenFor(bedrockUsername);
278-
if (refreshToken != null) {
277+
String authChain = geyser.authChainFor(bedrockUsername);
278+
if (authChain != null) {
279279
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name()));
280-
session.authenticateWithRefreshToken(refreshToken);
280+
session.authenticateWithAuthChain(authChain);
281281
return true;
282282
}
283283
}

0 commit comments

Comments
 (0)