auth timeout policy

This commit is contained in:
Darto KLoning
2026-10-03 22:20:30 +07:00
parent eb08aae8bf
commit 0442e07dde
5 changed files with 56 additions and 42 deletions
@@ -2,9 +2,18 @@ package id.dartokloning.lobbysystems;
public final class AuthConfig {
public boolean enabled = true;
/**
* true: a Mojang-verified premium account is authenticated automatically unless
* requireLoginEveryJoin is enabled.
* false: premium accounts still use Mojang verification, but must also register
* and use a Lobby Systems password.
*/
public boolean premiumAutoLogin = true;
public boolean protectPremiumUsernames = true;
public int authenticationTimeoutSeconds = 120;
public String registrationTimeoutMessage = "Registration timed out after {seconds} seconds. Please reconnect and create your password.";
public String loginTimeoutMessage = "Login timed out after {seconds} seconds. Please reconnect and enter your password.";
public String authenticationTimeoutMessage = "Authentication timed out after {seconds} seconds. Please reconnect and try again.";
public int maxLoginAttempts = 3;
public int minimumPasswordLength = 6;
public int pbkdf2Iterations = 210000;
@@ -4,7 +4,6 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundSetActionBarTextPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
@@ -114,14 +113,14 @@ public final class AuthManager {
}
// false means premium accounts are satisfied by Mojang authentication.
if (s.premium && !config.requireLoginEveryJoin) {
if (s.premium && config.premiumAutoLogin && !config.requireLoginEveryJoin) {
authenticate(player);
continue;
}
// true makes the Lobby Systems password an additional layer for premium
// accounts, and local accounts always require it on each connection.
if (s.premium && config.requireLoginEveryJoin && s.authenticated) {
if (s.premium && (!config.premiumAutoLogin || config.requireLoginEveryJoin) && s.authenticated) {
s.authenticated = false;
s.joinedAt = System.currentTimeMillis();
s.attempts = 0;
@@ -129,7 +128,6 @@ public final class AuthManager {
if (!s.authenticated) {
s.stage = isRegistered(player.getName().getString()) ? Stage.LOGIN : Stage.REGISTER;
s.joinedAt = System.currentTimeMillis();
s.lastCountdownSecond = -1L;
showCurrentDialog(player, s);
}
}
@@ -137,7 +135,7 @@ public final class AuthManager {
/** Called from the login mixin before the player enters play state. */
public LoginRoute resolveLoginRoute(String username) {
if (!config.enabled || !config.premiumAutoLogin) return LoginRoute.PREMIUM;
if (!config.enabled) return LoginRoute.PREMIUM;
PremiumResult result = premiumStatus(username);
return switch (result) {
case PREMIUM -> LoginRoute.PREMIUM;
@@ -227,13 +225,18 @@ public final class AuthManager {
for (ServerPlayer player : List.copyOf(server.getPlayerList().getPlayers())) {
Session s = sessions.get(player.getUUID());
if (s == null || s.authenticated) continue;
int timeoutSeconds = s.stage == Stage.RULES ? Math.max(1, rules.timeoutSeconds) : Math.max(1, config.authenticationTimeoutSeconds);
int timeoutSeconds = s.stage == Stage.RULES
? Math.max(1, rules.timeoutSeconds)
: Math.max(1, config.authenticationTimeoutSeconds);
long remainingMillis = timeoutSeconds * 1000L - (now - s.joinedAt);
if (remainingMillis <= 0L) {
String timeoutMessage = s.stage == Stage.RULES
? "You did not accept the server rules in time. Please reconnect and try again."
: "Authentication timed out. Please reconnect and try again.";
player.connection.disconnect(Component.literal(timeoutMessage));
String template = switch (s.stage) {
case RULES -> rules.timeoutMessage;
case REGISTER -> config.registrationTimeoutMessage;
case LOGIN -> config.loginTimeoutMessage;
default -> config.authenticationTimeoutMessage;
};
player.connection.disconnect(Component.literal(formatTimeoutMessage(template, timeoutSeconds)));
continue;
}
// Keep the player at the safe Lobby authentication position. World safety is
@@ -249,24 +252,6 @@ public final class AuthManager {
showCurrentDialog(player, s);
}
// A live countdown is shown in the action bar. Do not reopen the dialog every
// few seconds: reopening destroys text currently typed into password fields.
long remainingSeconds = Math.max(1L, (remainingMillis + 999L) / 1000L);
if (remainingSeconds != s.lastCountdownSecond) {
s.lastCountdownSecond = remainingSeconds;
String countdownLabel = switch (s.stage) {
case RULES -> "Accept the rules within: ";
case REGISTER -> "Register within: ";
case LOGIN -> "Login within: ";
default -> "Time remaining: ";
};
long minutes = remainingSeconds / 60L;
long seconds = remainingSeconds % 60L;
player.connection.send(new ClientboundSetActionBarTextPacket(
Component.literal(countdownLabel + String.format(Locale.ROOT, "%02d:%02d", minutes, seconds))
.withStyle(net.minecraft.ChatFormatting.YELLOW)
));
}
}
}
@@ -293,7 +278,6 @@ public final class AuthManager {
// are accepted, start a fresh timer for REGISTER/LOGIN instead of carrying over
// time spent reading the rules.
s.joinedAt = System.currentTimeMillis();
s.lastCountdownSecond = -1L;
continueAfterRules(player);
return 1;
}
@@ -303,7 +287,7 @@ public final class AuthManager {
Session s = sessions.get(player.getUUID());
if (s == null || s.authenticated) return failAndReopen(player, "§eYou are already authenticated.", DIALOG_REGISTER);
if (s.stage == Stage.RULES) return failAndReopen(player, "§cPlease accept the server rules first.", DIALOG_RULES);
if (s.premium && !config.requireLoginEveryJoin) return fail(player, "§ePremium accounts use Mojang authentication and do not need a local password.");
if (s.premium && config.premiumAutoLogin && !config.requireLoginEveryJoin) return fail(player, "§ePremium accounts use Mojang authentication and do not need a local password.");
String name = player.getName().getString();
if (!s.premium) {
@@ -370,7 +354,7 @@ public final class AuthManager {
Session s = sessions.get(player.getUUID());
if (s == null) return false;
if (s.premium && !config.requireLoginEveryJoin) {
if (s.premium && config.premiumAutoLogin && !config.requireLoginEveryJoin) {
authenticate(player);
player.sendSystemMessage(Component.literal("§aLobby Systems: Premium account verified. Auto-login successful."));
return false;
@@ -561,6 +545,13 @@ public final class AuthManager {
}
}
private static String formatTimeoutMessage(String template, int seconds) {
String value = (template == null || template.isBlank())
? "Authentication timed out after {seconds} seconds. Please reconnect and try again."
: template;
return value.replace("{seconds}", Integer.toString(seconds));
}
private static String key(String username) { return username.toLowerCase(Locale.ROOT); }
private void saveConfig() throws Exception {
@@ -619,7 +610,6 @@ public final class AuthManager {
long lastReminder; // retained for binary/source migration; no longer reopens dialogs
boolean dialogPending;
long dialogReadyAt;
long lastCountdownSecond = -1L;
final double x, y, z;
final float yaw, pitch;
Stage stage = Stage.RULES;
@@ -7,6 +7,7 @@ public final class RulesConfig {
public boolean enabled = true;
public int version = 1;
public int timeoutSeconds = 60;
public String timeoutMessage = "You did not accept the server rules within {seconds} seconds. Please reconnect and try again.";
public String title = "Server Rules";
public String introduction = "Please read and accept our rules:";
public List<String> rules = new ArrayList<>(List.of(