diff --git a/src/main/java/org/samo_lego/simpleauth/SimpleAuth.java b/src/main/java/org/samo_lego/simpleauth/SimpleAuth.java index 5ca1289..4f2a2ac 100644 --- a/src/main/java/org/samo_lego/simpleauth/SimpleAuth.java +++ b/src/main/java/org/samo_lego/simpleauth/SimpleAuth.java @@ -1,16 +1,20 @@ package org.samo_lego.simpleauth; +import com.google.gson.JsonObject; import net.fabricmc.api.DedicatedServerModInitializer; import net.fabricmc.fabric.api.event.player.*; import net.fabricmc.fabric.api.event.server.ServerStopCallback; import net.fabricmc.fabric.api.registry.CommandRegistry; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.LiteralText; import net.minecraft.text.Text; +import net.minecraft.world.dimension.DimensionType; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.iq80.leveldb.WriteBatch; import org.samo_lego.simpleauth.commands.*; import org.samo_lego.simpleauth.event.AuthEventHandler; import org.samo_lego.simpleauth.event.entity.player.*; @@ -21,13 +25,12 @@ import org.samo_lego.simpleauth.storage.PlayerCache; import org.samo_lego.simpleauth.storage.SimpleAuthDatabase; import java.io.File; -import java.io.FileReader; -import java.io.IOException; import java.util.HashMap; import java.util.Properties; import java.util.Timer; import java.util.TimerTask; +import static org.iq80.leveldb.impl.Iq80DBFactory.bytes; import static org.samo_lego.simpleauth.utils.CarpetHelper.isPlayerCarpetFake; import static org.samo_lego.simpleauth.utils.UuidConverter.convertUuid; @@ -109,6 +112,32 @@ public class SimpleAuth implements DedicatedServerModInitializer { private static void onStopServer() { LOGGER.info("[SimpleAuth] Shutting down SimpleAuth."); + + WriteBatch batch = db.levelDBStore.createWriteBatch(); + // Writing coords of de-authenticated players to database + deauthenticatedUsers.forEach((uuid, playerCache) -> { + JsonObject data = new JsonObject(); + data.addProperty("password", playerCache.password); + + JsonObject lastLocation = new JsonObject(); + lastLocation.addProperty("dimId", playerCache.lastDimId); + lastLocation.addProperty("x", playerCache.lastX); + lastLocation.addProperty("y", playerCache.lastY); + lastLocation.addProperty("z", playerCache.lastZ); + + data.addProperty("lastLocation", lastLocation.toString()); + + batch.put(bytes("UUID:" + uuid), bytes("data:" + data.toString())); + }); + try { + // Writing and closing batch + db.levelDBStore.write(batch); + batch.close(); + } catch (IOException e) { + LOGGER.error("[SimpleAuth] Error saving player data! " + e.getMessage()); + } + + // Closing DB connection db.close(); } @@ -122,7 +151,12 @@ public class SimpleAuth implements DedicatedServerModInitializer { // Authenticates player and sends the message public static void authenticatePlayer(ServerPlayerEntity player, Text msg) { + // Teleporting player back + if(config.main.spawnOnJoin) + teleportPlayer(player, false); + deauthenticatedUsers.remove(convertUuid(player)); + // Player no longer needs to be invisible and invulnerable player.setInvulnerable(false); player.setInvisible(false); @@ -133,9 +167,14 @@ public class SimpleAuth implements DedicatedServerModInitializer { public static void deauthenticatePlayer(ServerPlayerEntity player) { if(db.isClosed()) return; + // Marking player as not authenticated, (re)setting login tries to zero String uuid = convertUuid(player); - SimpleAuth.deauthenticatedUsers.put(uuid, new PlayerCache(uuid, player.getIp())); + SimpleAuth.deauthenticatedUsers.put(uuid, new PlayerCache(uuid, player)); + + // Teleporting player to spawn to hide its position + if(config.main.spawnOnJoin) + teleportPlayer(player, true); // Player is now not authenticated player.sendMessage(notAuthenticated(), false); @@ -157,4 +196,33 @@ public class SimpleAuth implements DedicatedServerModInitializer { // We ask CarpetHelper class since it has the imports needed return FabricLoader.getInstance().isModLoaded("carpet") && isPlayerCarpetFake(player); } + + // Teleports player to spawn or last location when authenticating + public static void teleportPlayer(ServerPlayerEntity player, boolean toSpawn) { + MinecraftServer server = player.getServer(); + if(server == null) + return; + if (toSpawn) { + // Teleports player to spawn + player.teleport( + server.getWorld(DimensionType.byRawId(config.worldSpawn.dimensionId)), + config.worldSpawn.x, + config.worldSpawn.y, + config.worldSpawn.z, + 0, + 0 + ); + return; + } + PlayerCache cache = deauthenticatedUsers.get(convertUuid(player)); + // Puts player to last cached position + player.teleport( + server.getWorld(DimensionType.byRawId(cache.lastDimId)), + cache.lastX, + cache.lastY, + cache.lastZ, + 0, + 0 + ); + } } \ No newline at end of file diff --git a/src/main/java/org/samo_lego/simpleauth/commands/AuthCommand.java b/src/main/java/org/samo_lego/simpleauth/commands/AuthCommand.java index c9d5af1..2899768 100644 --- a/src/main/java/org/samo_lego/simpleauth/commands/AuthCommand.java +++ b/src/main/java/org/samo_lego/simpleauth/commands/AuthCommand.java @@ -2,6 +2,7 @@ package org.samo_lego.simpleauth.commands; import com.google.gson.JsonObject; import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.command.arguments.BlockPosArgumentType; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.server.command.ServerCommandSource; @@ -15,19 +16,24 @@ import org.samo_lego.simpleauth.storage.PlayerCache; import org.samo_lego.simpleauth.utils.AuthHelper; import java.io.File; +import java.util.Objects; +import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger; +import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; import static com.mojang.brigadier.arguments.StringArgumentType.getString; import static com.mojang.brigadier.arguments.StringArgumentType.word; import static net.minecraft.server.command.CommandManager.argument; import static net.minecraft.server.command.CommandManager.literal; +import static org.samo_lego.simpleauth.SimpleAuth.config; +import static org.samo_lego.simpleauth.SimpleAuth.db; public class AuthCommand { private static final Logger LOGGER = LogManager.getLogger(); - private static Text userdataDeleted = new LiteralText(SimpleAuth.config.lang.userdataDeleted); - private static Text userdataUpdated = new LiteralText(SimpleAuth.config.lang.userdataUpdated); - private static Text configurationReloaded = new LiteralText(SimpleAuth.config.lang.configurationReloaded); - private static Text globalPasswordSet = new LiteralText(SimpleAuth.config.lang.globalPasswordSet); + private static Text userdataDeleted = new LiteralText(config.lang.userdataDeleted); + private static Text userdataUpdated = new LiteralText(config.lang.userdataUpdated); + private static Text configurationReloaded = new LiteralText(config.lang.configurationReloaded); + private static Text globalPasswordSet = new LiteralText(config.lang.globalPasswordSet); public static void registerCommand(CommandDispatcher dispatcher) { // Registering the "/auth" command @@ -44,6 +50,27 @@ public class AuthCommand { )) ) ) + .then(literal("setSpawn") + .executes( ctx -> setSpawn( + ctx.getSource(), + Objects.requireNonNull(ctx.getSource().getEntity()).dimension.getRawId(), + ctx.getSource().getEntity().getX(), + ctx.getSource().getEntity().getY(), + ctx.getSource().getEntity().getZ() + )) + .then(argument("dimension id", integer()) + .then(argument("position", BlockPosArgumentType.blockPos()) + .executes(ctx -> setSpawn( + ctx.getSource(), + getInteger(ctx, "dimension id"), + BlockPosArgumentType.getLoadedBlockPos(ctx, "position").getX(), + // +1 to not spawn player in ground + BlockPosArgumentType.getLoadedBlockPos(ctx, "position").getY() + 1, + BlockPosArgumentType.getLoadedBlockPos(ctx, "position").getZ() + )) + ) + ) + ) .then(literal("remove") .then(argument("uuid", word()) .executes( ctx -> removeAccount( @@ -80,12 +107,12 @@ public class AuthCommand { // Reloading the config private static int reloadConfig(ServerCommandSource source) { Entity sender = source.getEntity(); - SimpleAuth.config = AuthConfig.load(new File("./mods/SimpleAuth/config.json")); + config = AuthConfig.load(new File("./mods/SimpleAuth/config.json")); if(sender != null) ((PlayerEntity) sender).sendMessage(configurationReloaded, false); else - LOGGER.info(SimpleAuth.config.lang.configurationReloaded); + LOGGER.info(config.lang.configurationReloaded); return 1; } @@ -94,27 +121,41 @@ public class AuthCommand { // Getting the player who send the command Entity sender = source.getEntity(); // Writing the global pass to config - SimpleAuth.config.main.globalPassword = AuthHelper.hashPass(pass.toCharArray()); - SimpleAuth.config.main.enableGlobalPassword = true; - SimpleAuth.config.save(new File("./mods/SimpleAuth/config.json")); + config.main.globalPassword = AuthHelper.hashPass(pass.toCharArray()); + config.main.enableGlobalPassword = true; + config.save(new File("./mods/SimpleAuth/config.json")); if(sender != null) ((PlayerEntity) sender).sendMessage(globalPasswordSet, false); else - LOGGER.info(SimpleAuth.config.lang.globalPasswordSet); + LOGGER.info(config.lang.globalPasswordSet); + return 1; + } + + // + private static int setSpawn(ServerCommandSource source, int dimensionId, double x, double y, double z) { + config.worldSpawn.dimensionId = dimensionId; + config.worldSpawn.x = x; + config.worldSpawn.y = y; + config.worldSpawn.z = z; + Entity sender = source.getEntity(); + if(sender != null) + sender.sendSystemMessage(new LiteralText(config.lang.worldSpawnSet)); + else + LOGGER.info(config.lang.worldSpawnSet); return 1; } // Deleting (unregistering) user's account private static int removeAccount(ServerCommandSource source, String uuid) { Entity sender = source.getEntity(); - SimpleAuth.db.deleteUserData(uuid); - SimpleAuth.deauthenticatedUsers.put(uuid, new PlayerCache(uuid, "")); + db.deleteUserData(uuid); + SimpleAuth.deauthenticatedUsers.put(uuid, new PlayerCache(uuid, null)); if(sender != null) ((PlayerEntity) sender).sendMessage(userdataDeleted, false); else - LOGGER.info(SimpleAuth.config.lang.userdataDeleted); + LOGGER.info(config.lang.userdataDeleted); return 1; // Success } @@ -128,11 +169,11 @@ public class AuthCommand { String hash = AuthHelper.hashPass(password.toCharArray()); playerdata.addProperty("password", hash); - if(SimpleAuth.db.registerUser(uuid, playerdata.toString())) { + if(db.registerUser(uuid, playerdata.toString())) { if(sender != null) ((PlayerEntity) sender).sendMessage(userdataUpdated, false); else - LOGGER.info(SimpleAuth.config.lang.userdataUpdated); + LOGGER.info(config.lang.userdataUpdated); return 1; } return 0; @@ -148,11 +189,11 @@ public class AuthCommand { String hash = AuthHelper.hashPass(password.toCharArray()); playerdata.addProperty("password", hash); - SimpleAuth.db.updateUserData(uuid, playerdata.toString()); + db.updateUserData(uuid, playerdata.toString()); if(sender != null) ((PlayerEntity) sender).sendMessage(userdataUpdated, false); else - LOGGER.info(SimpleAuth.config.lang.userdataUpdated); + LOGGER.info(config.lang.userdataUpdated); return 1; } // todo PlayerEntity.getOfflinePlayerUuid("") diff --git a/src/main/java/org/samo_lego/simpleauth/event/AuthEventHandler.java b/src/main/java/org/samo_lego/simpleauth/event/AuthEventHandler.java index f61fc0b..6dbfc8e 100644 --- a/src/main/java/org/samo_lego/simpleauth/event/AuthEventHandler.java +++ b/src/main/java/org/samo_lego/simpleauth/event/AuthEventHandler.java @@ -71,16 +71,17 @@ public class AuthEventHandler { return null; } + // Player joining the server public static void onPlayerJoin(ServerPlayerEntity player) { // If player is fake auth is not needed - if(isPlayerFake(player)) + if (isPlayerFake(player)) return; // Checking if session is still valid String uuid = convertUuid(player); PlayerCache playerCache = deauthenticatedUsers.getOrDefault(uuid, null); - - if(playerCache != null) { + + if (playerCache != null) { if ( playerCache.wasAuthenticated && playerCache.validUntil >= System.currentTimeMillis() && @@ -89,11 +90,15 @@ public class AuthEventHandler { deauthenticatedUsers.remove(uuid); // Makes player authenticated return; } - // Session is invalid + // Invalidating session + playerCache.wasAuthenticated = false; } else deauthenticatePlayer(player); + if(config.main.spawnOnJoin) + teleportPlayer(player, true); + // Tries to rescue player from nether portal if(config.main.tryPortalRescue && player.getBlockState().getBlock().equals(Blocks.NETHER_PORTAL)) { boolean wasSuccessful = false; @@ -165,11 +170,7 @@ public class AuthEventHandler { } public static void onPlayerLeave(ServerPlayerEntity player) { - if( - !isAuthenticated(player) || - config.main.sessionTimeoutTime == -1 || - isPlayerFake(player) - ) + if(isPlayerFake(player) || !isAuthenticated(player) || config.main.sessionTimeoutTime == -1) return; // Starting session @@ -180,6 +181,7 @@ public class AuthEventHandler { PlayerCache playerCache = deauthenticatedUsers.get(convertUuid(player)); if(playerCache == null) return; + playerCache.wasAuthenticated = true; // Setting the session expire time playerCache.validUntil = System.currentTimeMillis() + config.main.sessionTimeoutTime * 1000; diff --git a/src/main/java/org/samo_lego/simpleauth/mixin/MixinServerPlayNetworkHandler.java b/src/main/java/org/samo_lego/simpleauth/mixin/MixinServerPlayNetworkHandler.java index 9426cec..06bd265 100644 --- a/src/main/java/org/samo_lego/simpleauth/mixin/MixinServerPlayNetworkHandler.java +++ b/src/main/java/org/samo_lego/simpleauth/mixin/MixinServerPlayNetworkHandler.java @@ -3,12 +3,14 @@ package org.samo_lego.simpleauth.mixin; import net.minecraft.network.packet.c2s.play.ChatMessageC2SPacket; import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket; import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.util.ActionResult; import org.samo_lego.simpleauth.event.entity.player.ChatCallback; import org.samo_lego.simpleauth.event.entity.player.PlayerMoveCallback; import org.samo_lego.simpleauth.event.item.TakeItemCallback; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -22,6 +24,10 @@ public abstract class MixinServerPlayNetworkHandler { @Shadow public ServerPlayerEntity player; + @Final + @Shadow + private MinecraftServer server; + @Inject( method = "onGameMessage(Lnet/minecraft/network/packet/c2s/play/ChatMessageC2SPacket;)V", at = @At( diff --git a/src/main/java/org/samo_lego/simpleauth/storage/AuthConfig.java b/src/main/java/org/samo_lego/simpleauth/storage/AuthConfig.java index 784c676..dc07531 100644 --- a/src/main/java/org/samo_lego/simpleauth/storage/AuthConfig.java +++ b/src/main/java/org/samo_lego/simpleauth/storage/AuthConfig.java @@ -40,7 +40,7 @@ public class AuthConfig { // Disables registering and forces logging in with global password // Visit https://github.com/samolego/SimpleAuth/wiki/Locking-server-with-global-password for more info public boolean enableGlobalPassword = false; - public String globalPassword = null; + public String globalPassword; // Tries to rescue players if they are stuck inside a portal on logging in // Visit https://github.com/samolego/SimpleAuth/wiki/Portal-Rescue for more info public boolean tryPortalRescue = true; @@ -54,6 +54,14 @@ public class AuthConfig { // Set to -1 to disable // Visit https://github.com/samolego/SimpleAuth/wiki/Sessions for more info public int sessionTimeoutTime = 60; + + public boolean spawnOnJoin = false; + public static class WorldSpawn { + public int dimensionId; + public double x; + public double y; + public double z; + } } public static class LangConfig { public String enterPassword = "§6You need to enter your password!"; @@ -83,6 +91,7 @@ public class AuthConfig { public String minPasswordChars = "§6Password needs to be at least %d characters long!"; public String disallowedUsername = "§6Invalid username characters! Allowed character regex: %s"; public String playerAlreadyOnline = "§cPlayer %s is already online!"; + public String worldSpawnSet = "§aSpawn for logging in was set successfully."; } public static class ExperimentalConfig { // Prevents player being kicked because another player with the same name has joined the server @@ -114,6 +123,7 @@ public class AuthConfig { .create(); public MainConfig main = new MainConfig(); + public MainConfig.WorldSpawn worldSpawn = new MainConfig.WorldSpawn(); public LangConfig lang = new LangConfig(); public ExperimentalConfig experimental = new ExperimentalConfig(); diff --git a/src/main/java/org/samo_lego/simpleauth/storage/PlayerCache.java b/src/main/java/org/samo_lego/simpleauth/storage/PlayerCache.java index ed98aa1..b15bf5f 100644 --- a/src/main/java/org/samo_lego/simpleauth/storage/PlayerCache.java +++ b/src/main/java/org/samo_lego/simpleauth/storage/PlayerCache.java @@ -1,8 +1,12 @@ package org.samo_lego.simpleauth.storage; +import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import net.minecraft.server.network.ServerPlayerEntity; +import static org.samo_lego.simpleauth.SimpleAuth.config; import static org.samo_lego.simpleauth.SimpleAuth.db; public class PlayerCache { @@ -12,22 +16,79 @@ public class PlayerCache { public int loginTries; public String lastIp; public long validUntil; - private static final JsonParser parser = new JsonParser(); + + public int lastDimId; + public double lastX; + public double lastY; + public double lastZ; + + private static final Gson gson = new Gson(); - public PlayerCache(String uuid, String ip) { - this.wasAuthenticated = false; - this.loginTries = 0; - this.lastIp = ip; + public PlayerCache(String uuid, ServerPlayerEntity player) { + if(db.isClosed()) + return; + + if(player != null) { + this.lastIp = player.getIp(); + + // Setting last coordinates + this.lastDimId = player.dimension.getRawId(); + this.lastX = player.getX(); + this.lastY = player.getY(); + this.lastZ = player.getZ(); + } + else { + this.lastIp = ""; + + // Setting last coordinates + this.lastDimId = config.worldSpawn.dimensionId; + this.lastX = config.worldSpawn.x; + this.lastY = config.worldSpawn.y; + this.lastZ = config.worldSpawn.z; + } if(db.isUserRegistered(uuid)) { - this.isRegistered = true; - JsonObject json = parser.parse(db.getData(uuid)).getAsJsonObject(); + String data = db.getData(uuid); + + // Getting (hashed) password + JsonObject json = gson.fromJson(data, JsonObject.class); this.password = json.get("password").getAsString(); + + // If coordinates are same as the one from world spawn + // we should check the DB for saved coords + if(config.main.spawnOnJoin) { + try { + JsonElement lastLoc = json.get("lastLocation"); + if ( + lastLoc != null && + this.lastDimId == config.worldSpawn.dimensionId && + this.lastX == config.worldSpawn.x && + this.lastY == config.worldSpawn.y && + this.lastZ == config.worldSpawn.z + ) { + // Getting DB coords + JsonObject lastLocation = gson.fromJson(lastLoc.getAsString(), JsonObject.class); + this.lastDimId = lastLocation.get("dimId").getAsInt(); + this.lastX = lastLocation.get("x").getAsDouble(); + this.lastY = lastLocation.get("y").getAsDouble(); + this.lastZ = lastLocation.get("z").getAsDouble(); + + // Removing location data from DB + json.remove("lastLocation"); + db.updateUserData(uuid, json.toString()); + } + } catch (JsonSyntaxException ignored) { + // Player didn't have any coords in db to tp to + } + } + this.isRegistered = true; } else { this.isRegistered = false; this.password = ""; } + this.wasAuthenticated = false; + this.loginTries = 0; } } diff --git a/src/main/java/org/samo_lego/simpleauth/storage/SimpleAuthDatabase.java b/src/main/java/org/samo_lego/simpleauth/storage/SimpleAuthDatabase.java index 514265f..94b746a 100644 --- a/src/main/java/org/samo_lego/simpleauth/storage/SimpleAuthDatabase.java +++ b/src/main/java/org/samo_lego/simpleauth/storage/SimpleAuthDatabase.java @@ -15,7 +15,7 @@ import static org.iq80.leveldb.impl.Iq80DBFactory.factory; public class SimpleAuthDatabase { private static final Logger LOGGER = LogManager.getLogger(); - private DB levelDBStore; + public DB levelDBStore; // Connects to the DB public void openConnection() { @@ -79,9 +79,9 @@ public class SimpleAuthDatabase { } // Updates the password of the user - public void updateUserData(String uuid, String password) { + public void updateUserData(String uuid, String data) { try { - levelDBStore.put(bytes("UUID:" + uuid),bytes("data:" + password)); + levelDBStore.put(bytes("UUID:" + uuid), bytes("data:" + data)); } catch (Error e) { LOGGER.error("[SimpleAuth] " + e.getMessage()); }