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
+23 -9
View File
@@ -1,4 +1,4 @@
# Lobby Systems v1.8.4 — Fabric 26.2
# Lobby Systems v1.8.5 — Fabric 26.2
Server-side lobby framework untuk Minecraft Fabric 26.2. Lobby Systems menangani lobby placement, portal antar-dimensi, gamemode/gamerule per dimensi, Starter Kit, penyimpanan posisi player, dan hybrid authentication untuk premium + local/cracked player.
@@ -100,12 +100,14 @@ Default:
"requireLoginEveryJoin": false
```
Semantik:
Semantik `premiumAutoLogin` dan `requireLoginEveryJoin`:
- `false`: premium player cukup dengan Mojang authentication dan auto-login; local/cracked player tetap wajib password Lobby Systems pada setiap connection.
- `true`: premium player tetap diverifikasi Mojang, tetapi juga wajib mempunyai dan memasukkan password Lobby Systems setiap connection. Local/cracked player tetap wajib password seperti biasa.
- `premiumAutoLogin=true` + `requireLoginEveryJoin=false`: premium player cukup dengan Mojang authentication dan auto-login.
- `premiumAutoLogin=false`: premium player tetap diverifikasi Mojang, tetapi wajib membuat/memasukkan password Lobby Systems.
- `requireLoginEveryJoin=true`: premium player juga wajib password Lobby Systems setiap connection, terlepas dari auto-login policy.
- Local/cracked player selalu memakai password Lobby Systems setiap connection.
Jadi `true` merupakan lapisan password tambahan untuk premium account, bukan pengganti Mojang authentication.
Password Lobby Systems untuk premium adalah lapisan tambahan; Mojang authentication tetap dipertahankan.
### Server Rules
@@ -115,6 +117,8 @@ Rules dipisahkan dari Auth config dan dibuat otomatis sebagai `config/lobby/auth
{
"enabled": true,
"version": 1,
"timeoutSeconds": 60,
"timeoutMessage": "You did not accept the server rules within {seconds} seconds. Please reconnect and try again.",
"title": "Server Rules",
"introduction": "Please read and accept our rules:",
"rules": [
@@ -223,7 +227,7 @@ Console menampilkan `RELOAD START` dan `RELOAD COMPLETE`. Player admin yang menj
- Memperbaiki crash `ConcurrentModificationException` ketika authentication timeout men-disconnect player dari dalam server tick. Auth sekarang mengiterasi snapshot player list, bukan live `ArrayList`.
- Initial dialog ditunda singkat sampai player benar-benar tersedia sebagai target command; ini menghilangkan kegagalan awal `No player was found`.
- Dialog tidak lagi dibuka ulang setiap `reminderIntervalSeconds`, karena itu dapat menghapus password yang sedang diketik.
- Countdown sekarang memakai dua fase independen melalui action bar: Rules memakai `rules.json -> timeoutSeconds`, sedangkan Register/Login memakai `auth/config.json -> authenticationTimeoutSeconds`. Timer Register/Login dimulai ulang dari penuh setelah Rules diterima.
- Rules dan Register/Login memakai timeout independen: Rules memakai `rules.json -> timeoutSeconds`, sedangkan Register/Login memakai `auth/config.json -> authenticationTimeoutSeconds`. Timer Register/Login dimulai ulang dari penuh setelah Rules diterima.
- Rules dipindahkan ke `config/lobby/auth/rules.json` dan Rules dialog dibangun dari file tersebut.
- `rulesEnabled` dan `rulesVersion` lama dipindahkan menjadi `enabled` dan `version` di `rules.json`.
- `/ls reload` juga me-reload `rules.json` dan mengevaluasi ulang acceptance/session player online.
@@ -240,8 +244,7 @@ All Rights Reserved. Source dan asset Lobby Systems tidak boleh didistribusikan
## Catatan v1.8.3
- Memperbaiki compile error Minecraft 26.2 karena `ServerPlayer.displayClientMessage(Component, boolean)` sudah tidak tersedia pada mapping/API target ini.
- Countdown authentication sekarang dikirim langsung dengan vanilla `ClientboundSetActionBarTextPacket`, sehingga tetap tampil sebagai action bar tanpa bergantung pada method player yang telah dihapus.
- Warna countdown menggunakan `ChatFormatting`/component styling, bukan legacy section-code di payload action bar.
- Implementasi action-bar countdown yang diperkenalkan sementara pada v1.8.3 kemudian dihentikan pada v1.8.5 karena Dialog bersifat modal dan countdown HUD tidak memberi UX yang konsisten.
- Seluruh perubahan v1.8.2 (safe timeout iteration, delayed initial dialog, `rules.json`, dan global `/ls reload`) tetap dipertahankan.
@@ -251,6 +254,17 @@ All Rights Reserved. Source dan asset Lobby Systems tidak boleh didistribusikan
- `rules.json` memiliki `timeoutSeconds` (default `60`) untuk batas waktu menerima Rules.
- Register/Login tetap memakai `authenticationTimeoutSeconds` (default `120`).
- Setelah Rules diterima, timer Rules dihentikan dan timer Register/Login dimulai dari awal.
- Countdown action bar memakai label sesuai fase: `Accept the rules within`, `Register within`, atau `Login within`.
- Tidak ada countdown visual pada HUD/action bar; timeout berjalan di server dan hanya muncul sebagai alasan disconnect jika waktunya habis.
- Timeout Rules dan timeout authentication memiliki pesan disconnect yang berbeda.
- `/ls reload` tetap memuat ulang seluruh konfigurasi Lobby Systems, termasuk `auth/rules.json` dan `auth/config.json`.
## v1.8.5 — Auth timeout messages & premium login policy
- Menghapus countdown visual dari action bar. Dialog Rules/Register/Login tetap bersih tanpa HUD timer yang tertutup layer Dialog.
- Two-stage timeout tetap dipertahankan secara server-side: Rules memakai `rules.json -> timeoutSeconds`, sedangkan Register/Login memakai `auth/config.json -> authenticationTimeoutSeconds`.
- `rules.json` sekarang memiliki `timeoutMessage`. Placeholder `{seconds}` otomatis diganti dengan nilai `timeoutSeconds`.
- `auth/config.json` sekarang memiliki `registrationTimeoutMessage`, `loginTimeoutMessage`, dan fallback `authenticationTimeoutMessage`; placeholder `{seconds}` memakai `authenticationTimeoutSeconds`.
- `premiumAutoLogin=false` sekarang benar-benar mematikan auto-login Lobby Systems untuk premium player. Mojang authentication tetap berjalan, kemudian premium player diarahkan ke Register/Login Dialog dan membutuhkan password Lobby Systems.
- `premiumAutoLogin=true` + `requireLoginEveryJoin=false` tetap merupakan mode premium auto-login. `requireLoginEveryJoin=true` tetap memaksa password Lobby Systems untuk premium player setiap join.
- `/ls reload` memuat ulang policy dan pesan timeout Auth/Rules tanpa restart server.
+1 -1
View File
@@ -5,6 +5,6 @@ minecraft_version=26.2
loader_version=0.19.3
loom_version=1.17-SNAPSHOT
fabric_api_version=0.156.0+26.2
mod_version=1.8.4
mod_version=1.8.5
maven_group=id.dartokloning
archives_base_name=lobby-systems
@@ -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(