mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-12-22 14:34:59 +01:00
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>
This commit is contained in:
parent
96c58566b5
commit
a85b312b40
17 changed files with 320 additions and 219 deletions
|
@ -25,7 +25,7 @@ dependencies {
|
||||||
shadow(libs.protocol.connection) { isTransitive = false }
|
shadow(libs.protocol.connection) { isTransitive = false }
|
||||||
shadow(libs.protocol.common) { isTransitive = false }
|
shadow(libs.protocol.common) { isTransitive = false }
|
||||||
shadow(libs.protocol.codec) { isTransitive = false }
|
shadow(libs.protocol.codec) { isTransitive = false }
|
||||||
shadow(libs.mcauthlib) { isTransitive = false }
|
shadow(libs.minecraftauth) { isTransitive = false }
|
||||||
shadow(libs.raknet) { isTransitive = false }
|
shadow(libs.raknet) { isTransitive = false }
|
||||||
|
|
||||||
// Consequences of shading + relocating mcauthlib: shadow/relocate mcpl!
|
// Consequences of shading + relocating mcauthlib: shadow/relocate mcpl!
|
||||||
|
|
|
@ -25,11 +25,10 @@ dependencies {
|
||||||
|
|
||||||
api(libs.bundles.protocol)
|
api(libs.bundles.protocol)
|
||||||
|
|
||||||
api(libs.mcauthlib)
|
api(libs.minecraftauth)
|
||||||
api(libs.mcprotocollib) {
|
api(libs.mcprotocollib) {
|
||||||
exclude("io.netty", "netty-all")
|
exclude("io.netty", "netty-all")
|
||||||
exclude("com.github.GeyserMC", "packetlib")
|
exclude("net.raphimc", "MinecraftAuth")
|
||||||
exclude("com.github.GeyserMC", "mcauthlib")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation(libs.raknet) {
|
implementation(libs.raknet) {
|
||||||
|
|
|
@ -39,7 +39,9 @@ public final class Constants {
|
||||||
public static final String GEYSER_DOWNLOAD_LOCATION = "https://geysermc.org/download";
|
public static final String GEYSER_DOWNLOAD_LOCATION = "https://geysermc.org/download";
|
||||||
public static final String UPDATE_PERMISSION = "geyser.update";
|
public static final String UPDATE_PERMISSION = "geyser.update";
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";
|
static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";
|
||||||
|
static final String SAVED_AUTH_CHAINS_FILE = "saved-auth-chains.json";
|
||||||
|
|
||||||
public static final String GEYSER_CUSTOM_NAMESPACE = "geyser_custom";
|
public static final String GEYSER_CUSTOM_NAMESPACE = "geyser_custom";
|
||||||
|
|
||||||
|
@ -54,4 +56,4 @@ public final class Constants {
|
||||||
}
|
}
|
||||||
GLOBAL_API_WS_URI = wsUri;
|
GLOBAL_API_WS_URI = wsUri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import com.fasterxml.jackson.core.JsonParser;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.gson.Gson;
|
||||||
import io.netty.channel.epoll.Epoll;
|
import io.netty.channel.epoll.Epoll;
|
||||||
import io.netty.util.NettyRuntime;
|
import io.netty.util.NettyRuntime;
|
||||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||||
|
@ -38,6 +39,8 @@ import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
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.MonotonicNonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
@ -93,6 +96,7 @@ import org.geysermc.geyser.util.AssetUtils;
|
||||||
import org.geysermc.geyser.util.CooldownUtils;
|
import org.geysermc.geyser.util.CooldownUtils;
|
||||||
import org.geysermc.geyser.util.DimensionUtils;
|
import org.geysermc.geyser.util.DimensionUtils;
|
||||||
import org.geysermc.geyser.util.Metrics;
|
import org.geysermc.geyser.util.Metrics;
|
||||||
|
import org.geysermc.geyser.util.MinecraftAuthLogger;
|
||||||
import org.geysermc.geyser.util.NewsHandler;
|
import org.geysermc.geyser.util.NewsHandler;
|
||||||
import org.geysermc.geyser.util.VersionCheckUtils;
|
import org.geysermc.geyser.util.VersionCheckUtils;
|
||||||
import org.geysermc.geyser.util.WebUtils;
|
import org.geysermc.geyser.util.WebUtils;
|
||||||
|
@ -179,7 +183,7 @@ public class GeyserImpl implements GeyserApi {
|
||||||
|
|
||||||
private PendingMicrosoftAuthentication pendingMicrosoftAuthentication;
|
private PendingMicrosoftAuthentication pendingMicrosoftAuthentication;
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
private Map<String, String> savedRefreshTokens;
|
private Map<String, String> savedAuthChains;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private static GeyserImpl instance;
|
private static GeyserImpl instance;
|
||||||
|
@ -552,37 +556,84 @@ public class GeyserImpl implements GeyserApi {
|
||||||
|
|
||||||
if (config.getRemote().authType() == AuthType.ONLINE) {
|
if (config.getRemote().authType() == AuthType.ONLINE) {
|
||||||
// May be written/read to on multiple threads from each GeyserSession as well as writing the config
|
// 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();
|
// TODO Remove after a while - just a migration help
|
||||||
if (tokensFile.exists()) {
|
//noinspection deprecation
|
||||||
|
File refreshTokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
|
||||||
|
if (refreshTokensFile.exists()) {
|
||||||
|
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<>() { };
|
TypeReference<Map<String, String>> type = new TypeReference<>() { };
|
||||||
|
|
||||||
Map<String, String> refreshTokenFile = null;
|
Map<String, String> authChainFile = null;
|
||||||
try {
|
try {
|
||||||
refreshTokenFile = JSON_MAPPER.readValue(tokensFile, type);
|
authChainFile = JSON_MAPPER.readValue(authChainsFile, type);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Cannot load saved user tokens!", e);
|
logger.error("Cannot load saved user tokens!", e);
|
||||||
}
|
}
|
||||||
if (refreshTokenFile != null) {
|
if (authChainFile != null) {
|
||||||
List<String> validUsers = config.getSavedUserLogins();
|
List<String> validUsers = config.getSavedUserLogins();
|
||||||
boolean doWrite = false;
|
boolean doWrite = false;
|
||||||
for (Map.Entry<String, String> entry : refreshTokenFile.entrySet()) {
|
for (Map.Entry<String, String> entry : authChainFile.entrySet()) {
|
||||||
String user = entry.getKey();
|
String user = entry.getKey();
|
||||||
if (!validUsers.contains(user)) {
|
if (!validUsers.contains(user)) {
|
||||||
// Perform a write to this file to purge the now-unused name
|
// Perform a write to this file to purge the now-unused name
|
||||||
doWrite = true;
|
doWrite = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
savedRefreshTokens.put(user, entry.getValue());
|
savedAuthChains.put(user, entry.getValue());
|
||||||
}
|
}
|
||||||
if (doWrite) {
|
if (doWrite) {
|
||||||
scheduleRefreshTokensWrite();
|
scheduleAuthChainsWrite();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
savedRefreshTokens = null;
|
savedAuthChains = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);
|
newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);
|
||||||
|
@ -829,11 +880,11 @@ public class GeyserImpl implements GeyserApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public String refreshTokenFor(@NonNull String bedrockName) {
|
public String authChainFor(@NonNull String bedrockName) {
|
||||||
return savedRefreshTokens.get(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)) {
|
if (!getConfig().getSavedUserLogins().contains(bedrockName)) {
|
||||||
// Do not save this login
|
// Do not save this login
|
||||||
return;
|
return;
|
||||||
|
@ -841,8 +892,8 @@ public class GeyserImpl implements GeyserApi {
|
||||||
|
|
||||||
// We can safely overwrite old instances because MsaAuthenticationService#getLoginResponseFromRefreshToken
|
// We can safely overwrite old instances because MsaAuthenticationService#getLoginResponseFromRefreshToken
|
||||||
// refreshes the token for us
|
// refreshes the token for us
|
||||||
if (!Objects.equals(refreshToken, savedRefreshTokens.put(bedrockName, refreshToken))) {
|
if (!Objects.equals(authChain, savedAuthChains.put(bedrockName, authChain))) {
|
||||||
scheduleRefreshTokensWrite();
|
scheduleAuthChainsWrite();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -852,15 +903,15 @@ public class GeyserImpl implements GeyserApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleRefreshTokensWrite() {
|
private void scheduleAuthChainsWrite() {
|
||||||
scheduledThread.execute(() -> {
|
scheduledThread.execute(() -> {
|
||||||
// Ensure all writes are handled on the same thread
|
// 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<>() { };
|
TypeReference<Map<String, String>> type = new TypeReference<>() { };
|
||||||
try (FileWriter writer = new FileWriter(savedTokens)) {
|
try (FileWriter writer = new FileWriter(savedAuthChains)) {
|
||||||
JSON_MAPPER.writerFor(type)
|
JSON_MAPPER.writerFor(type)
|
||||||
.withDefaultPrettyPrinter()
|
.withDefaultPrettyPrinter()
|
||||||
.writeValue(writer, savedRefreshTokens);
|
.writeValue(writer, this.savedAuthChains);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
getLogger().error("Unable to write saved refresh tokens!", e);
|
getLogger().error("Unable to write saved refresh tokens!", e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.item.type;
|
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.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.geysermc.geyser.level.block.type.Block;
|
import org.geysermc.geyser.level.block.type.Block;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.level.block.type;
|
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.math.vector.Vector3i;
|
||||||
import org.cloudburstmc.nbt.NbtMap;
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||||
|
|
|
@ -274,10 +274,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||||
|
|
||||||
private boolean couldLoginUserByName(String bedrockUsername) {
|
private boolean couldLoginUserByName(String bedrockUsername) {
|
||||||
if (geyser.getConfig().getSavedUserLogins().contains(bedrockUsername)) {
|
if (geyser.getConfig().getSavedUserLogins().contains(bedrockUsername)) {
|
||||||
String refreshToken = geyser.refreshTokenFor(bedrockUsername);
|
String authChain = geyser.authChainFor(bedrockUsername);
|
||||||
if (refreshToken != null) {
|
if (authChain != null) {
|
||||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name()));
|
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name()));
|
||||||
session.authenticateWithRefreshToken(refreshToken);
|
session.authenticateWithAuthChain(authChain);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,8 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.session;
|
package org.geysermc.geyser.session;
|
||||||
|
|
||||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
import com.google.gson.Gson;
|
||||||
import com.github.steveice10.mc.auth.exception.request.RequestException;
|
import com.google.gson.JsonObject;
|
||||||
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
|
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.EventLoop;
|
import io.netty.channel.EventLoop;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
|
@ -41,22 +40,60 @@ import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import net.kyori.adventure.key.Key;
|
import net.kyori.adventure.key.Key;
|
||||||
|
import net.raphimc.minecraftauth.step.java.StepMCProfile;
|
||||||
|
import net.raphimc.minecraftauth.step.java.StepMCToken;
|
||||||
|
import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession;
|
||||||
import org.checkerframework.checker.index.qual.NonNegative;
|
import org.checkerframework.checker.index.qual.NonNegative;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.checkerframework.common.value.qual.IntRange;
|
import org.checkerframework.common.value.qual.IntRange;
|
||||||
import org.cloudburstmc.math.vector.*;
|
import org.cloudburstmc.math.vector.Vector2f;
|
||||||
|
import org.cloudburstmc.math.vector.Vector2i;
|
||||||
|
import org.cloudburstmc.math.vector.Vector3d;
|
||||||
|
import org.cloudburstmc.math.vector.Vector3f;
|
||||||
|
import org.cloudburstmc.math.vector.Vector3i;
|
||||||
import org.cloudburstmc.nbt.NbtMap;
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons;
|
import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons;
|
||||||
import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
|
import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.*;
|
import org.cloudburstmc.protocol.bedrock.data.Ability;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.AbilityLayer;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.AuthoritativeMovementMode;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.ChatRestrictionLevel;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.ExperimentData;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.GamePublishSetting;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.GameRuleData;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.GameType;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.PlayerPermission;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.SpawnBiomeType;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumData;
|
import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumData;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
|
import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.command.SoftEnumUpdateType;
|
import org.cloudburstmc.protocol.bedrock.data.command.SoftEnumUpdateType;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.*;
|
import org.cloudburstmc.protocol.bedrock.packet.AvailableEntityIdentifiersPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.BiomeDefinitionListPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.CameraPresetsPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.ChunkRadiusUpdatedPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.CreativeContentPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.EmoteListPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.GameRulesChangedPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.ItemComponentPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEvent2Packet;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.SetTimePacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.StartGamePacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.SyncEntityPropertyPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.TextPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.TransferPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.UpdateAbilitiesPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.UpdateAdventureSettingsPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.UpdateClientInputLocksPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.UpdateSoftEnumPacket;
|
||||||
import org.cloudburstmc.protocol.common.util.OptionalBoolean;
|
import org.cloudburstmc.protocol.common.util.OptionalBoolean;
|
||||||
import org.geysermc.api.util.BedrockPlatform;
|
import org.geysermc.api.util.BedrockPlatform;
|
||||||
import org.geysermc.api.util.InputMode;
|
import org.geysermc.api.util.InputMode;
|
||||||
|
@ -106,7 +143,22 @@ import org.geysermc.geyser.registry.type.BlockMappings;
|
||||||
import org.geysermc.geyser.registry.type.ItemMappings;
|
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||||
import org.geysermc.geyser.session.auth.AuthData;
|
import org.geysermc.geyser.session.auth.AuthData;
|
||||||
import org.geysermc.geyser.session.auth.BedrockClientData;
|
import org.geysermc.geyser.session.auth.BedrockClientData;
|
||||||
import org.geysermc.geyser.session.cache.*;
|
import org.geysermc.geyser.session.cache.AdvancementsCache;
|
||||||
|
import org.geysermc.geyser.session.cache.BookEditCache;
|
||||||
|
import org.geysermc.geyser.session.cache.ChunkCache;
|
||||||
|
import org.geysermc.geyser.session.cache.EntityCache;
|
||||||
|
import org.geysermc.geyser.session.cache.EntityEffectCache;
|
||||||
|
import org.geysermc.geyser.session.cache.FormCache;
|
||||||
|
import org.geysermc.geyser.session.cache.LodestoneCache;
|
||||||
|
import org.geysermc.geyser.session.cache.PistonCache;
|
||||||
|
import org.geysermc.geyser.session.cache.PreferencesCache;
|
||||||
|
import org.geysermc.geyser.session.cache.RegistryCache;
|
||||||
|
import org.geysermc.geyser.session.cache.SkullCache;
|
||||||
|
import org.geysermc.geyser.session.cache.StructureBlockCache;
|
||||||
|
import org.geysermc.geyser.session.cache.TagCache;
|
||||||
|
import org.geysermc.geyser.session.cache.TeleportCache;
|
||||||
|
import org.geysermc.geyser.session.cache.WorldBorder;
|
||||||
|
import org.geysermc.geyser.session.cache.WorldCache;
|
||||||
import org.geysermc.geyser.skin.FloodgateSkinUploader;
|
import org.geysermc.geyser.skin.FloodgateSkinUploader;
|
||||||
import org.geysermc.geyser.text.GeyserLocale;
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
import org.geysermc.geyser.text.MinecraftLocale;
|
import org.geysermc.geyser.text.MinecraftLocale;
|
||||||
|
@ -116,9 +168,15 @@ import org.geysermc.geyser.util.ChunkUtils;
|
||||||
import org.geysermc.geyser.util.DimensionUtils;
|
import org.geysermc.geyser.util.DimensionUtils;
|
||||||
import org.geysermc.geyser.util.EntityUtils;
|
import org.geysermc.geyser.util.EntityUtils;
|
||||||
import org.geysermc.geyser.util.LoginEncryptionUtils;
|
import org.geysermc.geyser.util.LoginEncryptionUtils;
|
||||||
|
import org.geysermc.geyser.util.MinecraftAuthLogger;
|
||||||
|
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||||
import org.geysermc.mcprotocollib.network.BuiltinFlags;
|
import org.geysermc.mcprotocollib.network.BuiltinFlags;
|
||||||
import org.geysermc.mcprotocollib.network.Session;
|
import org.geysermc.mcprotocollib.network.Session;
|
||||||
import org.geysermc.mcprotocollib.network.event.session.*;
|
import org.geysermc.mcprotocollib.network.event.session.ConnectedEvent;
|
||||||
|
import org.geysermc.mcprotocollib.network.event.session.DisconnectedEvent;
|
||||||
|
import org.geysermc.mcprotocollib.network.event.session.PacketErrorEvent;
|
||||||
|
import org.geysermc.mcprotocollib.network.event.session.PacketSendingEvent;
|
||||||
|
import org.geysermc.mcprotocollib.network.event.session.SessionAdapter;
|
||||||
import org.geysermc.mcprotocollib.network.packet.Packet;
|
import org.geysermc.mcprotocollib.network.packet.Packet;
|
||||||
import org.geysermc.mcprotocollib.network.tcp.TcpClientSession;
|
import org.geysermc.mcprotocollib.network.tcp.TcpClientSession;
|
||||||
import org.geysermc.mcprotocollib.network.tcp.TcpSession;
|
import org.geysermc.mcprotocollib.network.tcp.TcpSession;
|
||||||
|
@ -153,7 +211,16 @@ import java.net.ConnectException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.BitSet;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
@ -163,6 +230,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@Getter
|
@Getter
|
||||||
public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
|
|
||||||
|
private static final Gson GSON = new Gson();
|
||||||
|
|
||||||
private final GeyserImpl geyser;
|
private final GeyserImpl geyser;
|
||||||
private final UpstreamSession upstream;
|
private final UpstreamSession upstream;
|
||||||
private DownstreamSession downstream;
|
private DownstreamSession downstream;
|
||||||
|
@ -690,7 +759,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void authenticateWithRefreshToken(String refreshToken) {
|
public void authenticateWithAuthChain(String authChain) {
|
||||||
if (loggedIn) {
|
if (loggedIn) {
|
||||||
geyser.getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().name()));
|
geyser.getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().name()));
|
||||||
return;
|
return;
|
||||||
|
@ -699,24 +768,23 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
loggingIn = true;
|
loggingIn = true;
|
||||||
|
|
||||||
CompletableFuture.supplyAsync(() -> {
|
CompletableFuture.supplyAsync(() -> {
|
||||||
MsaAuthenticationService service = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID);
|
StepFullJavaSession step = PendingMicrosoftAuthentication.AUTH_FLOW.apply(true, 30);
|
||||||
service.setRefreshToken(refreshToken);
|
StepFullJavaSession.FullJavaSession response;
|
||||||
try {
|
try {
|
||||||
service.login();
|
response = step.refresh(MinecraftAuthLogger.INSTANCE, PendingMicrosoftAuthentication.AUTH_CLIENT, step.fromJson(GSON.fromJson(authChain, JsonObject.class)));
|
||||||
} catch (RequestException e) {
|
} catch (Exception e) {
|
||||||
geyser.getLogger().error("Error while attempting to use refresh token for " + bedrockUsername() + "!", e);
|
geyser.getLogger().error("Error while attempting to use auth chain for " + bedrockUsername() + "!", e);
|
||||||
return Boolean.FALSE;
|
return Boolean.FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameProfile profile = service.getSelectedProfile();
|
StepMCProfile.MCProfile mcProfile = response.getMcProfile();
|
||||||
if (profile == null) {
|
StepMCToken.MCToken mcToken = mcProfile.getMcToken();
|
||||||
// Java account is offline
|
|
||||||
disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol = new MinecraftProtocol(profile, service.getAccessToken());
|
protocol = new MinecraftProtocol(
|
||||||
geyser.saveRefreshToken(bedrockUsername(), service.getRefreshToken());
|
new GameProfile(mcProfile.getId(), mcProfile.getName()),
|
||||||
|
mcToken.getAccessToken()
|
||||||
|
);
|
||||||
|
geyser.saveAuthChain(bedrockUsername(), GSON.toJson(step.toJson(response)));
|
||||||
return Boolean.TRUE;
|
return Boolean.TRUE;
|
||||||
}).whenComplete((successful, ex) -> {
|
}).whenComplete((successful, ex) -> {
|
||||||
if (this.closed) {
|
if (this.closed) {
|
||||||
|
@ -761,25 +829,15 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
final PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getOrCreateTask(
|
final PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getOrCreateTask(
|
||||||
getAuthData().xuid()
|
getAuthData().xuid()
|
||||||
);
|
);
|
||||||
task.setOnline(true);
|
if (task.getAuthentication() != null && task.getAuthentication().isDone()) {
|
||||||
task.resetTimer();
|
|
||||||
|
|
||||||
if (task.getAuthentication().isDone()) {
|
|
||||||
onMicrosoftLoginComplete(task);
|
onMicrosoftLoginComplete(task);
|
||||||
} else {
|
} else {
|
||||||
task.getCode(offlineAccess).whenComplete((response, ex) -> {
|
task.resetRunningFlow();
|
||||||
boolean connected = !closed;
|
task.performLoginAttempt(offlineAccess, code -> {
|
||||||
if (ex != null) {
|
if (!closed) {
|
||||||
if (connected) {
|
LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, code);
|
||||||
geyser.getLogger().error("Failed to get Microsoft auth code", ex);
|
|
||||||
disconnect(ex.toString());
|
|
||||||
}
|
|
||||||
task.cleanup(); // error getting auth code -> clean up immediately
|
|
||||||
} else if (connected) {
|
|
||||||
LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response);
|
|
||||||
task.getAuthentication().whenComplete((r, $) -> onMicrosoftLoginComplete(task));
|
|
||||||
}
|
}
|
||||||
});
|
}).handle((r, e) -> onMicrosoftLoginComplete(task));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -791,36 +849,32 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
task.cleanup(); // player is online -> remove pending authentication immediately
|
task.cleanup(); // player is online -> remove pending authentication immediately
|
||||||
Throwable ex = task.getLoginException();
|
return task.getAuthentication().handle((result, ex) -> {
|
||||||
if (ex != null) {
|
if (ex != null) {
|
||||||
geyser.getLogger().error("Failed to log in with Microsoft code!", ex);
|
geyser.getLogger().error("Failed to log in with Microsoft code!", ex);
|
||||||
disconnect(ex.toString());
|
disconnect(ex.toString());
|
||||||
} else {
|
return false;
|
||||||
MsaAuthenticationService service = task.getMsaAuthenticationService();
|
}
|
||||||
GameProfile selectedProfile = service.getSelectedProfile();
|
|
||||||
if (selectedProfile == null) {
|
|
||||||
disconnect(GeyserLocale.getPlayerLocaleString(
|
|
||||||
"geyser.network.remote.invalid_account",
|
|
||||||
clientData.getLanguageCode()
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
this.protocol = new MinecraftProtocol(
|
|
||||||
selectedProfile,
|
|
||||||
service.getAccessToken()
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
connectDownstream();
|
|
||||||
} catch (Throwable t) {
|
|
||||||
t.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save our refresh token for later use
|
StepMCProfile.MCProfile mcProfile = result.session().getMcProfile();
|
||||||
geyser.saveRefreshToken(bedrockUsername(), service.getRefreshToken());
|
StepMCToken.MCToken mcToken = mcProfile.getMcToken();
|
||||||
return true;
|
|
||||||
}
|
this.protocol = new MinecraftProtocol(
|
||||||
}
|
new GameProfile(mcProfile.getId(), mcProfile.getName()),
|
||||||
return false;
|
mcToken.getAccessToken()
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
connectDownstream();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
t.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save our auth chain for later use
|
||||||
|
geyser.saveAuthChain(bedrockUsername(), GSON.toJson(result.step().toJson(result.session())));
|
||||||
|
return true;
|
||||||
|
}).getNow(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1103,7 +1157,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
if (authData != null) {
|
if (authData != null) {
|
||||||
PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getTask(authData.xuid());
|
PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getTask(authData.xuid());
|
||||||
if (task != null) {
|
if (task != null) {
|
||||||
task.setOnline(false);
|
task.resetRunningFlow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,27 +25,44 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.session;
|
package org.geysermc.geyser.session;
|
||||||
|
|
||||||
import com.github.steveice10.mc.auth.exception.request.AuthPendingException;
|
|
||||||
import com.github.steveice10.mc.auth.exception.request.RequestException;
|
|
||||||
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
import net.lenni0451.commons.httpclient.HttpClient;
|
||||||
|
import net.raphimc.minecraftauth.MinecraftAuth;
|
||||||
|
import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession;
|
||||||
|
import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCode;
|
||||||
|
import net.raphimc.minecraftauth.util.MicrosoftConstants;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
import org.geysermc.geyser.GeyserLogger;
|
import org.geysermc.geyser.GeyserLogger;
|
||||||
|
import org.geysermc.geyser.util.MinecraftAuthLogger;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pending Microsoft authentication task cache.
|
* Pending Microsoft authentication task cache.
|
||||||
* It permits user to exit the server while they authorize Geyser to access their Microsoft account.
|
* It permits user to exit the server while they authorize Geyser to access their Microsoft account.
|
||||||
*/
|
*/
|
||||||
public class PendingMicrosoftAuthentication {
|
public class PendingMicrosoftAuthentication {
|
||||||
|
public static final HttpClient AUTH_CLIENT = MinecraftAuth.createHttpClient();
|
||||||
|
public static final BiFunction<Boolean, Integer, StepFullJavaSession> AUTH_FLOW = (offlineAccess, timeoutSec) -> MinecraftAuth.builder()
|
||||||
|
.withClientId(GeyserImpl.OAUTH_CLIENT_ID)
|
||||||
|
.withScope(offlineAccess ? "XboxLive.signin XboxLive.offline_access" : "XboxLive.signin")
|
||||||
|
.withTimeout(timeoutSec)
|
||||||
|
.deviceCode()
|
||||||
|
.withoutDeviceToken()
|
||||||
|
.regularAuthentication(MicrosoftConstants.JAVA_XSTS_RELYING_PARTY)
|
||||||
|
.buildMinecraftJavaProfileStep(false);
|
||||||
/**
|
/**
|
||||||
* For GeyserConnect usage.
|
* For GeyserConnect usage.
|
||||||
*/
|
*/
|
||||||
|
@ -57,8 +74,8 @@ public class PendingMicrosoftAuthentication {
|
||||||
.build(new CacheLoader<>() {
|
.build(new CacheLoader<>() {
|
||||||
@Override
|
@Override
|
||||||
public AuthenticationTask load(@NonNull String userKey) {
|
public AuthenticationTask load(@NonNull String userKey) {
|
||||||
return storeServerInformation ? new ProxyAuthenticationTask(userKey, timeoutSeconds * 1000L)
|
return storeServerInformation ? new ProxyAuthenticationTask(userKey, timeoutSeconds)
|
||||||
: new AuthenticationTask(userKey, timeoutSeconds * 1000L);
|
: new AuthenticationTask(userKey, timeoutSeconds);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -80,37 +97,23 @@ public class PendingMicrosoftAuthentication {
|
||||||
public class AuthenticationTask {
|
public class AuthenticationTask {
|
||||||
private static final Executor DELAYED_BY_ONE_SECOND = CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS);
|
private static final Executor DELAYED_BY_ONE_SECOND = CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS);
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID);
|
|
||||||
private final String userKey;
|
private final String userKey;
|
||||||
private final long timeoutMs;
|
private final int timeoutSec;
|
||||||
|
|
||||||
private long remainingTimeMs;
|
|
||||||
|
|
||||||
@Setter
|
|
||||||
private boolean online = true;
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final CompletableFuture<MsaAuthenticationService> authentication;
|
private CompletableFuture<StepChainResult> authentication;
|
||||||
|
|
||||||
@Getter
|
private AuthenticationTask(String userKey, int timeoutSec) {
|
||||||
private volatile Throwable loginException;
|
|
||||||
|
|
||||||
private AuthenticationTask(String userKey, long timeoutMs) {
|
|
||||||
this.userKey = userKey;
|
this.userKey = userKey;
|
||||||
this.timeoutMs = timeoutMs;
|
this.timeoutSec = timeoutSec;
|
||||||
this.remainingTimeMs = timeoutMs;
|
|
||||||
|
|
||||||
this.authentication = new CompletableFuture<>();
|
|
||||||
this.authentication.whenComplete((r, ex) -> {
|
|
||||||
this.loginException = ex;
|
|
||||||
// avoid memory leak, in case player doesn't connect again
|
|
||||||
CompletableFuture.delayedExecutor(timeoutMs, TimeUnit.MILLISECONDS).execute(this::cleanup);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetTimer() {
|
public void resetRunningFlow() {
|
||||||
this.remainingTimeMs = this.timeoutMs;
|
if (authentication == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interrupt the current flow
|
||||||
|
this.authentication.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cleanup() {
|
public void cleanup() {
|
||||||
|
@ -121,52 +124,18 @@ public class PendingMicrosoftAuthentication {
|
||||||
authentications.invalidate(userKey);
|
authentications.invalidate(userKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<MsaAuthenticationService.MsCodeResponse> getCode(boolean offlineAccess) {
|
public CompletableFuture<StepChainResult> performLoginAttempt(boolean offlineAccess, Consumer<StepMsaDeviceCode.MsaDeviceCode> deviceCodeConsumer) {
|
||||||
// Request the code
|
return authentication = CompletableFuture.supplyAsync(() -> {
|
||||||
CompletableFuture<MsaAuthenticationService.MsCodeResponse> code = CompletableFuture.supplyAsync(
|
|
||||||
() -> tryGetCode(offlineAccess));
|
|
||||||
// Once the code is received, continuously try to request the access token, profile, etc
|
|
||||||
code.thenRun(() -> performLoginAttempt(System.currentTimeMillis()));
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param offlineAccess whether we want a refresh token for later use.
|
|
||||||
*/
|
|
||||||
private MsaAuthenticationService.MsCodeResponse tryGetCode(boolean offlineAccess) throws CompletionException {
|
|
||||||
try {
|
|
||||||
return msaAuthenticationService.getAuthCode(offlineAccess);
|
|
||||||
} catch (RequestException e) {
|
|
||||||
throw new CompletionException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void performLoginAttempt(long lastAttempt) {
|
|
||||||
CompletableFuture.runAsync(() -> {
|
|
||||||
try {
|
try {
|
||||||
msaAuthenticationService.login();
|
StepFullJavaSession step = AUTH_FLOW.apply(offlineAccess, timeoutSec);
|
||||||
} catch (AuthPendingException e) {
|
return new StepChainResult(step, step.getFromInput(MinecraftAuthLogger.INSTANCE, AUTH_CLIENT, new StepMsaDeviceCode.MsaDeviceCodeCallback(deviceCodeConsumer)));
|
||||||
long currentAttempt = System.currentTimeMillis();
|
|
||||||
if (!online) {
|
|
||||||
// decrement timer only when player's offline
|
|
||||||
remainingTimeMs -= currentAttempt - lastAttempt;
|
|
||||||
if (remainingTimeMs <= 0L) {
|
|
||||||
// time's up
|
|
||||||
authentication.completeExceptionally(new TaskTimeoutException());
|
|
||||||
cleanup();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// try again in 1 second
|
|
||||||
performLoginAttempt(currentAttempt);
|
|
||||||
return;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
authentication.completeExceptionally(e);
|
throw new CompletionException(e);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// login successful
|
}, DELAYED_BY_ONE_SECOND).whenComplete((r, ex) -> {
|
||||||
authentication.complete(msaAuthenticationService);
|
// avoid memory leak, in case player doesn't connect again
|
||||||
}, DELAYED_BY_ONE_SECOND);
|
CompletableFuture.delayedExecutor(timeoutSec, TimeUnit.SECONDS).execute(this::cleanup);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -181,22 +150,11 @@ public class PendingMicrosoftAuthentication {
|
||||||
private String server;
|
private String server;
|
||||||
private int port;
|
private int port;
|
||||||
|
|
||||||
private ProxyAuthenticationTask(String userKey, long timeoutMs) {
|
private ProxyAuthenticationTask(String userKey, int timeoutSec) {
|
||||||
super(userKey, timeoutMs);
|
super(userKey, timeoutSec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public record StepChainResult(StepFullJavaSession step, StepFullJavaSession.FullJavaSession session) {
|
||||||
* @see PendingMicrosoftAuthentication
|
|
||||||
*/
|
|
||||||
public static class TaskTimeoutException extends Exception {
|
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
TaskTimeoutException() {
|
|
||||||
super("It took too long to authorize Geyser to access your Microsoft account. " +
|
|
||||||
"Please request new code and try again.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,11 +25,10 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.skin;
|
package org.geysermc.geyser.skin;
|
||||||
|
|
||||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||||
import com.github.steveice10.mc.auth.data.GameProfile.Texture;
|
import org.geysermc.mcprotocollib.auth.GameProfile.Texture;
|
||||||
import com.github.steveice10.mc.auth.data.GameProfile.TextureModel;
|
import org.geysermc.mcprotocollib.auth.GameProfile.TextureModel;
|
||||||
import com.github.steveice10.mc.auth.data.GameProfile.TextureType;
|
import org.geysermc.mcprotocollib.auth.GameProfile.TextureType;
|
||||||
import com.github.steveice10.mc.auth.exception.property.PropertyException;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
|
@ -113,12 +112,7 @@ public class FakeHeadProvider {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<TextureType, Texture> textures = null;
|
Map<TextureType, Texture> textures = profile.getTextures(false);
|
||||||
try {
|
|
||||||
textures = profile.getTextures(false);
|
|
||||||
} catch (PropertyException e) {
|
|
||||||
session.getGeyser().getLogger().debug("Failed to get textures from GameProfile: " + e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textures == null || textures.isEmpty()) {
|
if (textures == null || textures.isEmpty()) {
|
||||||
loadHead(session, entity, profile.getName());
|
loadHead(session, entity, profile.getName());
|
||||||
|
|
|
@ -25,10 +25,9 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.translator.item;
|
package org.geysermc.geyser.translator.item;
|
||||||
|
|
||||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||||
import com.github.steveice10.mc.auth.data.GameProfile.Texture;
|
import org.geysermc.mcprotocollib.auth.GameProfile.Texture;
|
||||||
import com.github.steveice10.mc.auth.data.GameProfile.TextureType;
|
import org.geysermc.mcprotocollib.auth.GameProfile.TextureType;
|
||||||
import com.github.steveice10.mc.auth.exception.property.PropertyException;
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
@ -487,12 +486,7 @@ public final class ItemTranslator {
|
||||||
|
|
||||||
GameProfile profile = components.get(DataComponentType.PROFILE);
|
GameProfile profile = components.get(DataComponentType.PROFILE);
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
Map<TextureType, Texture> textures = null;
|
Map<TextureType, Texture> textures = profile.getTextures(false);
|
||||||
try {
|
|
||||||
textures = profile.getTextures(false);
|
|
||||||
} catch (PropertyException e) {
|
|
||||||
GeyserImpl.getInstance().getLogger().debug("Failed to get textures from GameProfile: " + e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textures == null || textures.isEmpty()) {
|
if (textures == null || textures.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -46,10 +46,10 @@ public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslat
|
||||||
if (session.remoteServer().authType() == AuthType.ONLINE) {
|
if (session.remoteServer().authType() == AuthType.ONLINE) {
|
||||||
if (!session.isLoggedIn()) {
|
if (!session.isLoggedIn()) {
|
||||||
if (session.getGeyser().getConfig().getSavedUserLogins().contains(session.bedrockUsername())) {
|
if (session.getGeyser().getConfig().getSavedUserLogins().contains(session.bedrockUsername())) {
|
||||||
if (session.getGeyser().refreshTokenFor(session.bedrockUsername()) == null) {
|
if (session.getGeyser().authChainFor(session.bedrockUsername()) == null) {
|
||||||
LoginEncryptionUtils.buildAndShowConsentWindow(session);
|
LoginEncryptionUtils.buildAndShowConsentWindow(session);
|
||||||
} else {
|
} else {
|
||||||
// If the refresh token is not null and we're here, then the refresh token expired
|
// If the auth chain is not null and we're here, then it expired
|
||||||
// and the expiration form has been cached
|
// and the expiration form has been cached
|
||||||
session.getFormCache().resendAllForms();
|
session.getFormCache().resendAllForms();
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.translator.protocol.java;
|
package org.geysermc.geyser.translator.protocol.java;
|
||||||
|
|
||||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||||
import net.kyori.adventure.key.Key;
|
import net.kyori.adventure.key.Key;
|
||||||
import org.geysermc.geyser.api.network.AuthType;
|
import org.geysermc.geyser.api.network.AuthType;
|
||||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.translator.protocol.java.entity.player;
|
package org.geysermc.geyser.translator.protocol.java.entity.player;
|
||||||
|
|
||||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.cloudburstmc.math.vector.Vector3f;
|
import org.cloudburstmc.math.vector.Vector3f;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerListPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.PlayerListPacket;
|
||||||
|
|
|
@ -28,7 +28,7 @@ package org.geysermc.geyser.util;
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
|
import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCode;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.LoginPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.LoginPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.ServerToClientHandshakePacket;
|
import org.cloudburstmc.protocol.bedrock.packet.ServerToClientHandshakePacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.util.ChainValidationResult;
|
import org.cloudburstmc.protocol.bedrock.util.ChainValidationResult;
|
||||||
|
@ -203,7 +203,7 @@ public class LoginEncryptionUtils {
|
||||||
/**
|
/**
|
||||||
* Shows the code that a user must input into their browser
|
* Shows the code that a user must input into their browser
|
||||||
*/
|
*/
|
||||||
public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse msCode) {
|
public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, StepMsaDeviceCode.MsaDeviceCode msCode) {
|
||||||
String locale = session.locale();
|
String locale = session.locale();
|
||||||
|
|
||||||
StringBuilder message = new StringBuilder("%xbox.signin.website\n")
|
StringBuilder message = new StringBuilder("%xbox.signin.website\n")
|
||||||
|
@ -212,7 +212,7 @@ public class LoginEncryptionUtils {
|
||||||
.append(ChatColor.RESET)
|
.append(ChatColor.RESET)
|
||||||
.append("\n%xbox.signin.enterCode\n")
|
.append("\n%xbox.signin.enterCode\n")
|
||||||
.append(ChatColor.GREEN)
|
.append(ChatColor.GREEN)
|
||||||
.append(msCode.user_code);
|
.append(msCode.getUserCode());
|
||||||
int timeout = session.getGeyser().getConfig().getPendingAuthenticationTimeout();
|
int timeout = session.getGeyser().getConfig().getPendingAuthenticationTimeout();
|
||||||
if (timeout != 0) {
|
if (timeout != 0) {
|
||||||
message.append("\n\n")
|
message.append("\n\n")
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.util;
|
||||||
|
|
||||||
|
import net.raphimc.minecraftauth.util.logging.ILogger;
|
||||||
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
|
|
||||||
|
public class MinecraftAuthLogger implements ILogger {
|
||||||
|
|
||||||
|
public static final MinecraftAuthLogger INSTANCE = new MinecraftAuthLogger();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(String message) {
|
||||||
|
GeyserImpl.getInstance().getLogger().debug(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warn(String message) {
|
||||||
|
GeyserImpl.getInstance().getLogger().warning(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(String message) {
|
||||||
|
GeyserImpl.getInstance().getLogger().error(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,8 +12,8 @@ gson = "2.3.1" # Provided by Spigot 1.8.8
|
||||||
websocket = "1.5.1"
|
websocket = "1.5.1"
|
||||||
protocol = "3.0.0.Beta2-20240704.153116-14"
|
protocol = "3.0.0.Beta2-20240704.153116-14"
|
||||||
raknet = "1.0.0.CR3-20240416.144209-1"
|
raknet = "1.0.0.CR3-20240416.144209-1"
|
||||||
mcauthlib = "e5b0bcc"
|
minecraftauth = "4.1.0"
|
||||||
mcprotocollib = "1.21-20240616.154144-5"
|
mcprotocollib = "1.21-20240718.102029-13"
|
||||||
adventure = "4.14.0"
|
adventure = "4.14.0"
|
||||||
adventure-platform = "4.3.0"
|
adventure-platform = "4.3.0"
|
||||||
junit = "5.9.2"
|
junit = "5.9.2"
|
||||||
|
@ -107,7 +107,7 @@ commodore = { group = "me.lucko", name = "commodore", version.ref = "commodore"
|
||||||
guava = { group = "com.google.guava", name = "guava", version.ref = "guava" }
|
guava = { group = "com.google.guava", name = "guava", version.ref = "guava" }
|
||||||
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
||||||
junit = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" }
|
junit = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" }
|
||||||
mcauthlib = { group = "com.github.GeyserMC", name = "MCAuthLib", version.ref = "mcauthlib" }
|
minecraftauth = { group = "net.raphimc", name = "MinecraftAuth", version.ref = "minecraftauth" }
|
||||||
mcprotocollib = { group = "org.geysermc.mcprotocollib", name = "protocol", version.ref = "mcprotocollib" }
|
mcprotocollib = { group = "org.geysermc.mcprotocollib", name = "protocol", version.ref = "mcprotocollib" }
|
||||||
raknet = { group = "org.cloudburstmc.netty", name = "netty-transport-raknet", version.ref = "raknet" }
|
raknet = { group = "org.cloudburstmc.netty", name = "netty-transport-raknet", version.ref = "raknet" }
|
||||||
terminalconsoleappender = { group = "net.minecrell", name = "terminalconsoleappender", version.ref = "terminalconsoleappender" }
|
terminalconsoleappender = { group = "net.minecrell", name = "terminalconsoleappender", version.ref = "terminalconsoleappender" }
|
||||||
|
|
Loading…
Reference in a new issue