auth timeout policy
This commit is contained in:
@@ -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
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user