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>
|
<groupId>com.github.steveice10</groupId>
|
||||||
<artifactId>packetlib</artifactId>
|
<artifactId>packetlib</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.github.steveice10</groupId>
|
||||||
|
<artifactId>mcauthlib</artifactId>
|
||||||
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -198,6 +202,11 @@
|
||||||
<version>4.13.1</version>
|
<version>4.13.1</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.GeyserMC</groupId>
|
||||||
|
<artifactId>MCAuthLib</artifactId>
|
||||||
|
<version>0e48a094f2</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<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 GIT_VERSION = "DEV"; // A fallback for running in IDEs
|
||||||
public static final String 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 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<>();
|
private final List<GeyserSession> players = new ArrayList<>();
|
||||||
|
@ -101,8 +106,8 @@ public class GeyserConnector {
|
||||||
private final ScheduledExecutorService generalThreadPool;
|
private final ScheduledExecutorService generalThreadPool;
|
||||||
|
|
||||||
private BedrockServer bedrockServer;
|
private BedrockServer bedrockServer;
|
||||||
private PlatformType platformType;
|
private final PlatformType platformType;
|
||||||
private GeyserBootstrap bootstrap;
|
private final GeyserBootstrap bootstrap;
|
||||||
|
|
||||||
private Metrics metrics;
|
private Metrics metrics;
|
||||||
|
|
||||||
|
|
|
@ -118,6 +118,8 @@ public interface GeyserConfiguration {
|
||||||
|
|
||||||
String getAuthType();
|
String getAuthType();
|
||||||
|
|
||||||
|
boolean isPasswordAuthentication();
|
||||||
|
|
||||||
boolean isUseProxyProtocol();
|
boolean isUseProxyProtocol();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,6 +127,12 @@ public interface GeyserConfiguration {
|
||||||
String getEmail();
|
String getEmail();
|
||||||
|
|
||||||
String getPassword();
|
String getPassword();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will be removed after Microsoft accounts are fully migrated
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
boolean isMicrosoftAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMetricsInfo {
|
interface IMetricsInfo {
|
||||||
|
|
|
@ -149,17 +149,24 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
||||||
@JsonProperty("auth-type")
|
@JsonProperty("auth-type")
|
||||||
private String authType = "online";
|
private String authType = "online";
|
||||||
|
|
||||||
|
@JsonProperty("allow-password-authentication")
|
||||||
|
private boolean passwordAuthentication = true;
|
||||||
|
|
||||||
@JsonProperty("use-proxy-protocol")
|
@JsonProperty("use-proxy-protocol")
|
||||||
private boolean useProxyProtocol = false;
|
private boolean useProxyProtocol = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true) // DO NOT REMOVE THIS! Otherwise, after we remove microsoft-account configs will not load
|
||||||
public static class UserAuthenticationInfo implements IUserAuthenticationInfo {
|
public static class UserAuthenticationInfo implements IUserAuthenticationInfo {
|
||||||
@AsteriskSerializer.Asterisk()
|
@AsteriskSerializer.Asterisk()
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
@AsteriskSerializer.Asterisk()
|
@AsteriskSerializer.Asterisk()
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
|
@JsonProperty("microsoft-account")
|
||||||
|
private boolean microsoftAccount = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
|
|
@ -161,6 +161,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||||
|
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().getName()));
|
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().getName()));
|
||||||
|
session.setMicrosoftAccount(info.isMicrosoftAccount());
|
||||||
session.authenticate(info.getEmail(), info.getPassword());
|
session.authenticate(info.getEmail(), info.getPassword());
|
||||||
|
|
||||||
// TODO send a message to bedrock user telling them they are connected (if nothing like a motd
|
// 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;
|
package org.geysermc.connector.network.session;
|
||||||
|
|
||||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
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.InvalidCredentialsException;
|
||||||
import com.github.steveice10.mc.auth.exception.request.RequestException;
|
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.MinecraftConstants;
|
||||||
import com.github.steveice10.mc.protocol.MinecraftProtocol;
|
import com.github.steveice10.mc.protocol.MinecraftProtocol;
|
||||||
import com.github.steveice10.mc.protocol.data.SubProtocol;
|
import com.github.steveice10.mc.protocol.data.SubProtocol;
|
||||||
|
@ -110,6 +114,10 @@ public class GeyserSession implements CommandSender {
|
||||||
@Setter
|
@Setter
|
||||||
private BedrockClientData clientData;
|
private BedrockClientData clientData;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@Setter
|
||||||
|
private boolean microsoftAccount;
|
||||||
|
|
||||||
private final SessionPlayerEntity playerEntity;
|
private final SessionPlayerEntity playerEntity;
|
||||||
private PlayerInventory inventory;
|
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.
|
* 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 daylightCycle = true;
|
||||||
|
|
||||||
private boolean reducedDebugInfo = false;
|
private boolean reducedDebugInfo = false;
|
||||||
|
@ -443,139 +450,22 @@ public class GeyserSession implements CommandSender {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
if (password != null && !password.isEmpty()) {
|
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 {
|
} else {
|
||||||
protocol = new MinecraftProtocol(username);
|
protocol = new MinecraftProtocol(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE;
|
connectDownstream();
|
||||||
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);
|
|
||||||
} catch (InvalidCredentialsException | IllegalArgumentException e) {
|
} catch (InvalidCredentialsException | IllegalArgumentException e) {
|
||||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", username));
|
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", username));
|
||||||
disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode()));
|
disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode()));
|
||||||
|
@ -585,6 +475,199 @@ public class GeyserSession implements CommandSender {
|
||||||
}).start();
|
}).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) {
|
public void disconnect(String reason) {
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
loggedIn = false;
|
loggedIn = false;
|
||||||
|
@ -872,6 +955,18 @@ public class GeyserSession implements CommandSender {
|
||||||
reducedDebugInfo = value;
|
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
|
* Send a gamerule value to the client
|
||||||
*
|
*
|
||||||
|
|
|
@ -81,7 +81,7 @@ public class JavaPlayerPositionRotationTranslator extends PacketTranslator<Serve
|
||||||
|
|
||||||
ChunkUtils.updateChunkPosition(session, pos.toInt());
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,17 +46,10 @@ public class JavaUpdateTimeTranslator extends PacketTranslator<ServerUpdateTimeP
|
||||||
session.sendUpstreamPacket(setTimePacket);
|
session.sendUpstreamPacket(setTimePacket);
|
||||||
if (!session.isDaylightCycle() && time >= 0) {
|
if (!session.isDaylightCycle() && time >= 0) {
|
||||||
// Client thinks there is no daylight cycle but there is
|
// Client thinks there is no daylight cycle but there is
|
||||||
setDoDaylightCycleGamerule(session, true);
|
session.setDaylightCycle(true);
|
||||||
} else if (session.isDaylightCycle() && time < 0) {
|
} else if (session.isDaylightCycle() && time < 0) {
|
||||||
// Client thinks there is daylight cycle but there isn't
|
// 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.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.node.JsonNodeType;
|
import com.fasterxml.jackson.databind.node.JsonNodeType;
|
||||||
|
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
|
||||||
import com.nimbusds.jose.JWSObject;
|
import com.nimbusds.jose.JWSObject;
|
||||||
import com.nukkitx.network.util.Preconditions;
|
import com.nukkitx.network.util.Preconditions;
|
||||||
import com.nukkitx.protocol.bedrock.packet.LoginPacket;
|
import com.nukkitx.protocol.bedrock.packet.LoginPacket;
|
||||||
import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket;
|
import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket;
|
||||||
import com.nukkitx.protocol.bedrock.util.EncryptionUtils;
|
import com.nukkitx.protocol.bedrock.util.EncryptionUtils;
|
||||||
import org.geysermc.common.window.CustomFormBuilder;
|
import org.geysermc.common.window.*;
|
||||||
import org.geysermc.common.window.CustomFormWindow;
|
|
||||||
import org.geysermc.common.window.FormWindow;
|
|
||||||
import org.geysermc.common.window.SimpleFormWindow;
|
|
||||||
import org.geysermc.common.window.button.FormButton;
|
import org.geysermc.common.window.button.FormButton;
|
||||||
import org.geysermc.common.window.component.InputComponent;
|
import org.geysermc.common.window.component.InputComponent;
|
||||||
import org.geysermc.common.window.component.LabelComponent;
|
import org.geysermc.common.window.component.LabelComponent;
|
||||||
import org.geysermc.common.window.response.CustomFormResponse;
|
import org.geysermc.common.window.response.CustomFormResponse;
|
||||||
|
import org.geysermc.common.window.response.ModalFormResponse;
|
||||||
import org.geysermc.common.window.response.SimpleFormResponse;
|
import org.geysermc.common.window.response.SimpleFormResponse;
|
||||||
import org.geysermc.connector.GeyserConnector;
|
import org.geysermc.connector.GeyserConnector;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
@ -156,13 +155,21 @@ public class LoginEncryptionUtils {
|
||||||
session.sendUpstreamPacketImmediately(packet);
|
session.sendUpstreamPacketImmediately(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int AUTH_FORM_ID = 1336;
|
private static final int AUTH_MSA_DETAILS_FORM_ID = 1334;
|
||||||
private static int AUTH_DETAILS_FORM_ID = 1337;
|
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) {
|
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();
|
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));
|
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)));
|
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage)));
|
||||||
|
|
||||||
session.sendForm(window, AUTH_FORM_ID);
|
session.sendForm(window, AUTH_FORM_ID);
|
||||||
|
@ -179,12 +186,33 @@ public class LoginEncryptionUtils {
|
||||||
session.sendForm(window, AUTH_DETAILS_FORM_ID);
|
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) {
|
public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, int formId, String formData) {
|
||||||
WindowCache windowCache = session.getWindowCache();
|
WindowCache windowCache = session.getWindowCache();
|
||||||
if (!windowCache.getWindows().containsKey(formId))
|
if (!windowCache.getWindows().containsKey(formId))
|
||||||
return false;
|
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);
|
FormWindow window = windowCache.getWindows().remove(formId);
|
||||||
window.setResponse(formData.trim());
|
window.setResponse(formData.trim());
|
||||||
|
|
||||||
|
@ -205,16 +233,50 @@ public class LoginEncryptionUtils {
|
||||||
showLoginDetailsWindow(session);
|
showLoginDetailsWindow(session);
|
||||||
}
|
}
|
||||||
} else if (formId == AUTH_FORM_ID && window instanceof SimpleFormWindow) {
|
} 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();
|
SimpleFormResponse response = (SimpleFormResponse) window.getResponse();
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
if (response.getClickedButtonId() == 0) {
|
if (isPasswordAuthentication && response.getClickedButtonId() == 0) {
|
||||||
|
session.setMicrosoftAccount(false);
|
||||||
showLoginDetailsWindow(session);
|
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()));
|
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showLoginWindow(session);
|
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
|
port: 25565
|
||||||
# Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate).
|
# Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate).
|
||||||
auth-type: online
|
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.
|
# Whether to enable PROXY protocol or not while connecting to the server.
|
||||||
# This is useful only when:
|
# This is useful only when:
|
||||||
# 1) Your server supports PROXY protocol (it probably doesn't)
|
# 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
|
# BedrockAccountUsername: # Your Minecraft: Bedrock Edition username
|
||||||
# email: javaccountemail@example.com # Your Minecraft: Java Edition email
|
# email: javaccountemail@example.com # Your Minecraft: Java Edition email
|
||||||
# password: javaccountpassword123 # Your Minecraft: Java Edition password
|
# password: javaccountpassword123 # Your Minecraft: Java Edition password
|
||||||
|
# microsoft-account: true # Whether the account is a Mojang or Microsoft account.
|
||||||
#
|
#
|
||||||
# bluerkelp2:
|
# bluerkelp2:
|
||||||
# email: not_really_my_email_address_mr_minecrafter53267@gmail.com
|
# email: not_really_my_email_address_mr_minecrafter53267@gmail.com
|
||||||
# password: "this isn't really my password"
|
# 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.
|
# 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.
|
# Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients.
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 1a00766840baf1f512d98f5a75c177c8bcfba6f3
|
Subproject commit 6f246c24ddbd543a359d651e706da470fe53ceeb
|
Loading…
Reference in a new issue