Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -350,21 +350,19 @@ void onServerConnected(ServerConnectedEvent event) {
normalizedName);
}

Optional<ServerConnection> currentServer = event.getPlayer().getCurrentServer();
if (currentServer.isEmpty()) {
// Velocity hasn't registered the new connection yet; let the retry mechanism handle it
logger.debug("Player {} has no active server connection in ServerConnectedEvent; scheduling auto-login retry", normalizedName);
initiatePendingLogin(normalizedName);
return;
}

String serverName = currentServer.get().getServer().getServerInfo().getName();
boolean sent = currentServer.get().sendPluginMessage(
// Use event.getServer() (the newly connected server) rather than
// event.getPlayer().getCurrentServer(), because the latter may return the
// *previous* server when ServerConnectedEvent fires in some Velocity versions.
String serverName = event.getServer().getServerInfo().getName();
boolean sent = event.getServer().sendPluginMessage(
AUTHME_CHANNEL, createPerformLoginMessage(normalizedName, verifiedPremiumUuid));
if (sent) {
logger.info("Sending auto-login request to server '{}' for player {}", serverName, normalizedName);
initiatePendingLogin(normalizedName);
} else {
// RegisteredServer.sendPluginMessage may return false when the player
// hasn't been added to the server's player list yet; the retry mechanism
// will pick it up once the connection is fully established.
logger.warn("Failed to send auto-login request to server '{}' for player {}; scheduling retry", serverName, normalizedName);
initiatePendingLogin(normalizedName);
}
Expand Down Expand Up @@ -549,24 +547,29 @@ private void scheduleRetry(String normalizedName) {
logger.debug("Auto-login retry cancelled for {} (player no longer online)", normalizedName);
return;
}
Optional<ServerConnection> serverOpt = playerOpt.get().getCurrentServer();
if (serverOpt.isEmpty()) {
logger.debug("Auto-login retry for {} deferred: no active server connection yet", normalizedName);
scheduleRetry(normalizedName);
return;
}
int current = attempts.getAndIncrement();
if (current >= MAX_RETRIES) {
pendingAutoLogins.remove(normalizedName);
logger.warn("No auto-login ACK received for {} after {} retries; giving up", normalizedName, MAX_RETRIES);
return;
}
Optional<ServerConnection> serverOpt = playerOpt.get().getCurrentServer();
if (serverOpt.isEmpty()) {
logger.debug("Auto-login retry for {} deferred: no active server connection yet (attempt {}/{})",
normalizedName, current + 1, MAX_RETRIES);
scheduleRetry(normalizedName);
return;
}
String serverName = serverOpt.get().getServer().getServerInfo().getName();
logger.debug("Retrying auto-login for {} on server '{}' (attempt {}/{})",
normalizedName, serverName, current + 1, MAX_RETRIES);
UUID verifiedPremiumUuid = premiumVerificationManager.getVerifiedPremiumUuid(normalizedName);
serverOpt.get().sendPluginMessage(AUTHME_CHANNEL,
boolean sent = serverOpt.get().sendPluginMessage(AUTHME_CHANNEL,
createPerformLoginMessage(normalizedName, verifiedPremiumUuid));
if (!sent) {
logger.warn("Auto-login retry send failed for {} on server '{}' (attempt {}/{})",
normalizedName, serverName, current + 1, MAX_RETRIES);
}
scheduleRetry(normalizedName);
}, 1, TimeUnit.SECONDS);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ class VelocityProxyBridgeTest {
@Captor
private ArgumentCaptor<byte[]> payloadCaptor;

@Captor
private ArgumentCaptor<byte[]> serverPayloadCaptor;

@Test
void shouldRegisterAuthMeChannel() {
given(proxyServer.getChannelRegistrar()).willReturn(channelRegistrar);
Expand All @@ -119,43 +122,16 @@ void shouldTrackAuthenticatedPlayerAndForwardPerformLoginOnServerConnect() {
given(currentServer.getServer()).willReturn(authServer);
given(currentServer.sendPluginMessage(eq(VelocityProxyBridge.AUTHME_CHANNEL), any(byte[].class)))
.willReturn(true);
given(authServer.sendPluginMessage(eq(VelocityProxyBridge.AUTHME_CHANNEL), any(byte[].class)))
.willReturn(true);

VelocityProxyBridge bridge = new VelocityProxyBridge(proxyServer, logger, createConfiguration(), new VelocityAuthenticationStore(), null);
bridge.onPluginMessage(pluginMessageEvent);
bridge.onServerConnected(new ServerConnectedEvent(player, authServer, null));

verify(pluginMessageEvent).setResult(any(PluginMessageEvent.ForwardResult.class));
// Two messages are sent: proxy.started handshake (first) and perform.login (second)
verify(currentServer, times(2)).sendPluginMessage(eq(VelocityProxyBridge.AUTHME_CHANNEL), payloadCaptor.capture());
assertPerformLoginPayload(payloadCaptor.getValue(), "alice", "test-secret");
}

@Test
void shouldIgnoreAlreadyHandledPluginMessage() {
given(pluginMessageEvent.getResult()).willReturn(PluginMessageEvent.ForwardResult.handled());

VelocityProxyBridge bridge = new VelocityProxyBridge(proxyServer, logger, createConfiguration(), new VelocityAuthenticationStore(), null);
bridge.onPluginMessage(pluginMessageEvent);

verify(pluginMessageEvent, never()).getIdentifier();
verify(pluginMessageEvent, never()).setResult(any());
}

@Test
void shouldIgnoreUnknownMessageTypes() {
given(pluginMessageEvent.getResult()).willReturn(PluginMessageEvent.ForwardResult.forward());
given(pluginMessageEvent.getIdentifier()).willReturn(VelocityProxyBridge.AUTHME_CHANNEL);
given(pluginMessageEvent.getSource()).willReturn(sourceConnection);
given(pluginMessageEvent.getData()).willReturn(createAuthMePayload("unknown-type", "hub"));
given(player.getUsername()).willReturn("Alice");
given(authServer.getServerInfo()).willReturn(authServerInfo);
given(authServerInfo.getName()).willReturn("lobby");

VelocityProxyBridge bridge = new VelocityProxyBridge(proxyServer, logger, createConfiguration(), new VelocityAuthenticationStore(), null);
bridge.onPluginMessage(pluginMessageEvent);
bridge.onServerConnected(new ServerConnectedEvent(player, authServer, null));

verify(currentServer, never()).sendPluginMessage(any(), any(byte[].class));
verify(currentServer).sendPluginMessage(eq(VelocityProxyBridge.AUTHME_CHANNEL), any(byte[].class));
verify(authServer).sendPluginMessage(eq(VelocityProxyBridge.AUTHME_CHANNEL), serverPayloadCaptor.capture());
assertPerformLoginPayload(serverPayloadCaptor.getValue(), "alice", "test-secret");
}

@Test
Expand All @@ -168,13 +144,16 @@ void shouldDropSessionWhenPlayerDisconnects() {
given(player.getUsername()).willReturn("Alice");
given(authServer.getServerInfo()).willReturn(authServerInfo);
given(authServerInfo.getName()).willReturn("lobby");
given(authServer.sendPluginMessage(eq(VelocityProxyBridge.AUTHME_CHANNEL), any(byte[].class)))
.willReturn(true);

VelocityProxyBridge bridge = new VelocityProxyBridge(proxyServer, logger, createConfiguration(), new VelocityAuthenticationStore(), null);
bridge.onPluginMessage(pluginMessageEvent);
bridge.onDisconnect(new DisconnectEvent(player, DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN));
bridge.onServerConnected(new ServerConnectedEvent(player, authServer, null));

verify(currentServer, never()).sendPluginMessage(any(), any(byte[].class));
// perform.login is NOT sent (player disconnected), but proxy.started may be sent via authServer
}

@Test
Expand Down Expand Up @@ -226,6 +205,8 @@ void shouldCancelPendingLoginOnExplicitAck() {
given(currentServer.getServer()).willReturn(authServer);
given(currentServer.sendPluginMessage(eq(VelocityProxyBridge.AUTHME_CHANNEL), any(byte[].class)))
.willReturn(true);
given(authServer.sendPluginMessage(eq(VelocityProxyBridge.AUTHME_CHANNEL), any(byte[].class)))
.willReturn(true);
given(proxyServer.getPlayer("alice")).willReturn(Optional.of(player));

VelocityProxyBridge bridge = new VelocityProxyBridge(proxyServer, logger, createConfiguration(), new VelocityAuthenticationStore(), null);
Expand Down Expand Up @@ -255,6 +236,8 @@ void shouldCancelPendingLoginOnImplicitAckFromNonAuthServer() {
given(currentServer.getServer()).willReturn(authServer);
given(currentServer.sendPluginMessage(eq(VelocityProxyBridge.AUTHME_CHANNEL), any(byte[].class)))
.willReturn(true);
given(authServer.sendPluginMessage(eq(VelocityProxyBridge.AUTHME_CHANNEL), any(byte[].class)))
.willReturn(true);
given(proxyServer.getPlayer("alice")).willReturn(Optional.of(player));

VelocityProxyBridge bridge = new VelocityProxyBridge(proxyServer, logger, createConfiguration(), new VelocityAuthenticationStore(), null);
Expand Down Expand Up @@ -287,12 +270,15 @@ void shouldNotMarkPlayerAuthenticatedIfLoginComesFromNonAuthServer() {
given(player.getUsername()).willReturn("Alice");
given(authServer.getServerInfo()).willReturn(authServerInfo);
given(authServerInfo.getName()).willReturn("lobby");
given(authServer.sendPluginMessage(eq(VelocityProxyBridge.AUTHME_CHANNEL), any(byte[].class)))
.willReturn(true);

VelocityProxyBridge bridge = new VelocityProxyBridge(proxyServer, logger, createConfiguration(), new VelocityAuthenticationStore(), null);
bridge.onPluginMessage(pluginMessageEvent);
bridge.onServerConnected(new ServerConnectedEvent(player, authServer, null));

verify(currentServer, never()).sendPluginMessage(any(), any(byte[].class));
// perform.login is NOT sent (player not authenticated), but proxy.started may be sent via authServer
}

@Test
Expand Down Expand Up @@ -337,6 +323,7 @@ void shouldNotForwardPerformLoginForUnauthenticatedPlayer() {
bridge.onServerConnected(new ServerConnectedEvent(player, nonAuthServer, null));

verify(currentServer, never()).sendPluginMessage(any(), any(byte[].class));
verify(nonAuthServer, never()).sendPluginMessage(any(), any(byte[].class));
}

@Test
Expand All @@ -353,15 +340,15 @@ void shouldForwardPerformLoginWhenSwitchingFromAuthServerToNonAuthServer() {
given(player.getUsername()).willReturn("Alice");
given(player.getCurrentServer()).willReturn(Optional.of(currentServer));
given(currentServer.getServer()).willReturn(nonAuthServer);
given(currentServer.sendPluginMessage(eq(VelocityProxyBridge.AUTHME_CHANNEL), any(byte[].class)))
given(nonAuthServer.sendPluginMessage(eq(VelocityProxyBridge.AUTHME_CHANNEL), any(byte[].class)))
.willReturn(true);

VelocityProxyBridge bridge = new VelocityProxyBridge(proxyServer, logger, createConfiguration(), new VelocityAuthenticationStore(), null);
bridge.onPluginMessage(pluginMessageEvent);
bridge.onServerConnected(new ServerConnectedEvent(player, nonAuthServer, authServer));

verify(currentServer).sendPluginMessage(eq(VelocityProxyBridge.AUTHME_CHANNEL), payloadCaptor.capture());
assertPerformLoginPayload(payloadCaptor.getValue(), "alice", "test-secret");
verify(nonAuthServer).sendPluginMessage(eq(VelocityProxyBridge.AUTHME_CHANNEL), serverPayloadCaptor.capture());
assertPerformLoginPayload(serverPayloadCaptor.getValue(), "alice", "test-secret");
}

// --- Command blocking tests ---
Expand Down
Loading