Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port to MinecraftAuth #4779

Merged
merged 22 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8c89615
Initial work on porting to MinecraftAuth
AlexProgrammerDE Jun 20, 2024
07522a7
Fix offline access order
AlexProgrammerDE Jun 20, 2024
11a0293
Merge branch 'master' into minecraftauth
Kas-tle Jun 24, 2024
c4a4688
Merge branch 'master' into minecraftauth
AlexProgrammerDE Jul 1, 2024
517a69e
Fix two null issues
AlexProgrammerDE Jul 1, 2024
adff671
Merge remote-tracking branch 'origin/minecraftauth' into minecraftauth
AlexProgrammerDE Jul 1, 2024
fd95700
Cleanup
AlexProgrammerDE Jul 1, 2024
59495d8
Fix: mappings
onebeastchris Jul 11, 2024
450af55
Add migration from refresh tokens to auth chains
onebeastchris Jul 12, 2024
681fac6
Merge remote-tracking branch 'refs/remotes/upstream/master' into fork…
onebeastchris Jul 18, 2024
2d80429
Attempt to implement auth saving
onebeastchris Jul 18, 2024
e4e7940
remove unnecessary diff
onebeastchris Jul 18, 2024
add31d1
Use static Gson
AlexProgrammerDE Jul 18, 2024
ed795b2
Properly save auth chain
AlexProgrammerDE Jul 18, 2024
12691ac
Address review
AlexProgrammerDE Jul 18, 2024
ac98b76
Update core/src/main/java/org/geysermc/geyser/translator/protocol/bed…
AlexProgrammerDE Jul 20, 2024
4a6d480
Bump mcauth
AlexProgrammerDE Jul 21, 2024
6cb27a1
Merge remote-tracking branch 'origin/minecraftauth' into minecraftauth
AlexProgrammerDE Jul 21, 2024
37806bd
Merge remote-tracking branch 'upstream/master' into minecraftauth
AlexProgrammerDE Jul 21, 2024
37338b7
Add log info
AlexProgrammerDE Jul 24, 2024
0296aeb
Use resultNow since we know the future is always done when onMicrosof…
AlexProgrammerDE Jul 24, 2024
fbea2cb
Don't use new API
Camotoy Jul 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bootstrap/mod/fabric/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ dependencies {
shadow(libs.protocol.connection) { isTransitive = false }
shadow(libs.protocol.common) { isTransitive = false }
shadow(libs.protocol.codec) { isTransitive = false }
shadow(libs.mcauthlib) { isTransitive = false }
shadow(libs.minecraftauth) { isTransitive = false }
shadow(libs.raknet) { isTransitive = false }

// Consequences of shading + relocating mcauthlib: shadow/relocate mcpl!
Expand Down
5 changes: 2 additions & 3 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,10 @@ dependencies {

api(libs.bundles.protocol)

api(libs.mcauthlib)
api(libs.minecraftauth)
api(libs.mcprotocollib) {
exclude("io.netty", "netty-all")
exclude("com.github.GeyserMC", "packetlib")
exclude("com.github.GeyserMC", "mcauthlib")
exclude("net.raphimc", "MinecraftAuth")
}

implementation(libs.raknet) {
Expand Down
4 changes: 3 additions & 1 deletion core/src/main/java/org/geysermc/geyser/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ public final class Constants {
public static final String GEYSER_DOWNLOAD_LOCATION = "https://geysermc.org/download";
public static final String UPDATE_PERMISSION = "geyser.update";

@Deprecated
static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";
static final String SAVED_AUTH_CHAINS_FILE = "saved-auth-chains.json";
onebeastchris marked this conversation as resolved.
Show resolved Hide resolved

public static final String GEYSER_CUSTOM_NAMESPACE = "geyser_custom";

Expand All @@ -54,4 +56,4 @@ public final class Constants {
}
GLOBAL_API_WS_URI = wsUri;
}
}
}
91 changes: 71 additions & 20 deletions core/src/main/java/org/geysermc/geyser/GeyserImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import io.netty.channel.epoll.Epoll;
import io.netty.util.NettyRuntime;
import io.netty.util.concurrent.DefaultThreadFactory;
Expand All @@ -38,6 +39,8 @@
import lombok.Setter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession;
import net.raphimc.minecraftauth.step.msa.StepMsaToken;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
Expand Down Expand Up @@ -93,6 +96,7 @@
import org.geysermc.geyser.util.CooldownUtils;
import org.geysermc.geyser.util.DimensionUtils;
import org.geysermc.geyser.util.Metrics;
import org.geysermc.geyser.util.MinecraftAuthLogger;
import org.geysermc.geyser.util.NewsHandler;
import org.geysermc.geyser.util.VersionCheckUtils;
import org.geysermc.geyser.util.WebUtils;
Expand Down Expand Up @@ -179,7 +183,7 @@ public class GeyserImpl implements GeyserApi {

private PendingMicrosoftAuthentication pendingMicrosoftAuthentication;
@Getter(AccessLevel.NONE)
private Map<String, String> savedRefreshTokens;
private Map<String, String> savedAuthChains;

@Getter
private static GeyserImpl instance;
Expand Down Expand Up @@ -552,37 +556,84 @@ private void startInstance() {

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

File tokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
if (tokensFile.exists()) {
// TODO Remove after a while - just a migration help
//noinspection deprecation
File refreshTokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
if (refreshTokensFile.exists()) {
AlexProgrammerDE marked this conversation as resolved.
Show resolved Hide resolved
logger.info("Migrating refresh tokens to auth chains...");
TypeReference<Map<String, String>> type = new TypeReference<>() { };
Map<String, String> refreshTokens = null;
try {
refreshTokens = JSON_MAPPER.readValue(refreshTokensFile, type);
} catch (IOException e) {
// ignored - we'll just delete this file :))
}

if (refreshTokens != null) {
List<String> validUsers = config.getSavedUserLogins();
final Gson gson = new Gson();
for (Map.Entry<String, String> entry : refreshTokens.entrySet()) {
String user = entry.getKey();
if (!validUsers.contains(user)) {
continue;
}

// Migrate refresh tokens to auth chains
try {
StepFullJavaSession javaSession = PendingMicrosoftAuthentication.AUTH_FLOW.apply(false, 10);
StepFullJavaSession.FullJavaSession fullJavaSession = javaSession.getFromInput(
MinecraftAuthLogger.INSTANCE,
PendingMicrosoftAuthentication.AUTH_CLIENT,
new StepMsaToken.RefreshToken(entry.getValue())
);

String authChain = gson.toJson(javaSession.toJson(fullJavaSession));
savedAuthChains.put(user, authChain);
} catch (Exception e) {
GeyserImpl.getInstance().getLogger().warning("Could not migrate " + entry.getKey() + " to an auth chain! " +
"They will need to sign in the next time they join Geyser.");
}

// Ensure the new additions are written to the file
scheduleAuthChainsWrite();
}
}

// Finally: Delete it. Goodbye!
refreshTokensFile.delete();
}

File authChainsFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile();
if (authChainsFile.exists()) {
TypeReference<Map<String, String>> type = new TypeReference<>() { };

Map<String, String> refreshTokenFile = null;
Map<String, String> authChainFile = null;
try {
refreshTokenFile = JSON_MAPPER.readValue(tokensFile, type);
authChainFile = JSON_MAPPER.readValue(authChainsFile, type);
} catch (IOException e) {
logger.error("Cannot load saved user tokens!", e);
}
if (refreshTokenFile != null) {
if (authChainFile != null) {
List<String> validUsers = config.getSavedUserLogins();
boolean doWrite = false;
for (Map.Entry<String, String> entry : refreshTokenFile.entrySet()) {
for (Map.Entry<String, String> entry : authChainFile.entrySet()) {
String user = entry.getKey();
if (!validUsers.contains(user)) {
// Perform a write to this file to purge the now-unused name
doWrite = true;
continue;
}
savedRefreshTokens.put(user, entry.getValue());
savedAuthChains.put(user, entry.getValue());
}
if (doWrite) {
scheduleRefreshTokensWrite();
scheduleAuthChainsWrite();
}
}
}
} else {
savedRefreshTokens = null;
savedAuthChains = null;
}

newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);
Expand Down Expand Up @@ -829,20 +880,20 @@ public WorldManager getWorldManager() {
}

@Nullable
public String refreshTokenFor(@NonNull String bedrockName) {
return savedRefreshTokens.get(bedrockName);
public String authChainFor(@NonNull String bedrockName) {
return savedAuthChains.get(bedrockName);
}

public void saveRefreshToken(@NonNull String bedrockName, @NonNull String refreshToken) {
public void saveAuthChain(@NonNull String bedrockName, @NonNull String authChain) {
if (!getConfig().getSavedUserLogins().contains(bedrockName)) {
// Do not save this login
return;
}

// We can safely overwrite old instances because MsaAuthenticationService#getLoginResponseFromRefreshToken
// refreshes the token for us
if (!Objects.equals(refreshToken, savedRefreshTokens.put(bedrockName, refreshToken))) {
scheduleRefreshTokensWrite();
if (!Objects.equals(authChain, savedAuthChains.put(bedrockName, authChain))) {
scheduleAuthChainsWrite();
}
}

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

private void scheduleRefreshTokensWrite() {
private void scheduleAuthChainsWrite() {
scheduledThread.execute(() -> {
// Ensure all writes are handled on the same thread
File savedTokens = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
File savedAuthChains = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile();
TypeReference<Map<String, String>> type = new TypeReference<>() { };
try (FileWriter writer = new FileWriter(savedTokens)) {
try (FileWriter writer = new FileWriter(savedAuthChains)) {
JSON_MAPPER.writerFor(type)
.withDefaultPrettyPrinter()
.writeValue(writer, savedRefreshTokens);
.writeValue(writer, this.savedAuthChains);
} catch (IOException e) {
getLogger().error("Unable to write saved refresh tokens!", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

package org.geysermc.geyser.item.type;

import com.github.steveice10.mc.auth.data.GameProfile;
import org.geysermc.mcprotocollib.auth.GameProfile;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.session.GeyserSession;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

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

import com.github.steveice10.mc.auth.data.GameProfile;
import org.geysermc.mcprotocollib.auth.GameProfile;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,10 @@ public PacketSignal handle(ModalFormResponsePacket packet) {

private boolean couldLoginUserByName(String bedrockUsername) {
if (geyser.getConfig().getSavedUserLogins().contains(bedrockUsername)) {
String refreshToken = geyser.refreshTokenFor(bedrockUsername);
if (refreshToken != null) {
String authChain = geyser.authChainFor(bedrockUsername);
if (authChain != null) {
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name()));
session.authenticateWithRefreshToken(refreshToken);
session.authenticateWithAuthChain(authChain);
return true;
}
}
Expand Down
Loading