From bd51249364716dbc232b9b2b97b943e1b7fee492 Mon Sep 17 00:00:00 2001 From: lokspel <208148594+lokspel@users.noreply.github.com> Date: Fri, 3 Jul 2026 23:39:10 +0400 Subject: [PATCH] Fix player freeze after /freemium with proxy auto-login --- .../authme/process/join/AsynchronousJoin.java | 34 +++++++++-------- .../process/join/AsynchronousJoinTest.java | 19 ++++++++++ .../listener/PaperDialogFlowListener.java | 38 +++++++++++++++++-- .../listener/PaperDialogFlowListenerTest.java | 4 +- 4 files changed, 75 insertions(+), 20 deletions(-) diff --git a/authme-core/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java b/authme-core/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java index e64493e72..17f858bda 100644 --- a/authme-core/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java +++ b/authme-core/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java @@ -60,7 +60,7 @@ * Asynchronous process for when a player joins. */ public class AsynchronousJoin implements AsynchronousProcess { - + private final ConsoleLogger logger = ConsoleLoggerFactory.get(AsynchronousJoin.class); @Inject @@ -214,23 +214,25 @@ public void processJoin(Player player) { } else { ProxySessionManager.ProxyLoginRequest proxyLoginRequest = proxySessionManager.consumeLoginRequest(name); if (proxyLoginRequest != null) { - if (!proxyLoginRequestValidator.validate(player, proxyLoginRequest.verifiedPremiumUuid())) { - return; - } - if (playerCache.isAuthenticated(name)) { + if (proxyLoginRequestValidator.validate(player, proxyLoginRequest.verifiedPremiumUuid())) { + if (playerCache.isAuthenticated(name)) { + return; + } + service.send(player, MessageKey.SESSION_RECONNECTION); + // Run commands + bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(player, + () -> commandManager.runCommandsOnSessionLogin(player)); + // Use forceLoginFromProxy (quiet=true, no BungeeCord redirect) so that if + // BungeeReceiver.performLogin() concurrently already completed the login, this + // call is a no-op rather than sending an "already logged in" error. + bukkitService.runTaskOptionallyAsync(() -> asynchronousLogin.forceLoginFromProxy(player)); + logger.info("The user " + player.getName() + " has been automatically logged in, " + + "as present in autologin queue."); return; } - service.send(player, MessageKey.SESSION_RECONNECTION); - // Run commands - bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(player, - () -> commandManager.runCommandsOnSessionLogin(player)); - // Use forceLoginFromProxy (quiet=true, no BungeeCord redirect) so that if - // BungeeReceiver.performLogin() concurrently already completed the login, this - // call is a no-op rather than sending an "already logged in" error. - bukkitService.runTaskOptionallyAsync(() -> asynchronousLogin.forceLoginFromProxy(player)); - logger.info("The user " + player.getName() + " has been automatically logged in, " - + "as present in autologin queue."); - return; + // Validation failed (e.g. premium UUID mismatch after /freemium). + // Fall through to session check / limbo flow below to avoid leaving the + // player stuck: not authenticated, no dialog, no movement allowed. } } if (sessionService.canResumeSession(player)) { diff --git a/authme-core/src/test/java/fr/xephi/authme/process/join/AsynchronousJoinTest.java b/authme-core/src/test/java/fr/xephi/authme/process/join/AsynchronousJoinTest.java index 7c7cddb69..c18ba9b54 100644 --- a/authme-core/src/test/java/fr/xephi/authme/process/join/AsynchronousJoinTest.java +++ b/authme-core/src/test/java/fr/xephi/authme/process/join/AsynchronousJoinTest.java @@ -326,6 +326,25 @@ public void shouldFinalizePendingPremiumInJoinFromQueuedProxyRequest() { verify(bungeeSender, never()).sendPremiumUnset("Bobby"); } + @Test + public void shouldFallThroughToSessionOrLimboWhenProxyValidationFails() { + // given — proxy sends perform.login with a premium UUID, but the player just ran /freemium, + // so the proxy login request cannot be validated. Must fall through to session/limbo flow + // instead of leaving the player stuck with no authentication and no dialog. + Player player = mockPlayer("Bobby"); + setUpRegisteredJoin(player); + given(proxySessionManager.consumeLoginRequest("bobby")) + .willReturn(new ProxySessionManager.ProxyLoginRequest("bobby", UUID.randomUUID())); + given(proxyLoginRequestValidator.validate(eq(player), any())).willReturn(false); + + // when + asynchronousJoin.processJoin(player); + + // then — falls through to session check; session is not valid so limbo + dialog + verify(limboService).createLimboPlayer(player, true); + verify(asynchronousLogin, never()).forceLoginFromProxy(player); + } + @Test public void shouldForceLoginPlayerApprovedViaPreJoinDialog() { // given diff --git a/authme-paper-common/src/main/java/fr/xephi/authme/listener/PaperDialogFlowListener.java b/authme-paper-common/src/main/java/fr/xephi/authme/listener/PaperDialogFlowListener.java index 0be188a0c..dd8b095e7 100644 --- a/authme-paper-common/src/main/java/fr/xephi/authme/listener/PaperDialogFlowListener.java +++ b/authme-paper-common/src/main/java/fr/xephi/authme/listener/PaperDialogFlowListener.java @@ -16,6 +16,7 @@ import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.DialogWindowService; +import fr.xephi.authme.service.PendingPremiumCache; import fr.xephi.authme.service.PreJoinDialogService; import fr.xephi.authme.service.PremiumLoginVerifier; import fr.xephi.authme.service.SessionService; @@ -75,6 +76,9 @@ public class PaperDialogFlowListener implements Listener { @Inject private PreJoinDialogService preJoinDialogService; + @Inject + private PendingPremiumCache pendingPremiumCache; + @Inject private DialogWindowService dialogWindowService; @@ -207,7 +211,11 @@ private void handleBlockingLoginDialog(PlayerConfigurationConnection connection, // phase between the shouldSkipDialogs() check and now: if a proxy session has been queued, // force-login instead of showing the dialog. if (proxySessionManager.shouldResumeSession(normalizedName)) { - preJoinDialogService.approvePreJoinForceLogin(normalizedName); + ProxySessionManager.ProxyLoginRequest req = proxySessionManager.getLoginRequest(normalizedName); + if (req != null && (req.verifiedPremiumUuid() == null + || isProxyPremiumRequestValid(normalizedName, req))) { + preJoinDialogService.approvePreJoinForceLogin(normalizedName); + } } if (!loginResponse.isDone()) { @@ -246,7 +254,7 @@ private void processPreJoinLogin(UUID playerId, String playerName, DialogRespons } private void processPreJoinLoginRecovery(UUID playerId, String playerName, - PlayerConfigurationConnection connection) { + PlayerConfigurationConnection connection) { DialogWindowSpec recoverySpec = dialogWindowService.createPreJoinRecoveryDialog(playerName); connection.getAudience().showDialog(PaperDialogHelper.createPreJoinRecoveryDialog(recoverySpec)); } @@ -410,14 +418,38 @@ private boolean shouldSkipPreJoinDialogForPremium(PlayerAuth auth, String player return true; } + private boolean isProxyPremiumRequestValid(String normalizedName, ProxySessionManager.ProxyLoginRequest request) { + UUID verifiedUuid = request.verifiedPremiumUuid(); + if (verifiedUuid == null) { + return true; + } + PlayerAuth auth = dataSource.getAuth(normalizedName); + if (auth == null) { + return false; + } + if (auth.isPremium()) { + return verifiedUuid.equals(auth.getPremiumUuid()); + } + UUID pendingUuid = pendingPremiumCache.getPendingUuid(normalizedName); + return pendingUuid != null && verifiedUuid.equals(pendingUuid); + } + // MC 1.21.6 (protocol 771) introduced the dialog / custom-click packets required for pre-join dialogs private static final int DIALOG_MIN_PROTOCOL = 771; private boolean shouldSkipDialogs(String normalizedName, PlayerConfigurationConnection connection) { - if (playerCache.isAuthenticated(normalizedName) || proxySessionManager.shouldResumeSession(normalizedName)) { + if (playerCache.isAuthenticated(normalizedName)) { return true; } + if (proxySessionManager.shouldResumeSession(normalizedName)) { + ProxySessionManager.ProxyLoginRequest request = proxySessionManager.getLoginRequest(normalizedName); + if (request != null && (request.verifiedPremiumUuid() == null + || isProxyPremiumRequestValid(normalizedName, request))) { + return true; + } + } + InetSocketAddress clientAddress = connection.getClientAddress(); String ipAddress = clientAddress == null ? null : clientAddress.getAddress().getHostAddress(); if (sessionService.hasValidSession(normalizedName, ipAddress)) { diff --git a/authme-paper-common/src/test/java/fr/xephi/authme/listener/PaperDialogFlowListenerTest.java b/authme-paper-common/src/test/java/fr/xephi/authme/listener/PaperDialogFlowListenerTest.java index bb79ba4fd..e5335e68a 100644 --- a/authme-paper-common/src/test/java/fr/xephi/authme/listener/PaperDialogFlowListenerTest.java +++ b/authme-paper-common/src/test/java/fr/xephi/authme/listener/PaperDialogFlowListenerTest.java @@ -361,6 +361,8 @@ public void shouldSkipPreJoinDialogsForProxyAutoLogin() throws Exception { given(commonService.getProperty(RestrictionSettings.UNRESTRICTED_NAMES)).willReturn(Set.of()); given(playerCache.isAuthenticated("bobby")).willReturn(false); given(proxySessionManager.shouldResumeSession("bobby")).willReturn(true); + given(proxySessionManager.getLoginRequest("bobby")) + .willReturn(new ProxySessionManager.ProxyLoginRequest("bobby", null)); UUID playerId = UUID.randomUUID(); PlayerProfile profile = mock(PlayerProfile.class); @@ -539,7 +541,7 @@ public void shouldNotSkipPreJoinDialogForImpostorWithMismatchedMojangUuidInProxy } private static boolean invokeShouldSkipPreJoinDialogForPremium(PaperDialogFlowListener listener, - PlayerAuth auth, String playerName, UUID playerId) throws ReflectiveOperationException { + PlayerAuth auth, String playerName, UUID playerId) throws ReflectiveOperationException { var method = PaperDialogFlowListener.class .getDeclaredMethod("shouldSkipPreJoinDialogForPremium", PlayerAuth.class, String.class, UUID.class); method.setAccessible(true);