Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-09-08 20:43:04 +02:00
Allow single-device Microsoft authentication (#2688)
By default, there is a two-minute delay if you disconnect so you can authenticate your Microsoft account. Co-authored-by: rtm516 <rtm516@users.noreply.github.com> Co-authored-by: Camotoy <20743703+Camotoy@users.noreply.github.com>
Dieser Commit ist enthalten in:
Ursprung
0251bb64b8
Commit
d0220a9b71
@ -59,6 +59,7 @@ import org.geysermc.geyser.registry.BlockRegistries;
|
|||||||
import org.geysermc.geyser.registry.Registries;
|
import org.geysermc.geyser.registry.Registries;
|
||||||
import org.geysermc.geyser.scoreboard.ScoreboardUpdater;
|
import org.geysermc.geyser.scoreboard.ScoreboardUpdater;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
import org.geysermc.geyser.session.PendingMicrosoftAuthentication;
|
||||||
import org.geysermc.geyser.session.SessionManager;
|
import org.geysermc.geyser.session.SessionManager;
|
||||||
import org.geysermc.geyser.session.auth.AuthType;
|
import org.geysermc.geyser.session.auth.AuthType;
|
||||||
import org.geysermc.geyser.skin.FloodgateSkinUploader;
|
import org.geysermc.geyser.skin.FloodgateSkinUploader;
|
||||||
@ -125,6 +126,8 @@ public class GeyserImpl implements GeyserApi {
|
|||||||
|
|
||||||
private Metrics metrics;
|
private Metrics metrics;
|
||||||
|
|
||||||
|
private PendingMicrosoftAuthentication pendingMicrosoftAuthentication;
|
||||||
|
|
||||||
private static GeyserImpl instance;
|
private static GeyserImpl instance;
|
||||||
|
|
||||||
private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) {
|
private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) {
|
||||||
@ -268,6 +271,8 @@ public class GeyserImpl implements GeyserApi {
|
|||||||
logger.debug("Not getting git properties for the news handler as we are in a development environment.");
|
logger.debug("Not getting git properties for the news handler as we are in a development environment.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pendingMicrosoftAuthentication = new PendingMicrosoftAuthentication(config.getPendingAuthenticationTimeout());
|
||||||
|
|
||||||
this.newsHandler = new NewsHandler(branch, buildNumber);
|
this.newsHandler = new NewsHandler(branch, buildNumber);
|
||||||
|
|
||||||
CooldownUtils.setDefaultShowCooldown(config.getShowCooldown());
|
CooldownUtils.setDefaultShowCooldown(config.getShowCooldown());
|
||||||
|
@ -100,6 +100,8 @@ public interface GeyserConfiguration {
|
|||||||
|
|
||||||
IMetricsInfo getMetrics();
|
IMetricsInfo getMetrics();
|
||||||
|
|
||||||
|
int getPendingAuthenticationTimeout();
|
||||||
|
|
||||||
interface IBedrockConfiguration {
|
interface IBedrockConfiguration {
|
||||||
|
|
||||||
String getAddress();
|
String getAddress();
|
||||||
|
@ -141,6 +141,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
|||||||
|
|
||||||
private MetricsInfo metrics = new MetricsInfo();
|
private MetricsInfo metrics = new MetricsInfo();
|
||||||
|
|
||||||
|
@JsonProperty("pending-authentication-timeout")
|
||||||
|
private int pendingAuthenticationTimeout = 120;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public static class BedrockConfiguration implements IBedrockConfiguration {
|
public static class BedrockConfiguration implements IBedrockConfiguration {
|
||||||
|
@ -32,6 +32,7 @@ import com.nukkitx.protocol.bedrock.data.ResourcePackType;
|
|||||||
import com.nukkitx.protocol.bedrock.packet.*;
|
import com.nukkitx.protocol.bedrock.packet.*;
|
||||||
import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
|
import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
|
import org.geysermc.geyser.session.PendingMicrosoftAuthentication;
|
||||||
import org.geysermc.geyser.session.auth.AuthType;
|
import org.geysermc.geyser.session.auth.AuthType;
|
||||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
@ -199,6 +200,12 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getTask(session.getAuthData().xuid());
|
||||||
|
if (task != null) {
|
||||||
|
if (task.getAuthentication().isDone() && session.onMicrosoftLoginComplete(task)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
package org.geysermc.geyser.session;
|
package org.geysermc.geyser.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.AuthenticationService;
|
||||||
@ -119,7 +118,6 @@ import java.net.InetSocketAddress;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.CompletionException;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@ -712,65 +710,57 @@ public class GeyserSession implements GeyserConnection, CommandSender {
|
|||||||
packet.setTime(16000);
|
packet.setTime(16000);
|
||||||
sendUpstreamPacket(packet);
|
sendUpstreamPacket(packet);
|
||||||
|
|
||||||
// new thread so clients don't timeout
|
final PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getOrCreateTask(
|
||||||
MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID);
|
getAuthData().xuid()
|
||||||
|
);
|
||||||
|
task.setOnline(true);
|
||||||
|
task.resetTimer();
|
||||||
|
|
||||||
// Use a future to prevent timeouts as all the authentication is handled sync
|
if (task.getAuthentication().isDone()) {
|
||||||
// This will be changed with the new protocol library.
|
onMicrosoftLoginComplete(task);
|
||||||
CompletableFuture.supplyAsync(() -> {
|
} else {
|
||||||
try {
|
task.getCode().whenComplete((response, ex) -> {
|
||||||
return msaAuthenticationService.getAuthCode();
|
boolean connected = !closed;
|
||||||
} catch (RequestException e) {
|
if (ex != null) {
|
||||||
throw new CompletionException(e);
|
if (connected) {
|
||||||
}
|
geyser.getLogger().error("Failed to get Microsoft auth code", ex);
|
||||||
}).whenComplete((response, ex) -> {
|
disconnect(ex.toString());
|
||||||
if (ex != null) {
|
}
|
||||||
ex.printStackTrace();
|
task.cleanup(); // error getting auth code -> clean up immediately
|
||||||
disconnect(ex.toString());
|
} else if (connected) {
|
||||||
return;
|
LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response);
|
||||||
}
|
task.getAuthentication().whenComplete((r, $) -> onMicrosoftLoginComplete(task));
|
||||||
LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response);
|
}
|
||||||
attemptCodeAuthentication(msaAuthenticationService);
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public boolean onMicrosoftLoginComplete(PendingMicrosoftAuthentication.AuthenticationTask task) {
|
||||||
* Poll every second to see if the user has successfully signed in
|
if (closed) {
|
||||||
*/
|
return false;
|
||||||
private void attemptCodeAuthentication(MsaAuthenticationService msaAuthenticationService) {
|
|
||||||
if (loggedIn || closed) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
CompletableFuture.supplyAsync(() -> {
|
task.cleanup(); // player is online -> remove pending authentication immediately
|
||||||
try {
|
Throwable ex = task.getLoginException();
|
||||||
msaAuthenticationService.login();
|
if (ex != null) {
|
||||||
GameProfile profile = msaAuthenticationService.getSelectedProfile();
|
geyser.getLogger().error("Failed to log in with Microsoft code!", ex);
|
||||||
if (profile == null) {
|
disconnect(ex.toString());
|
||||||
// Java account is offline
|
} else {
|
||||||
disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
|
GameProfile selectedProfile = task.getMsaAuthenticationService().getSelectedProfile();
|
||||||
return null;
|
if (selectedProfile == null) {
|
||||||
}
|
disconnect(GeyserLocale.getPlayerLocaleString(
|
||||||
|
"geyser.network.remote.invalid_account",
|
||||||
return new MinecraftProtocol(profile, msaAuthenticationService.getAccessToken());
|
clientData.getLanguageCode()
|
||||||
} catch (RequestException e) {
|
));
|
||||||
throw new CompletionException(e);
|
} else {
|
||||||
}
|
this.protocol = new MinecraftProtocol(
|
||||||
}).whenComplete((response, ex) -> {
|
selectedProfile,
|
||||||
if (ex != null) {
|
task.getMsaAuthenticationService().getAccessToken()
|
||||||
if (!(ex instanceof CompletionException completionException) || !(completionException.getCause() instanceof AuthPendingException)) {
|
);
|
||||||
geyser.getLogger().error("Failed to log in with Microsoft code!", ex);
|
|
||||||
disconnect(ex.toString());
|
|
||||||
} else {
|
|
||||||
// Wait one second before trying again
|
|
||||||
geyser.getScheduledThread().schedule(() -> attemptCodeAuthentication(msaAuthenticationService), 1, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!closed) {
|
|
||||||
this.protocol = response;
|
|
||||||
connectDownstream();
|
connectDownstream();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -970,6 +960,12 @@ public class GeyserSession implements GeyserConnection, CommandSender {
|
|||||||
geyser.getSessionManager().removeSession(this);
|
geyser.getSessionManager().removeSession(this);
|
||||||
upstream.disconnect(reason);
|
upstream.disconnect(reason);
|
||||||
}
|
}
|
||||||
|
if (authData != null) {
|
||||||
|
PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getTask(authData.xuid());
|
||||||
|
if (task != null) {
|
||||||
|
task.setOnline(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tickThread != null) {
|
if (tickThread != null) {
|
||||||
|
@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 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.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.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
|
import org.geysermc.geyser.GeyserLogger;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pending Microsoft authentication task cache.
|
||||||
|
* It permits user to exit the server while they authorize Geyser to access their Microsoft account.
|
||||||
|
*/
|
||||||
|
public class PendingMicrosoftAuthentication {
|
||||||
|
private final LoadingCache<String, AuthenticationTask> authentications;
|
||||||
|
|
||||||
|
public PendingMicrosoftAuthentication(int timeoutSeconds) {
|
||||||
|
this.authentications = CacheBuilder.newBuilder()
|
||||||
|
.build(new CacheLoader<>() {
|
||||||
|
@Override
|
||||||
|
public AuthenticationTask load(@NonNull String userKey) {
|
||||||
|
return new AuthenticationTask(userKey, timeoutSeconds * 1000L);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationTask getTask(@Nonnull String userKey) {
|
||||||
|
return authentications.getIfPresent(userKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows(ExecutionException.class)
|
||||||
|
public AuthenticationTask getOrCreateTask(@Nonnull String userKey) {
|
||||||
|
return authentications.get(userKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AuthenticationTask {
|
||||||
|
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 long timeoutMs;
|
||||||
|
|
||||||
|
private long remainingTimeMs;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private boolean online = true;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final CompletableFuture<MsaAuthenticationService.MsCodeResponse> code;
|
||||||
|
@Getter
|
||||||
|
private final CompletableFuture<MsaAuthenticationService> authentication;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private volatile Throwable loginException;
|
||||||
|
|
||||||
|
private AuthenticationTask(String userKey, long timeoutMs) {
|
||||||
|
this.userKey = userKey;
|
||||||
|
this.timeoutMs = timeoutMs;
|
||||||
|
this.remainingTimeMs = timeoutMs;
|
||||||
|
|
||||||
|
// Request the code
|
||||||
|
this.code = CompletableFuture.supplyAsync(this::tryGetCode);
|
||||||
|
this.authentication = new CompletableFuture<>();
|
||||||
|
// Once the code is received, continuously try to request the access token, profile, etc
|
||||||
|
this.code.thenRun(() -> performLoginAttempt(System.currentTimeMillis()));
|
||||||
|
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() {
|
||||||
|
this.remainingTimeMs = this.timeoutMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cleanup() {
|
||||||
|
GeyserLogger logger = GeyserImpl.getInstance().getLogger();
|
||||||
|
if (logger.isDebug()) {
|
||||||
|
logger.debug("Cleaning up authentication task for " + userKey);
|
||||||
|
}
|
||||||
|
authentications.invalidate(userKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MsaAuthenticationService.MsCodeResponse tryGetCode() throws CompletionException {
|
||||||
|
try {
|
||||||
|
return msaAuthenticationService.getAuthCode();
|
||||||
|
} catch (RequestException e) {
|
||||||
|
throw new CompletionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performLoginAttempt(long lastAttempt) {
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
msaAuthenticationService.login();
|
||||||
|
} catch (AuthPendingException e) {
|
||||||
|
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) {
|
||||||
|
authentication.completeExceptionally(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// login successful
|
||||||
|
authentication.complete(msaAuthenticationService);
|
||||||
|
}, DELAYED_BY_ONE_SECOND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + "{userKey='" + userKey + "'}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see PendingMicrosoftAuthentication
|
||||||
|
*/
|
||||||
|
public static class TaskTimeoutException extends Exception {
|
||||||
|
TaskTimeoutException() {
|
||||||
|
super("It took too long to authorize Geyser to access your Microsoft account. " +
|
||||||
|
"Please request new code and try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -48,6 +48,7 @@ import org.geysermc.cumulus.SimpleForm;
|
|||||||
import org.geysermc.cumulus.response.CustomFormResponse;
|
import org.geysermc.cumulus.response.CustomFormResponse;
|
||||||
import org.geysermc.cumulus.response.ModalFormResponse;
|
import org.geysermc.cumulus.response.ModalFormResponse;
|
||||||
import org.geysermc.cumulus.response.SimpleFormResponse;
|
import org.geysermc.cumulus.response.SimpleFormResponse;
|
||||||
|
import org.geysermc.geyser.text.ChatColor;
|
||||||
import org.geysermc.geyser.text.GeyserLocale;
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
@ -312,10 +313,23 @@ 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, MsaAuthenticationService.MsCodeResponse msCode) {
|
||||||
|
StringBuilder message = new StringBuilder("%xbox.signin.website\n")
|
||||||
|
.append(ChatColor.AQUA)
|
||||||
|
.append("%xbox.signin.url")
|
||||||
|
.append(ChatColor.RESET)
|
||||||
|
.append("\n%xbox.signin.enterCode\n")
|
||||||
|
.append(ChatColor.GREEN)
|
||||||
|
.append(msCode.user_code);
|
||||||
|
int timeout = session.getGeyser().getConfig().getPendingAuthenticationTimeout();
|
||||||
|
if (timeout != 0) {
|
||||||
|
message.append("\n\n")
|
||||||
|
.append(ChatColor.RESET)
|
||||||
|
.append(GeyserLocale.getPlayerLocaleString("geyser.auth.login.timeout", session.getLocale(), String.valueOf(timeout)));
|
||||||
|
}
|
||||||
session.sendForm(
|
session.sendForm(
|
||||||
ModalForm.builder()
|
ModalForm.builder()
|
||||||
.title("%xbox.signin")
|
.title("%xbox.signin")
|
||||||
.content("%xbox.signin.website\n%xbox.signin.url\n%xbox.signin.enterCode\n" + msCode.user_code)
|
.content(message.toString())
|
||||||
.button1("%gui.done")
|
.button1("%gui.done")
|
||||||
.button2("%menu.disconnect")
|
.button2("%menu.disconnect")
|
||||||
.responseHandler((form, responseData) -> {
|
.responseHandler((form, responseData) -> {
|
||||||
|
@ -81,6 +81,10 @@ floodgate-key-file: key.pem
|
|||||||
# password: "this isn't really my password"
|
# password: "this isn't really my password"
|
||||||
# microsoft-account: false
|
# microsoft-account: false
|
||||||
|
|
||||||
|
# Specify how many seconds to wait while user authorizes Geyser to access their Microsoft account.
|
||||||
|
# User is allowed to disconnect from the server during this period.
|
||||||
|
pending-authentication-timeout: 120
|
||||||
|
|
||||||
# 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.
|
||||||
command-suggestions: true
|
command-suggestions: true
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit bdee0d0f3f8a1271cd001f0bd0d672d0010be1db
|
Subproject commit 5db9d29ece0b3d810ae42f6bdc9eeefd76e3d99d
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren