Вход без пароля, права админа и F3+B

Аутентификация переделана по схеме пользователя: лаунчер — единственный
источник токена, сервер проверяет его сам и лаунчера не спрашивает.

- rsx-auth/GameToken: токен это base64url(payload) + "." + base64url(HMAC-SHA256),
  payload {uuid, nick, aud, exp, jti, typ}, typ = session или reload.
- Сессия живёт час, reload-токен полтора часа и только внутри игры. Пакет
  SessionReload продлевает сессию и выдаёт новый reload-токен.
- Сервер проверяет подпись, аудиенс, срок и совпадение uuid. HTTP-запрос к
  лаунчеру из сервера убран.
- Клиент: окно пароля убрано совсем, ник берётся из токена, клиент сам
  продлевает сессию за 5 минут до конца.
- Для локальной разработки сервер с --dev-token пишет свой токен в
  world/dev-token.txt, клиент берёт токен оттуда.
- Права админа: свой список world/admins.txt, перечитывается раз в 5 секунд,
  поэтому право снимается на лету. Пакет AdminState.
- Ванильные блоки мира ломает только админ, режим игры прав не даёт.
- F3+B только для админов: без прав показываем отказ, с правом включаются и
  ванильные хитбоксы, и наш отладочный рендер. Права сняли посреди просмотра —
  гаснут сразу.
- run-game.ps1 умеет -Token.

Проверено на живом сервере: own session ok, player in, rights of Player given.
Тесты: протокол 34, сервер 88, форк собирается чисто.
This commit is contained in:
sasheg dev
2026-10-22 08:50:57 +03:00
parent cf90f71e58
commit d2a0a7a93b
21 changed files with 1478 additions and 543 deletions
@@ -343,6 +343,33 @@ public final class ItemTest {
eq("two names", Integer.valueOf(2), Integer.valueOf(listBack.names.length));
eq("the first name", "One", listBack.names[0]);
eq("the second mode", Integer.valueOf(1), Integer.valueOf(listBack.gameModes[1]));
Packets.AuthAck ack = Packets.AuthAck.ok("the session", 111L, "the reload", 222L);
Packets.AuthAck ackBack = Packets.AuthAck.read(new Buf(ack.write().toByteArray()));
yes("the session was made", ackBack.accepted);
eq("the session token", "the session", ackBack.token);
eq("when it runs out", Long.valueOf(111L), Long.valueOf(ackBack.expiresAt));
eq("the reload token", "the reload", ackBack.reloadToken);
eq("when the reload token runs out", Long.valueOf(222L),
Long.valueOf(ackBack.reloadExpiresAt));
Packets.SessionReload reload = new Packets.SessionReload("the reload");
Packets.SessionReload reloadBack =
Packets.SessionReload.read(new Buf(reload.write().toByteArray()));
eq("the reload token survives", "the reload", reloadBack.token);
Packets.SessionRenewed renewed = new Packets.SessionRenewed(true, "",
"the session", 333L, "the reload", 444L);
Packets.SessionRenewed renewedBack =
Packets.SessionRenewed.read(new Buf(renewed.write().toByteArray()));
yes("the session was made longer", renewedBack.accepted);
eq("the new session token", "the session", renewedBack.token);
eq("the new reload token", "the reload", renewedBack.reloadToken);
Packets.AdminState admin = new Packets.AdminState(true);
Packets.AdminState adminBack =
Packets.AdminState.read(new Buf(admin.write().toByteArray()));
yes("the right is there", adminBack.admin);
}
private static void near(Object what, double expected, double actual) {
@@ -488,10 +488,10 @@ public final class ProtocolTest {
eq("remove", Integer.valueOf(11), Integer.valueOf(
Packets.EntityRemove.read(new Buf(remove.write().toByteArray())).entityId));
Packets.LauncherAuth launcher = new Packets.LauncherAuth("Player",
Packets.TokenAuth launcher = new Packets.TokenAuth("Player",
"00000000-0000-0000-0000-000000000000", "a.b.c");
Packets.LauncherAuth launcherBack =
Packets.LauncherAuth.read(new Buf(launcher.write().toByteArray()));
Packets.TokenAuth launcherBack =
Packets.TokenAuth.read(new Buf(launcher.write().toByteArray()));
eq("launcher account", "Player", launcherBack.account);
eq("launcher uuid", "00000000-0000-0000-0000-000000000000", launcherBack.uuid);
eq("launcher token", "a.b.c", launcherBack.token);
@@ -544,7 +544,7 @@ public final class ProtocolTest {
Packets.LOGIN, Packets.LOGIN_ACK, Packets.JOIN_WORLD,
Packets.WORLD_JOIN, Packets.CHUNK_DATA, Packets.BLOCK_CHANGE,
Packets.PLAYER_MOVE, Packets.ENTITY_MOVE, Packets.PING, Packets.PONG,
Packets.ENTITY_SPAWN, Packets.ENTITY_REMOVE, Packets.LAUNCHER_AUTH,
Packets.ENTITY_SPAWN, Packets.ENTITY_REMOVE, Packets.TOKEN_AUTH,
Packets.SET_GAME_MODE, Packets.INVENTORY, Packets.INVENTORY_SLOT,
Packets.DIG, Packets.BREAK_PROGRESS, Packets.CRAFT_START, Packets.CRAFT_UPDATE,
Packets.RESEARCH_START, Packets.RESEARCH_UPDATE, Packets.TECH_TREE_UNLOCK,
@@ -553,6 +553,7 @@ public final class ProtocolTest {
Packets.LEARN_BLUEPRINT,
Packets.HITBOXES, Packets.HITBOX_INSTANCES, Packets.HITBOXES_RELOAD,
Packets.ATTACK, Packets.CHAT, Packets.CHAT_SEND, Packets.PLAYER_LIST,
Packets.SESSION_RELOAD, Packets.SESSION_RENEWED, Packets.ADMIN_STATE,
Packets.DISCONNECT,
};
for (int i = 0; i < ids.length; i++) {