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..a3d911b1f 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 @@ -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())) { + 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; } - 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; + // 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..0cc61a668 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()) { @@ -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..4fea4d49b 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);