forked from GeyserMC/Geyser
		
	Microsoft account authentication (#1808)
Microsoft accounts can now use Geyser, while maintaining full backwards compatibility with Mojang accounts. Co-authored-by: Camotoy <20743703+Camotoy@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									6aa74a2322
								
							
						
					
					
						commit
						1c0cc4622a
					
				
					 11 changed files with 338 additions and 153 deletions
				
			
		| 
						 | 
				
			
			@ -132,6 +132,10 @@
 | 
			
		|||
                    <groupId>com.github.steveice10</groupId>
 | 
			
		||||
                    <artifactId>packetlib</artifactId>
 | 
			
		||||
                </exclusion>
 | 
			
		||||
                <exclusion>
 | 
			
		||||
                    <groupId>com.github.steveice10</groupId>
 | 
			
		||||
                    <artifactId>mcauthlib</artifactId>
 | 
			
		||||
                </exclusion>
 | 
			
		||||
            </exclusions>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
| 
						 | 
				
			
			@ -198,6 +202,11 @@
 | 
			
		|||
            <version>4.13.1</version>
 | 
			
		||||
            <scope>test</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.github.GeyserMC</groupId>
 | 
			
		||||
            <artifactId>MCAuthLib</artifactId>
 | 
			
		||||
            <version>0e48a094f2</version>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -86,6 +86,11 @@ public class GeyserConnector {
 | 
			
		|||
    public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs
 | 
			
		||||
    public static final String VERSION = "DEV"; // A fallback for running in IDEs
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Oauth client ID for Microsoft authentication
 | 
			
		||||
     */
 | 
			
		||||
    public static final String OAUTH_CLIENT_ID = "204cefd1-4818-4de1-b98d-513fae875d88";
 | 
			
		||||
 | 
			
		||||
    private static final String IP_REGEX = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b";
 | 
			
		||||
 | 
			
		||||
    private final List<GeyserSession> players = new ArrayList<>();
 | 
			
		||||
| 
						 | 
				
			
			@ -101,8 +106,8 @@ public class GeyserConnector {
 | 
			
		|||
    private final ScheduledExecutorService generalThreadPool;
 | 
			
		||||
 | 
			
		||||
    private BedrockServer bedrockServer;
 | 
			
		||||
    private PlatformType platformType;
 | 
			
		||||
    private GeyserBootstrap bootstrap;
 | 
			
		||||
    private final PlatformType platformType;
 | 
			
		||||
    private final GeyserBootstrap bootstrap;
 | 
			
		||||
 | 
			
		||||
    private Metrics metrics;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -118,6 +118,8 @@ public interface GeyserConfiguration {
 | 
			
		|||
 | 
			
		||||
        String getAuthType();
 | 
			
		||||
 | 
			
		||||
        boolean isPasswordAuthentication();
 | 
			
		||||
 | 
			
		||||
        boolean isUseProxyProtocol();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -125,6 +127,12 @@ public interface GeyserConfiguration {
 | 
			
		|||
        String getEmail();
 | 
			
		||||
 | 
			
		||||
        String getPassword();
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Will be removed after Microsoft accounts are fully migrated
 | 
			
		||||
         */
 | 
			
		||||
        @Deprecated
 | 
			
		||||
        boolean isMicrosoftAccount();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    interface IMetricsInfo {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -149,17 +149,24 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
 | 
			
		|||
        @JsonProperty("auth-type")
 | 
			
		||||
        private String authType = "online";
 | 
			
		||||
 | 
			
		||||
        @JsonProperty("allow-password-authentication")
 | 
			
		||||
        private boolean passwordAuthentication = true;
 | 
			
		||||
 | 
			
		||||
        @JsonProperty("use-proxy-protocol")
 | 
			
		||||
        private boolean useProxyProtocol = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Getter
 | 
			
		||||
    @JsonIgnoreProperties(ignoreUnknown = true) // DO NOT REMOVE THIS! Otherwise, after we remove microsoft-account configs will not load
 | 
			
		||||
    public static class UserAuthenticationInfo implements IUserAuthenticationInfo {
 | 
			
		||||
        @AsteriskSerializer.Asterisk()
 | 
			
		||||
        private String email;
 | 
			
		||||
 | 
			
		||||
        @AsteriskSerializer.Asterisk()
 | 
			
		||||
        private String password;
 | 
			
		||||
 | 
			
		||||
        @JsonProperty("microsoft-account")
 | 
			
		||||
        private boolean microsoftAccount = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Getter
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -161,6 +161,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
 | 
			
		|||
 | 
			
		||||
            if (info != null) {
 | 
			
		||||
                connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().getName()));
 | 
			
		||||
                session.setMicrosoftAccount(info.isMicrosoftAccount());
 | 
			
		||||
                session.authenticate(info.getEmail(), info.getPassword());
 | 
			
		||||
 | 
			
		||||
                // TODO send a message to bedrock user telling them they are connected (if nothing like a motd
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,8 +26,12 @@
 | 
			
		|||
package org.geysermc.connector.network.session;
 | 
			
		||||
 | 
			
		||||
import com.github.steveice10.mc.auth.data.GameProfile;
 | 
			
		||||
import com.github.steveice10.mc.auth.exception.request.AuthPendingException;
 | 
			
		||||
import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException;
 | 
			
		||||
import com.github.steveice10.mc.auth.exception.request.RequestException;
 | 
			
		||||
import com.github.steveice10.mc.auth.service.AuthenticationService;
 | 
			
		||||
import com.github.steveice10.mc.auth.service.MojangAuthenticationService;
 | 
			
		||||
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
 | 
			
		||||
import com.github.steveice10.mc.protocol.MinecraftConstants;
 | 
			
		||||
import com.github.steveice10.mc.protocol.MinecraftProtocol;
 | 
			
		||||
import com.github.steveice10.mc.protocol.data.SubProtocol;
 | 
			
		||||
| 
						 | 
				
			
			@ -110,6 +114,10 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
    @Setter
 | 
			
		||||
    private BedrockClientData clientData;
 | 
			
		||||
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    @Setter
 | 
			
		||||
    private boolean microsoftAccount;
 | 
			
		||||
 | 
			
		||||
    private final SessionPlayerEntity playerEntity;
 | 
			
		||||
    private PlayerInventory inventory;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -257,7 +265,6 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
    /**
 | 
			
		||||
     * Controls whether the daylight cycle gamerule has been sent to the client, so the sun/moon remain motionless.
 | 
			
		||||
     */
 | 
			
		||||
    @Setter
 | 
			
		||||
    private boolean daylightCycle = true;
 | 
			
		||||
 | 
			
		||||
    private boolean reducedDebugInfo = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -443,139 +450,22 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
        new Thread(() -> {
 | 
			
		||||
            try {
 | 
			
		||||
                if (password != null && !password.isEmpty()) {
 | 
			
		||||
                    protocol = new MinecraftProtocol(username, password);
 | 
			
		||||
                    AuthenticationService authenticationService;
 | 
			
		||||
                    if (microsoftAccount) {
 | 
			
		||||
                        authenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        authenticationService = new MojangAuthenticationService();
 | 
			
		||||
                    }
 | 
			
		||||
                    authenticationService.setUsername(username);
 | 
			
		||||
                    authenticationService.setPassword(password);
 | 
			
		||||
                    authenticationService.login();
 | 
			
		||||
 | 
			
		||||
                    protocol = new MinecraftProtocol(authenticationService);
 | 
			
		||||
                } else {
 | 
			
		||||
                    protocol = new MinecraftProtocol(username);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE;
 | 
			
		||||
                final PublicKey publicKey;
 | 
			
		||||
 | 
			
		||||
                if (floodgate) {
 | 
			
		||||
                    PublicKey key = null;
 | 
			
		||||
                    try {
 | 
			
		||||
                        key = EncryptionUtil.getKeyFromFile(
 | 
			
		||||
                                connector.getConfig().getFloodgateKeyPath(),
 | 
			
		||||
                                PublicKey.class
 | 
			
		||||
                        );
 | 
			
		||||
                    } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
 | 
			
		||||
                        connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e);
 | 
			
		||||
                    }
 | 
			
		||||
                    publicKey = key;
 | 
			
		||||
                } else publicKey = null;
 | 
			
		||||
 | 
			
		||||
                if (publicKey != null) {
 | 
			
		||||
                    connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key"));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Start ticking
 | 
			
		||||
                tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS);
 | 
			
		||||
 | 
			
		||||
                downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory());
 | 
			
		||||
                if (connector.getConfig().getRemote().isUseProxyProtocol()) {
 | 
			
		||||
                    downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true);
 | 
			
		||||
                    downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
 | 
			
		||||
                }
 | 
			
		||||
                // Let Geyser handle sending the keep alive
 | 
			
		||||
                downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
 | 
			
		||||
                downstream.getSession().addListener(new SessionAdapter() {
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public void packetSending(PacketSendingEvent event) {
 | 
			
		||||
                        //todo move this somewhere else
 | 
			
		||||
                        if (event.getPacket() instanceof HandshakePacket && floodgate) {
 | 
			
		||||
                            String encrypted = "";
 | 
			
		||||
                            try {
 | 
			
		||||
                                encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData(
 | 
			
		||||
                                        clientData.getGameVersion(),
 | 
			
		||||
                                        authData.getName(),
 | 
			
		||||
                                        authData.getXboxUUID(),
 | 
			
		||||
                                        clientData.getDeviceOS().ordinal(),
 | 
			
		||||
                                        clientData.getLanguageCode(),
 | 
			
		||||
                                        clientData.getCurrentInputMode().ordinal(),
 | 
			
		||||
                                        upstream.getSession().getAddress().getAddress().getHostAddress()
 | 
			
		||||
                                ));
 | 
			
		||||
                            } catch (Exception e) {
 | 
			
		||||
                                connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            HandshakePacket handshakePacket = event.getPacket();
 | 
			
		||||
                            event.setPacket(new HandshakePacket(
 | 
			
		||||
                                    handshakePacket.getProtocolVersion(),
 | 
			
		||||
                                    handshakePacket.getHostname() + '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted,
 | 
			
		||||
                                    handshakePacket.getPort(),
 | 
			
		||||
                                    handshakePacket.getIntent()
 | 
			
		||||
                            ));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public void connected(ConnectedEvent event) {
 | 
			
		||||
                        loggingIn = false;
 | 
			
		||||
                        loggedIn = true;
 | 
			
		||||
                        if (protocol.getProfile() == null) {
 | 
			
		||||
                            // Java account is offline
 | 
			
		||||
                            disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                        connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.connect", authData.getName(), protocol.getProfile().getName(), remoteServer.getAddress()));
 | 
			
		||||
                        playerEntity.setUuid(protocol.getProfile().getId());
 | 
			
		||||
                        playerEntity.setUsername(protocol.getProfile().getName());
 | 
			
		||||
 | 
			
		||||
                        String locale = clientData.getLanguageCode();
 | 
			
		||||
 | 
			
		||||
                        // Let the user know there locale may take some time to download
 | 
			
		||||
                        // as it has to be extracted from a JAR
 | 
			
		||||
                        if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) {
 | 
			
		||||
                            // This should probably be left hardcoded as it will only show for en_us clients
 | 
			
		||||
                            sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time");
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        // Download and load the language for the player
 | 
			
		||||
                        LocaleUtils.downloadAndLoadLocale(locale);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public void disconnected(DisconnectedEvent event) {
 | 
			
		||||
                        loggingIn = false;
 | 
			
		||||
                        loggedIn = false;
 | 
			
		||||
                        connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.disconnect", authData.getName(), remoteServer.getAddress(), event.getReason()));
 | 
			
		||||
                        if (event.getCause() != null) {
 | 
			
		||||
                            event.getCause().printStackTrace();
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        upstream.disconnect(MessageTranslator.convertMessageLenient(event.getReason()));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public void packetReceived(PacketReceivedEvent event) {
 | 
			
		||||
                        if (!closed) {
 | 
			
		||||
                            // Required, or else Floodgate players break with Bukkit chunk caching
 | 
			
		||||
                            if (event.getPacket() instanceof LoginSuccessPacket) {
 | 
			
		||||
                                GameProfile profile = ((LoginSuccessPacket) event.getPacket()).getProfile();
 | 
			
		||||
                                playerEntity.setUsername(profile.getName());
 | 
			
		||||
                                playerEntity.setUuid(profile.getId());
 | 
			
		||||
 | 
			
		||||
                                // Check if they are not using a linked account
 | 
			
		||||
                                if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) {
 | 
			
		||||
                                    SkinManager.handleBedrockSkin(playerEntity, clientData);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public void packetError(PacketErrorEvent event) {
 | 
			
		||||
                        connector.getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.downstream_error", event.getCause().getMessage()));
 | 
			
		||||
                        if (connector.getConfig().isDebugMode())
 | 
			
		||||
                            event.getCause().printStackTrace();
 | 
			
		||||
                        event.setSuppress(true);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                downstream.getSession().connect();
 | 
			
		||||
                connector.addPlayer(this);
 | 
			
		||||
                connectDownstream();
 | 
			
		||||
            } catch (InvalidCredentialsException | IllegalArgumentException e) {
 | 
			
		||||
                connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", username));
 | 
			
		||||
                disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode()));
 | 
			
		||||
| 
						 | 
				
			
			@ -585,6 +475,199 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
        }).start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Present a form window to the user asking to log in with another web browser
 | 
			
		||||
     */
 | 
			
		||||
    public void authenticateWithMicrosoftCode() {
 | 
			
		||||
        if (loggedIn) {
 | 
			
		||||
            connector.getLogger().severe(LanguageUtils.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().getName()));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        loggingIn = true;
 | 
			
		||||
        // new thread so clients don't timeout
 | 
			
		||||
        new Thread(() -> {
 | 
			
		||||
            try {
 | 
			
		||||
                MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID);
 | 
			
		||||
 | 
			
		||||
                MsaAuthenticationService.MsCodeResponse response = msaAuthenticationService.getAuthCode();
 | 
			
		||||
                LoginEncryptionUtils.showMicrosoftCodeWindow(this, response);
 | 
			
		||||
 | 
			
		||||
                // This just looks cool
 | 
			
		||||
                SetTimePacket packet = new SetTimePacket();
 | 
			
		||||
                packet.setTime(16000);
 | 
			
		||||
                sendUpstreamPacket(packet);
 | 
			
		||||
 | 
			
		||||
                // Wait for the code to validate
 | 
			
		||||
                attemptCodeAuthentication(msaAuthenticationService);
 | 
			
		||||
            } catch (InvalidCredentialsException | IllegalArgumentException e) {
 | 
			
		||||
                connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", getAuthData().getName()));
 | 
			
		||||
                disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode()));
 | 
			
		||||
            } catch (RequestException ex) {
 | 
			
		||||
                ex.printStackTrace();
 | 
			
		||||
            }
 | 
			
		||||
        }).start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Poll every second to see if the user has successfully signed in
 | 
			
		||||
     */
 | 
			
		||||
    private void attemptCodeAuthentication(MsaAuthenticationService msaAuthenticationService) {
 | 
			
		||||
        if (loggedIn || closed) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            msaAuthenticationService.login();
 | 
			
		||||
            protocol = new MinecraftProtocol(msaAuthenticationService);
 | 
			
		||||
 | 
			
		||||
            connectDownstream();
 | 
			
		||||
        } catch (RequestException e) {
 | 
			
		||||
            if (!(e instanceof AuthPendingException)) {
 | 
			
		||||
                e.printStackTrace();
 | 
			
		||||
            } else {
 | 
			
		||||
                // Wait one second before trying again
 | 
			
		||||
                connector.getGeneralThreadPool().schedule(() -> attemptCodeAuthentication(msaAuthenticationService), 1, TimeUnit.SECONDS);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * After getting whatever credentials needed, we attempt to join the Java server.
 | 
			
		||||
     */
 | 
			
		||||
    private void connectDownstream() {
 | 
			
		||||
        boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE;
 | 
			
		||||
        final PublicKey publicKey;
 | 
			
		||||
 | 
			
		||||
        if (floodgate) {
 | 
			
		||||
            PublicKey key = null;
 | 
			
		||||
            try {
 | 
			
		||||
                key = EncryptionUtil.getKeyFromFile(
 | 
			
		||||
                        connector.getConfig().getFloodgateKeyPath(),
 | 
			
		||||
                        PublicKey.class
 | 
			
		||||
                );
 | 
			
		||||
            } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
 | 
			
		||||
                connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e);
 | 
			
		||||
            }
 | 
			
		||||
            publicKey = key;
 | 
			
		||||
        } else publicKey = null;
 | 
			
		||||
 | 
			
		||||
        if (publicKey != null) {
 | 
			
		||||
            connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key"));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Start ticking
 | 
			
		||||
        tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS);
 | 
			
		||||
 | 
			
		||||
        downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory());
 | 
			
		||||
        if (connector.getConfig().getRemote().isUseProxyProtocol()) {
 | 
			
		||||
            downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true);
 | 
			
		||||
            downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
 | 
			
		||||
        }
 | 
			
		||||
        // Let Geyser handle sending the keep alive
 | 
			
		||||
        downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
 | 
			
		||||
        downstream.getSession().addListener(new SessionAdapter() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void packetSending(PacketSendingEvent event) {
 | 
			
		||||
                //todo move this somewhere else
 | 
			
		||||
                if (event.getPacket() instanceof HandshakePacket && floodgate) {
 | 
			
		||||
                    String encrypted = "";
 | 
			
		||||
                    try {
 | 
			
		||||
                        encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData(
 | 
			
		||||
                                clientData.getGameVersion(),
 | 
			
		||||
                                authData.getName(),
 | 
			
		||||
                                authData.getXboxUUID(),
 | 
			
		||||
                                clientData.getDeviceOS().ordinal(),
 | 
			
		||||
                                clientData.getLanguageCode(),
 | 
			
		||||
                                clientData.getCurrentInputMode().ordinal(),
 | 
			
		||||
                                upstream.getSession().getAddress().getAddress().getHostAddress()
 | 
			
		||||
                        ));
 | 
			
		||||
                    } catch (Exception e) {
 | 
			
		||||
                        connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    HandshakePacket handshakePacket = event.getPacket();
 | 
			
		||||
                    event.setPacket(new HandshakePacket(
 | 
			
		||||
                            handshakePacket.getProtocolVersion(),
 | 
			
		||||
                            handshakePacket.getHostname() + '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted,
 | 
			
		||||
                            handshakePacket.getPort(),
 | 
			
		||||
                            handshakePacket.getIntent()
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void connected(ConnectedEvent event) {
 | 
			
		||||
                loggingIn = false;
 | 
			
		||||
                loggedIn = true;
 | 
			
		||||
                if (protocol.getProfile() == null) {
 | 
			
		||||
                    // Java account is offline
 | 
			
		||||
                    disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.connect", authData.getName(), protocol.getProfile().getName(), remoteServer.getAddress()));
 | 
			
		||||
                playerEntity.setUuid(protocol.getProfile().getId());
 | 
			
		||||
                playerEntity.setUsername(protocol.getProfile().getName());
 | 
			
		||||
 | 
			
		||||
                String locale = clientData.getLanguageCode();
 | 
			
		||||
 | 
			
		||||
                // Let the user know there locale may take some time to download
 | 
			
		||||
                // as it has to be extracted from a JAR
 | 
			
		||||
                if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) {
 | 
			
		||||
                    // This should probably be left hardcoded as it will only show for en_us clients
 | 
			
		||||
                    sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Download and load the language for the player
 | 
			
		||||
                LocaleUtils.downloadAndLoadLocale(locale);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void disconnected(DisconnectedEvent event) {
 | 
			
		||||
                loggingIn = false;
 | 
			
		||||
                loggedIn = false;
 | 
			
		||||
                connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.disconnect", authData.getName(), remoteServer.getAddress(), event.getReason()));
 | 
			
		||||
                if (event.getCause() != null) {
 | 
			
		||||
                    event.getCause().printStackTrace();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                upstream.disconnect(MessageTranslator.convertMessageLenient(event.getReason()));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void packetReceived(PacketReceivedEvent event) {
 | 
			
		||||
                if (!closed) {
 | 
			
		||||
                    // Required, or else Floodgate players break with Bukkit chunk caching
 | 
			
		||||
                    if (event.getPacket() instanceof LoginSuccessPacket) {
 | 
			
		||||
                        GameProfile profile = ((LoginSuccessPacket) event.getPacket()).getProfile();
 | 
			
		||||
                        playerEntity.setUsername(profile.getName());
 | 
			
		||||
                        playerEntity.setUuid(profile.getId());
 | 
			
		||||
 | 
			
		||||
                        // Check if they are not using a linked account
 | 
			
		||||
                        if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) {
 | 
			
		||||
                            SkinManager.handleBedrockSkin(playerEntity, clientData);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void packetError(PacketErrorEvent event) {
 | 
			
		||||
                connector.getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.downstream_error", event.getCause().getMessage()));
 | 
			
		||||
                if (connector.getConfig().isDebugMode())
 | 
			
		||||
                    event.getCause().printStackTrace();
 | 
			
		||||
                event.setSuppress(true);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (!daylightCycle) {
 | 
			
		||||
            setDaylightCycle(true);
 | 
			
		||||
        }
 | 
			
		||||
        downstream.getSession().connect();
 | 
			
		||||
        connector.addPlayer(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void disconnect(String reason) {
 | 
			
		||||
        if (!closed) {
 | 
			
		||||
            loggedIn = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -872,6 +955,18 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
        reducedDebugInfo = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Changes the daylight cycle gamerule on the client
 | 
			
		||||
     * This is used in the login screen along-side normal usage
 | 
			
		||||
     *
 | 
			
		||||
     * @param doCycle If the cycle should continue
 | 
			
		||||
     */
 | 
			
		||||
    public void setDaylightCycle(boolean doCycle) {
 | 
			
		||||
        sendGameRule("dodaylightcycle", doCycle);
 | 
			
		||||
        // Save the value so we don't have to constantly send a daylight cycle gamerule update
 | 
			
		||||
        this.daylightCycle = doCycle;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send a gamerule value to the client
 | 
			
		||||
     *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -81,7 +81,7 @@ public class JavaPlayerPositionRotationTranslator extends PacketTranslator<Serve
 | 
			
		|||
 | 
			
		||||
            ChunkUtils.updateChunkPosition(session, pos.toInt());
 | 
			
		||||
 | 
			
		||||
            session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.entity.player.spawn", packet.getX(), packet.getY(), packet.getZ()));
 | 
			
		||||
            session.getConnector().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.entity.player.spawn", packet.getX(), packet.getY(), packet.getZ()));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,17 +46,10 @@ public class JavaUpdateTimeTranslator extends PacketTranslator<ServerUpdateTimeP
 | 
			
		|||
        session.sendUpstreamPacket(setTimePacket);
 | 
			
		||||
        if (!session.isDaylightCycle() && time >= 0) {
 | 
			
		||||
            // Client thinks there is no daylight cycle but there is
 | 
			
		||||
            setDoDaylightCycleGamerule(session, true);
 | 
			
		||||
            session.setDaylightCycle(true);
 | 
			
		||||
        } else if (session.isDaylightCycle() && time < 0) {
 | 
			
		||||
            // Client thinks there is daylight cycle but there isn't
 | 
			
		||||
            setDoDaylightCycleGamerule(session, false);
 | 
			
		||||
            session.setDaylightCycle(false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void setDoDaylightCycleGamerule(GeyserSession session, boolean doCycle) {
 | 
			
		||||
        session.sendGameRule("dodaylightcycle", doCycle);
 | 
			
		||||
        // Save the value so we don't have to constantly send a daylight cycle gamerule update
 | 
			
		||||
        session.setDaylightCycle(doCycle);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,19 +29,18 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
 | 
			
		|||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
import com.fasterxml.jackson.databind.node.JsonNodeType;
 | 
			
		||||
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
 | 
			
		||||
import com.nimbusds.jose.JWSObject;
 | 
			
		||||
import com.nukkitx.network.util.Preconditions;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.packet.LoginPacket;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.util.EncryptionUtils;
 | 
			
		||||
import org.geysermc.common.window.CustomFormBuilder;
 | 
			
		||||
import org.geysermc.common.window.CustomFormWindow;
 | 
			
		||||
import org.geysermc.common.window.FormWindow;
 | 
			
		||||
import org.geysermc.common.window.SimpleFormWindow;
 | 
			
		||||
import org.geysermc.common.window.*;
 | 
			
		||||
import org.geysermc.common.window.button.FormButton;
 | 
			
		||||
import org.geysermc.common.window.component.InputComponent;
 | 
			
		||||
import org.geysermc.common.window.component.LabelComponent;
 | 
			
		||||
import org.geysermc.common.window.response.CustomFormResponse;
 | 
			
		||||
import org.geysermc.common.window.response.ModalFormResponse;
 | 
			
		||||
import org.geysermc.common.window.response.SimpleFormResponse;
 | 
			
		||||
import org.geysermc.connector.GeyserConnector;
 | 
			
		||||
import org.geysermc.connector.network.session.GeyserSession;
 | 
			
		||||
| 
						 | 
				
			
			@ -156,13 +155,21 @@ public class LoginEncryptionUtils {
 | 
			
		|||
        session.sendUpstreamPacketImmediately(packet);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static int AUTH_FORM_ID = 1336;
 | 
			
		||||
    private static int AUTH_DETAILS_FORM_ID = 1337;
 | 
			
		||||
    private static final int AUTH_MSA_DETAILS_FORM_ID = 1334;
 | 
			
		||||
    private static final int AUTH_MSA_CODE_FORM_ID = 1335;
 | 
			
		||||
    private static final int AUTH_FORM_ID = 1336;
 | 
			
		||||
    private static final int AUTH_DETAILS_FORM_ID = 1337;
 | 
			
		||||
 | 
			
		||||
    public static void showLoginWindow(GeyserSession session) {
 | 
			
		||||
        // Set DoDaylightCycle to false so the time doesn't accelerate while we're here
 | 
			
		||||
        session.setDaylightCycle(false);
 | 
			
		||||
 | 
			
		||||
        String userLanguage = session.getLocale();
 | 
			
		||||
        SimpleFormWindow window = new SimpleFormWindow(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.title", userLanguage), LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.desc", userLanguage));
 | 
			
		||||
        window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login", userLanguage)));
 | 
			
		||||
        if (session.getConnector().getConfig().getRemote().isPasswordAuthentication()) {
 | 
			
		||||
            window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.mojang", userLanguage)));
 | 
			
		||||
        }
 | 
			
		||||
        window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.microsoft", userLanguage)));
 | 
			
		||||
        window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage)));
 | 
			
		||||
 | 
			
		||||
        session.sendForm(window, AUTH_FORM_ID);
 | 
			
		||||
| 
						 | 
				
			
			@ -179,12 +186,33 @@ public class LoginEncryptionUtils {
 | 
			
		|||
        session.sendForm(window, AUTH_DETAILS_FORM_ID);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Prompts the user between either OAuth code login or manual password authentication
 | 
			
		||||
     */
 | 
			
		||||
    public static void showMicrosoftAuthenticationWindow(GeyserSession session) {
 | 
			
		||||
        String userLanguage = session.getLocale();
 | 
			
		||||
        SimpleFormWindow window = new SimpleFormWindow(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.microsoft", userLanguage), "");
 | 
			
		||||
        window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.method.browser", userLanguage)));
 | 
			
		||||
        window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.method.password", userLanguage))); // This form won't show if password authentication is disabled
 | 
			
		||||
        window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage)));
 | 
			
		||||
        session.sendForm(window, AUTH_MSA_DETAILS_FORM_ID);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shows the code that a user must input into their browser
 | 
			
		||||
     */
 | 
			
		||||
    public static void showMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse response) {
 | 
			
		||||
        ModalFormWindow msaCodeWindow = new ModalFormWindow("%xbox.signin", "%xbox.signin.website\n%xbox.signin.url\n%xbox.signin.enterCode\n" +
 | 
			
		||||
                response.user_code, "Done", "%menu.disconnect");
 | 
			
		||||
        session.sendForm(msaCodeWindow, LoginEncryptionUtils.AUTH_MSA_CODE_FORM_ID);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, int formId, String formData) {
 | 
			
		||||
        WindowCache windowCache = session.getWindowCache();
 | 
			
		||||
        if (!windowCache.getWindows().containsKey(formId))
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        if(formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID) {
 | 
			
		||||
        if (formId == AUTH_MSA_DETAILS_FORM_ID || formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID || formId == AUTH_MSA_CODE_FORM_ID) {
 | 
			
		||||
            FormWindow window = windowCache.getWindows().remove(formId);
 | 
			
		||||
            window.setResponse(formData.trim());
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -205,16 +233,50 @@ public class LoginEncryptionUtils {
 | 
			
		|||
                        showLoginDetailsWindow(session);
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (formId == AUTH_FORM_ID && window instanceof SimpleFormWindow) {
 | 
			
		||||
                    boolean isPasswordAuthentication = session.getConnector().getConfig().getRemote().isPasswordAuthentication();
 | 
			
		||||
                    int microsoftButton = isPasswordAuthentication ? 1 : 0;
 | 
			
		||||
                    int disconnectButton = isPasswordAuthentication ? 2 : 1;
 | 
			
		||||
                    SimpleFormResponse response = (SimpleFormResponse) window.getResponse();
 | 
			
		||||
                    if (response != null) {
 | 
			
		||||
                        if (response.getClickedButtonId() == 0) {
 | 
			
		||||
                        if (isPasswordAuthentication && response.getClickedButtonId() == 0) {
 | 
			
		||||
                            session.setMicrosoftAccount(false);
 | 
			
		||||
                            showLoginDetailsWindow(session);
 | 
			
		||||
                        } else if(response.getClickedButtonId() == 1) {
 | 
			
		||||
                        } else if (response.getClickedButtonId() == microsoftButton) {
 | 
			
		||||
                            session.setMicrosoftAccount(true);
 | 
			
		||||
                            if (isPasswordAuthentication) {
 | 
			
		||||
                                showMicrosoftAuthenticationWindow(session);
 | 
			
		||||
                            } else {
 | 
			
		||||
                                // Just show the OAuth code
 | 
			
		||||
                                session.authenticateWithMicrosoftCode();
 | 
			
		||||
                            }
 | 
			
		||||
                        } else if (response.getClickedButtonId() == disconnectButton) {
 | 
			
		||||
                            session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        showLoginWindow(session);
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (formId == AUTH_MSA_DETAILS_FORM_ID && window instanceof SimpleFormWindow) {
 | 
			
		||||
                    SimpleFormResponse response = (SimpleFormResponse) window.getResponse();
 | 
			
		||||
                    if (response != null) {
 | 
			
		||||
                        if (response.getClickedButtonId() == 0) {
 | 
			
		||||
                            session.authenticateWithMicrosoftCode();
 | 
			
		||||
                        } else if (response.getClickedButtonId() == 1) {
 | 
			
		||||
                            showLoginDetailsWindow(session);
 | 
			
		||||
                        } else if (response.getClickedButtonId() == 2) {
 | 
			
		||||
                            session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        showLoginWindow(session);
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (formId == AUTH_MSA_CODE_FORM_ID && window instanceof ModalFormWindow) {
 | 
			
		||||
                    ModalFormResponse response = (ModalFormResponse) window.getResponse();
 | 
			
		||||
                    if (response != null) {
 | 
			
		||||
                        if (response.getClickedButtonId() == 1) {
 | 
			
		||||
                            session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        showMicrosoftAuthenticationWindow(session);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,6 +32,9 @@ remote:
 | 
			
		|||
  port: 25565
 | 
			
		||||
  # Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate).
 | 
			
		||||
  auth-type: online
 | 
			
		||||
  # Allow for password-based authentication methods through Geyser. Only useful in online mode.
 | 
			
		||||
  # If this is false, users must authenticate to Microsoft using a code provided by Geyser on their desktop.
 | 
			
		||||
  allow-password-authentication: true
 | 
			
		||||
  # Whether to enable PROXY protocol or not while connecting to the server.
 | 
			
		||||
  # This is useful only when:
 | 
			
		||||
  # 1) Your server supports PROXY protocol (it probably doesn't)
 | 
			
		||||
| 
						 | 
				
			
			@ -52,10 +55,12 @@ floodgate-key-file: public-key.pem
 | 
			
		|||
#  BedrockAccountUsername: # Your Minecraft: Bedrock Edition username
 | 
			
		||||
#    email: javaccountemail@example.com # Your Minecraft: Java Edition email
 | 
			
		||||
#    password: javaccountpassword123 # Your Minecraft: Java Edition password
 | 
			
		||||
#    microsoft-account: true # Whether the account is a Mojang or Microsoft account.
 | 
			
		||||
#
 | 
			
		||||
#  bluerkelp2: 
 | 
			
		||||
#    email: not_really_my_email_address_mr_minecrafter53267@gmail.com 
 | 
			
		||||
#    password: "this isn't really my password"
 | 
			
		||||
#    microsoft-account: false
 | 
			
		||||
 | 
			
		||||
# Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands.
 | 
			
		||||
# Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +1 @@
 | 
			
		|||
Subproject commit 1a00766840baf1f512d98f5a75c177c8bcfba6f3
 | 
			
		||||
Subproject commit 6f246c24ddbd543a359d651e706da470fe53ceeb
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue