Skip to content

Commit b9622c4

Browse files
committed
fix: Premium cache on proxy side
1 parent 328175e commit b9622c4

10 files changed

Lines changed: 191 additions & 77 deletions

File tree

authme-bungee/src/main/java/fr/xephi/authme/bungee/AuthMeBungeePlugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public final class AuthMeBungeePlugin extends AbstractAuthMeBungeePlugin {
99
public void onEnable() {
1010
configManager = new BungeeConfigManager(getDataFolder().toPath());
1111
BungeeAuthenticationStore authenticationStore = new BungeeAuthenticationStore();
12-
proxyBridge = new BungeeProxyBridge(getProxy(), getLogger(), configManager.getConfiguration(), authenticationStore);
12+
proxyBridge = new BungeeProxyBridge(getProxy(), getLogger(), configManager.getConfiguration(), authenticationStore, getDataFolder().toPath());
1313

1414
getProxy().getPluginManager().registerListener(this, proxyBridge);
1515
getProxy().getPluginManager().registerCommand(this, new BungeeReloadCommand(configManager, proxyBridge));

authme-bungee/src/main/java/fr/xephi/authme/bungee/BungeeProxyBridge.java

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,16 @@
2121
import net.md_5.bungee.event.EventHandler;
2222
import net.md_5.bungee.event.EventPriority;
2323

24+
import java.io.BufferedReader;
25+
import java.io.BufferedWriter;
26+
import java.io.IOException;
27+
import java.nio.charset.StandardCharsets;
28+
import java.nio.file.Files;
29+
import java.nio.file.NoSuchFileException;
30+
import java.nio.file.Path;
31+
import java.nio.file.StandardCopyOption;
2432
import java.util.ArrayList;
33+
import java.util.HashSet;
2534
import java.util.List;
2635
import java.util.Locale;
2736
import java.util.Map;
@@ -68,9 +77,10 @@ public final class BungeeProxyBridge implements Listener {
6877
t.setDaemon(true);
6978
return t;
7079
});
80+
private final Path cacheFile;
7181

7282
BungeeProxyBridge(ProxyServer proxyServer, Logger logger, BungeeProxyConfiguration configuration,
73-
BungeeAuthenticationStore authenticationStore) {
83+
BungeeAuthenticationStore authenticationStore, Path dataDirectory) {
7484
this.proxyServer = proxyServer;
7585
this.logger = logger;
7686
this.configuration = configuration;
@@ -81,6 +91,50 @@ public final class BungeeProxyBridge implements Listener {
8191
this::requiresPremiumVerification, this::isPendingPremiumVerification,
8292
this::clearPendingPremiumVerification,
8393
() -> this.configuration.keepOfflineUuidCompatibility());
94+
this.cacheFile = dataDirectory != null ? dataDirectory.resolve("premium_names.cache") : null;
95+
if (cacheFile != null) {
96+
loadPremiumNamesCache();
97+
}
98+
}
99+
100+
private void loadPremiumNamesCache() {
101+
Set<String> loaded = new HashSet<>();
102+
try (BufferedReader reader = Files.newBufferedReader(cacheFile, StandardCharsets.UTF_8)) {
103+
String line;
104+
while ((line = reader.readLine()) != null) {
105+
String name = line.trim();
106+
if (!name.isEmpty()) {
107+
loaded.add(name);
108+
}
109+
}
110+
Set<String> newSet = ConcurrentHashMap.newKeySet();
111+
newSet.addAll(loaded);
112+
premiumUsernames = newSet;
113+
logger.info("Loaded " + loaded.size() + " premium username(s) from cache");
114+
} catch (NoSuchFileException ignored) {
115+
// No cache yet — first startup
116+
} catch (IOException e) {
117+
logger.warning("Failed to load premium names cache: " + e.getMessage());
118+
}
119+
}
120+
121+
private void savePremiumNamesAsync() {
122+
if (cacheFile == null) {
123+
return;
124+
}
125+
Set<String> snapshot = new HashSet<>(premiumUsernames);
126+
retryScheduler.execute(() -> {
127+
Path tmp = cacheFile.resolveSibling(cacheFile.getFileName() + ".tmp");
128+
try (BufferedWriter writer = Files.newBufferedWriter(tmp, StandardCharsets.UTF_8)) {
129+
for (String name : snapshot) {
130+
writer.write(name);
131+
writer.newLine();
132+
}
133+
Files.move(tmp, cacheFile, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
134+
} catch (IOException e) {
135+
logger.warning("Failed to save premium names cache: " + e.getMessage());
136+
}
137+
});
84138
}
85139

86140
void reload(BungeeProxyConfiguration configuration) {
@@ -214,11 +268,13 @@ public void onPluginMessage(PluginMessageEvent event) {
214268
premiumUsernames.add(parsedMessage.playerName());
215269
pendingPremiumUsernames.remove(parsedMessage.playerName());
216270
logger.fine(() -> "Premium enabled for '" + parsedMessage.playerName() + "' (proxy cache updated)");
271+
savePremiumNamesAsync();
217272
} else if (PREMIUM_UNSET_MESSAGE.equals(parsedMessage.typeId())) {
218273
premiumUsernames.remove(parsedMessage.playerName());
219274
pendingPremiumUsernames.remove(parsedMessage.playerName());
220275
premiumVerificationManager.clearVerifiedPremium(parsedMessage.playerName());
221276
logger.fine(() -> "Premium disabled for '" + parsedMessage.playerName() + "' (proxy cache updated)");
277+
savePremiumNamesAsync();
222278
} else if (PREMIUM_PENDING_SET_MESSAGE.equals(parsedMessage.typeId())) {
223279
pendingPremiumUsernames.add(parsedMessage.playerName());
224280
premiumVerificationManager.clearVerifiedPremium(parsedMessage.playerName());
@@ -257,6 +313,7 @@ public void onPluginMessage(PluginMessageEvent event) {
257313
premiumUsernames = newPremiumSet;
258314
premiumListBuffer = new ArrayList<>();
259315
logger.info("Premium list received from backend: " + premiumUsernames.size() + " premium player(s)");
316+
savePremiumNamesAsync();
260317
}
261318
}
262319
}

authme-bungee/src/test/java/fr/xephi/authme/bungee/BungeeProxyBridgeTest.java

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ void shouldTrackAuthenticatedPlayerAndForwardPerformLoginOnServerSwitch() {
101101
given(currentServer.getInfo()).willReturn(authServerInfo);
102102
given(authServerInfo.getName()).willReturn("lobby");
103103

104-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
104+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
105105
bridge.onPluginMessage(pluginMessageEvent);
106106
bridge.onServerSwitch(serverSwitchEvent);
107107

@@ -122,7 +122,7 @@ void shouldIgnoreUnrelatedPluginMessages() {
122122
given(authServerInfo.getName()).willReturn("lobby");
123123
given(serverSwitchEvent.getFrom()).willReturn(authServerInfo);
124124

125-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
125+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
126126
bridge.onPluginMessage(pluginMessageEvent);
127127
bridge.onServerSwitch(serverSwitchEvent);
128128

@@ -143,7 +143,7 @@ void shouldDropSessionWhenPlayerDisconnects() {
143143
given(currentServer.getInfo()).willReturn(authServerInfo);
144144
given(authServerInfo.getName()).willReturn("lobby");
145145

146-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
146+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
147147
bridge.onPluginMessage(pluginMessageEvent);
148148
bridge.onPlayerDisconnect(playerDisconnectEvent);
149149
bridge.onServerSwitch(serverSwitchEvent);
@@ -164,7 +164,7 @@ void shouldRedirectPlayerOnLogoutWhenConfigured() {
164164
proxyServer, logger, new BungeeProxyConfiguration(
165165
Set.of("lobby"), false, true, Set.of("/login"), true, true,
166166
"Authentication required.", true, true, "limbo", "", "", false),
167-
new BungeeAuthenticationStore());
167+
new BungeeAuthenticationStore(), null);
168168
bridge.onPluginMessage(pluginMessageEvent);
169169

170170
verify(player).connect(nonAuthServerInfo);
@@ -176,7 +176,7 @@ void shouldCancelClientPluginMessageToPreventForwarding() {
176176
given(pluginMessageEvent.getTag()).willReturn(BungeeProxyBridge.AUTHME_CHANNEL);
177177
given(pluginMessageEvent.getSender()).willReturn(player);
178178

179-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
179+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
180180
bridge.onPluginMessage(pluginMessageEvent);
181181

182182
verify(pluginMessageEvent).setCancelled(true);
@@ -196,7 +196,7 @@ void shouldCancelPendingLoginOnExplicitAck() {
196196
given(currentServer.getInfo()).willReturn(authServerInfo);
197197
given(serverSwitchEvent.getFrom()).willReturn(null);
198198

199-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
199+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
200200
bridge.onPluginMessage(pluginMessageEvent);
201201
bridge.onServerSwitch(serverSwitchEvent);
202202

@@ -220,7 +220,7 @@ void shouldCancelPendingLoginOnImplicitAckFromNonAuthServer() {
220220
given(player.getServer()).willReturn(currentServer);
221221
given(currentServer.getInfo()).willReturn(authServerInfo);
222222

223-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
223+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
224224

225225
// Mark authenticated via auth server login
226226
given(pluginMessageEvent.getData()).willReturn(createAuthMePayload("login", "Alice"));
@@ -251,7 +251,7 @@ void shouldNotMarkPlayerAuthenticatedIfLoginComesFromNonAuthServer() {
251251
given(currentServer.getInfo()).willReturn(authServerInfo);
252252
given(authServerInfo.getName()).willReturn("lobby");
253253

254-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
254+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
255255
bridge.onPluginMessage(pluginMessageEvent);
256256
bridge.onServerSwitch(serverSwitchEvent);
257257

@@ -269,7 +269,7 @@ void shouldBlockNonWhitelistedCommandForUnauthenticatedPlayer() {
269269
given(currentServer.getInfo()).willReturn(authServerInfo);
270270
given(authServerInfo.getName()).willReturn("lobby");
271271

272-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
272+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
273273
bridge.onCommand(commandEvent);
274274

275275
verify(commandEvent).setCancelled(true);
@@ -286,7 +286,7 @@ void shouldAllowWhitelistedCommandForUnauthenticatedPlayer() {
286286
given(currentServer.getInfo()).willReturn(authServerInfo);
287287
given(authServerInfo.getName()).willReturn("lobby");
288288

289-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
289+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
290290
bridge.onCommand(commandEvent);
291291

292292
verify(commandEvent, never()).setCancelled(true);
@@ -302,7 +302,7 @@ void shouldBlockChatForUnauthenticatedPlayer() {
302302
given(currentServer.getInfo()).willReturn(authServerInfo);
303303
given(authServerInfo.getName()).willReturn("lobby");
304304

305-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
305+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
306306
bridge.onPlayerChat(chatEvent);
307307

308308
verify(chatEvent).setCancelled(true);
@@ -317,7 +317,7 @@ void shouldDenySwitchToNonAuthServerForUnauthenticatedPlayer() {
317317
given(player.getName()).willReturn("Alice");
318318
given(player.getServer()).willReturn(currentServer);
319319

320-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
320+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
321321
bridge.onPlayerConnectingToServer(serverConnectEvent);
322322

323323
verify(serverConnectEvent).setCancelled(true);
@@ -333,7 +333,7 @@ void shouldDisconnectPlayerOnInitialJoinToNonAuthServerWhenSwitchRequiresAuth()
333333
given(player.getName()).willReturn("Alice");
334334
given(player.getServer()).willReturn(null);
335335

336-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
336+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
337337
bridge.onPlayerConnectingToServer(serverConnectEvent);
338338

339339
verify(serverConnectEvent).setCancelled(true);
@@ -354,7 +354,7 @@ void shouldNotForwardPerformLoginToNonAuthServers() {
354354
given(currentServer.getInfo()).willReturn(nonAuthServerInfo);
355355
given(nonAuthServerInfo.getName()).willReturn("survival");
356356

357-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
357+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
358358
bridge.onPluginMessage(pluginMessageEvent);
359359
bridge.onServerSwitch(serverSwitchEvent);
360360

@@ -376,7 +376,7 @@ void shouldForwardPerformLoginWhenLeavingAuthServer() {
376376
given(serverSwitchEvent.getFrom()).willReturn(authServerInfo);
377377
given(authServerInfo.getName()).willReturn("lobby");
378378

379-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
379+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
380380
bridge.onPluginMessage(pluginMessageEvent);
381381
bridge.onServerSwitch(serverSwitchEvent);
382382

@@ -390,7 +390,7 @@ void shouldSendProxyStartedHandshakeToAuthServerOnStartup() {
390390
given(authServerInfo.getName()).willReturn("lobby");
391391
given(authServerInfo.getPlayers()).willReturn(List.of(player));
392392

393-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
393+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
394394
bridge.broadcastProxyStartedHandshake();
395395

396396
verify(authServerInfo).sendData(eq(BungeeProxyBridge.AUTHME_CHANNEL), payloadCaptor.capture(), eq(false));
@@ -409,7 +409,7 @@ void shouldDeferProxyStartedHandshakeWhenNoPlayersOnAuthServer() {
409409
given(player.getServer()).willReturn(currentServer);
410410
given(currentServer.getInfo()).willReturn(authServerInfo);
411411

412-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
412+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
413413
bridge.broadcastProxyStartedHandshake();
414414

415415
// No handshake sent yet (no players at startup)
@@ -439,7 +439,7 @@ void shouldSendAutoLoginImmediatelyWhenPlayerAlreadySwitchedBeforeLoginMessage()
439439
given(currentServer.getInfo()).willReturn(nonAuthServerInfo);
440440
given(nonAuthServerInfo.getName()).willReturn("survival");
441441

442-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
442+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
443443
bridge.onPluginMessage(pluginMessageEvent);
444444

445445
verify(nonAuthServerInfo).sendData(eq(BungeeProxyBridge.AUTHME_CHANNEL), payloadCaptor.capture(), eq(false));
@@ -456,7 +456,7 @@ void shouldForceOnlineModeForPremiumHandshakeWhenOfflineCompatibilityDisabled()
456456
given(pendingConnection.getName()).willReturn("Alice");
457457
given(pendingConnection.isOnlineMode()).willReturn(false);
458458

459-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
459+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
460460
bridge.onPluginMessage(pluginMessageEvent);
461461
bridge.onPlayerHandshake(playerHandshakeEvent);
462462

@@ -473,7 +473,7 @@ void shouldForceOnlineModeForPremiumHandshakeAfterChunkedPremiumListResync() {
473473
given(pendingConnection.getName()).willReturn("Alice");
474474
given(pendingConnection.isOnlineMode()).willReturn(false);
475475

476-
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore());
476+
BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
477477
bridge.onPluginMessage(pluginMessageEvent);
478478
bridge.onPlayerHandshake(playerHandshakeEvent);
479479

0 commit comments

Comments
 (0)