Skip to content

Commit 68d8834

Browse files
committed
refactor: Some fixes for premium feature
- evict stale pending entries to prevent memory leak - atomically replace premium list to avoid concurrent update races - reject invalid usernames before DB lookup in packet listener - log rate-limit (429) errors distinctly in Mojang profile API - rename canBypassWithPremiumAtPreJoin to shouldSkipPreJoinDialogForPremium - use camelCase premiumUUID as default column name - clarify why UUID v4 skips the Mojang API call - use 'Mojang account' wording in premium account_not_found - clarify session server downtime behavior in FAQ - simplify premium bypass feature description
1 parent ab16b22 commit 68d8834

14 files changed

Lines changed: 50 additions & 26 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ You can also create your own translation file and, if you want, you can share it
5858
<li>Graphical login/register dialogs, with optional Paper/Folia pre-join dialogs</li>
5959
<li>Restricted users (associate a username with an IP)</li>
6060
<li>Protect player's inventory until correct authentication (requires PacketEvents)</li>
61-
<li><strong>Premium bypass: Mojang-account holders skip password auth via cryptographic session verification (requires PacketEvents)</strong></li>
61+
<li><strong>Premium bypass: Mojang-account holders skip password auth (requires PacketEvents)</strong></li>
6262
<li>Saves the quit location of the player</li>
6363
<li>Automatic database backup</li>
6464
<li>Available languages: <a href="https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/translations.md">translations</a></li>

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public final class BungeeProxyBridge implements Listener {
5252
private final BungeeAuthenticationStore authenticationStore;
5353
private final Map<String, AtomicInteger> pendingAutoLogins = new ConcurrentHashMap<>();
5454
private final Set<String> notifiedAuthServers = ConcurrentHashMap.newKeySet();
55-
private final Set<String> premiumUsernames = ConcurrentHashMap.newKeySet();
55+
private volatile Set<String> premiumUsernames = ConcurrentHashMap.newKeySet();
5656
// Players whose Mojang UUID was confirmed by the proxy during the login phase (LoginSuccess with UUID v4)
5757
private final Set<String> proxyVerifiedPremium = ConcurrentHashMap.newKeySet();
5858
private final ScheduledExecutorService retryScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
@@ -175,19 +175,20 @@ public void onPluginMessage(PluginMessageEvent event) {
175175
cancelPendingLogin(parsedMessage.playerName());
176176
} else if (PREMIUM_SET_MESSAGE.equals(parsedMessage.typeId())) {
177177
premiumUsernames.add(parsedMessage.playerName());
178-
logger.fine("Premium enabled for '" + parsedMessage.playerName() + "' (proxy cache updated)");
178+
logger.fine(() -> "Premium enabled for '" + parsedMessage.playerName() + "' (proxy cache updated)");
179179
} else if (PREMIUM_UNSET_MESSAGE.equals(parsedMessage.typeId())) {
180180
premiumUsernames.remove(parsedMessage.playerName());
181-
logger.fine("Premium disabled for '" + parsedMessage.playerName() + "' (proxy cache updated)");
181+
logger.fine(() -> "Premium disabled for '" + parsedMessage.playerName() + "' (proxy cache updated)");
182182
} else if (PREMIUM_LIST_MESSAGE.equals(parsedMessage.typeId())) {
183-
premiumUsernames.clear();
183+
Set<String> newPremiumSet = ConcurrentHashMap.newKeySet();
184184
if (!parsedMessage.playerName().isEmpty()) {
185185
for (String name : parsedMessage.playerName().split(",")) {
186186
if (!name.isEmpty()) {
187-
premiumUsernames.add(name.trim());
187+
newPremiumSet.add(name.trim());
188188
}
189189
}
190190
}
191+
premiumUsernames = newPremiumSet;
191192
logger.info("Premium list received from backend: " + premiumUsernames.size() + " premium player(s)");
192193
}
193194
}

authme-core/src/main/java/fr/xephi/authme/listener/packetevents/PremiumVerificationPacketListener.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Optional;
2525
import java.util.UUID;
2626
import java.util.concurrent.CompletableFuture;
27+
import java.util.regex.Pattern;
2728

2829
/**
2930
* Intercepts {@code LOGIN_START} and {@code ENCRYPTION_RESPONSE} packets to perform
@@ -42,6 +43,8 @@
4243
*/
4344
public class PremiumVerificationPacketListener extends PacketListenerAbstract {
4445

46+
private static final Pattern VALID_USERNAME = Pattern.compile("^[a-zA-Z0-9_]{2,16}$");
47+
4548
private final ConsoleLogger logger = ConsoleLoggerFactory.get(PremiumVerificationPacketListener.class);
4649

4750
private final DataSource dataSource;
@@ -64,7 +67,7 @@ public void onPacketReceive(PacketReceiveEvent event) {
6467
private void handleLoginStart(PacketReceiveEvent event) {
6568
WrapperLoginClientLoginStart wrapper = new WrapperLoginClientLoginStart(event);
6669
String username = wrapper.getUsername();
67-
if (username == null || username.isEmpty()) {
70+
if (username == null || !VALID_USERNAME.matcher(username).matches()) {
6871
return;
6972
}
7073

authme-core/src/main/java/fr/xephi/authme/service/MojangApiService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ public Optional<UUID> fetchUuidByName(String username) {
4646
if (code == HttpURLConnection.HTTP_NO_CONTENT || code == HttpURLConnection.HTTP_NOT_FOUND) {
4747
return Optional.empty();
4848
}
49+
if (code == 429) {
50+
logger.warning("Mojang profile API rate-limited (429) for '" + username + "'; try again later");
51+
return Optional.empty();
52+
}
4953
if (code != HttpURLConnection.HTTP_OK) {
5054
logger.warning("Mojang profile API returned " + code + " for '" + username + "'");
5155
return Optional.empty();

authme-core/src/main/java/fr/xephi/authme/service/PremiumLoginVerifier.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
public class PremiumLoginVerifier {
3535

3636
private static final long VERIFIED_TTL_MS = 60_000L;
37+
private static final long PENDING_TTL_MS = 30_000L;
3738

3839
private final ConsoleLogger logger = ConsoleLoggerFactory.get(PremiumLoginVerifier.class);
3940

@@ -71,12 +72,18 @@ public PublicKey getPublicKey() {
7172
* @return a freshly generated 4-byte verify token to embed in the EncryptionRequest
7273
*/
7374
public byte[] startVerification(String connectionKey, String username, UUID playerUUID) {
75+
evictStalePendingEntries();
7476
byte[] verifyToken = new byte[4];
7577
secureRandom.nextBytes(verifyToken);
7678
pending.put(connectionKey, new PendingVerification(username, playerUUID, verifyToken, System.currentTimeMillis()));
7779
return verifyToken;
7880
}
7981

82+
private void evictStalePendingEntries() {
83+
long now = System.currentTimeMillis();
84+
pending.entrySet().removeIf(e -> now - e.getValue().startedAt() > PENDING_TTL_MS);
85+
}
86+
8087
/** Returns true if an in-flight verification exists for this connection. */
8188
public boolean hasPending(String connectionKey) {
8289
return pending.containsKey(connectionKey);

authme-core/src/main/java/fr/xephi/authme/service/PremiumService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ public void enablePremium(Player player) {
7474

7575
UUID playerUuid = player.getUniqueId();
7676
if (playerUuid.version() == 4) {
77-
// UUID v4 = Mojang online UUID (online-mode server or proxy forwarding): use directly.
77+
// UUID v4 = Mojang-issued UUID: either the server is in online mode (Mojang already
78+
// verified the session at the connection level) or the proxy forwarded a real Mojang
79+
// UUID. No extra API call needed — the server's own auth is the source of truth.
7880
storePremiumUuid(auth, playerUuid, player, player.getName());
7981
} else {
8082
// UUID v3 = Bukkit offline UUID: fetch the real UUID from Mojang API.

authme-core/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ public final class DatabaseSettings implements SettingsHolder {
141141
@Comment({"Column for storing the Mojang UUID of premium players",
142142
"(null if premium mode is off for this player)"})
143143
public static final Property<String> MYSQL_COL_PREMIUM_UUID =
144-
newProperty("DataSource.mySQLColumnPremiumUUID", "premium_uuid");
144+
newProperty("DataSource.mySQLColumnPremiumUUID", "premiumUUID");
145145

146146
@Comment("Column for storing players groups")
147147
public static final Property<String> MYSQL_COL_GROUP =

authme-core/src/main/resources/messages/messages_en.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ admin:
216216
# Premium mode
217217
premium:
218218
feature_disabled: '&cPremium mode is not enabled on this server.'
219-
account_not_found: '&cNo premium Minecraft account found for your username.'
219+
account_not_found: '&cNo Mojang account found for your username.'
220220
already_enabled: '&ePremium mode is already enabled for your account.'
221221
enable_success: '&2Premium mode enabled! You will no longer need to authenticate on login.'
222222
not_enabled: '&ePremium mode is not enabled for your account.'

authme-core/src/main/resources/messages/messages_ro.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ admin:
212212
first_set_success: '&2Noul prim punct de spawn definit corect.'
213213
first_set_fail: '&cSetFirstSpawn a eșuat, te rugăm să reîncerci.'
214214
not_defined: '&cSpawn-ul a eșuat, te rugăm să încearcă să definești spawn-ul.'
215+
first_not_defined: '&cPrimul spawn a eșuat, te rugăm să încearcă să definești primul spawn.'
215216

216217
# Modul premium
217218
premium:
@@ -231,4 +232,3 @@ premium:
231232
disable_success: '&2Modul premium dezactivat pentru %name.'
232233
impostor_kicked: '&eUn jucător conectat ca %name a avut un UUID diferit și a fost dat afară.'
233234
kick_reason: '&cSetările premium ale contului tău au fost modificate de un administrator. Te rugăm să te reconectezi.'
234-
first_not_defined: '&cPrimul spawn a eșuat, te rugăm să încearcă să definești primul spawn.'

authme-core/src/test/resources/fr/xephi/authme/datasource/sql-initialize.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ CREATE TABLE authme (
2020
realname VARCHAR(255) NOT NULL DEFAULT 'Player',
2121
salt varchar(255),
2222
hasSession INT NOT NULL DEFAULT '0',
23-
premium_uuid VARCHAR(36)
23+
premiumUUID VARCHAR(36)
2424
);
2525

2626
INSERT INTO authme (username, password, ip, lastlogin, x, y, z, world, yaw, pitch, email, isLogged, realname, salt, regdate, regip, totp)

0 commit comments

Comments
 (0)