Merge branch 'fabric' into mongodb

# Conflicts:
#	src/main/java/org/samo_lego/simpleauth/SimpleAuth.java
#	src/main/java/org/samo_lego/simpleauth/storage/AuthConfig.java
#	src/main/java/org/samo_lego/simpleauth/storage/PlayerCache.java
This commit is contained in:
samo_lego 2020-10-16 22:45:51 +02:00
commit ae8cdd38fd
14 changed files with 348 additions and 299 deletions

View File

@ -63,5 +63,5 @@ jobs:
# The path to the asset you want to upload
asset_path: build/libs/${{ env.BUILDNAME }}.jar
# The name of the asset you want to upload
asset_name: ${{ env.BUILDNAME }}
asset_name: ${{ env.PROJECT_ID }}_fabric-${{ env.VERSION }}-${{ env.MC_VERSION }}.jar
asset_content_type: application/zip

View File

@ -10,7 +10,7 @@ loader_version=0.9.3+build.207
fabric_version=0.20.2+build.402-1.16
# Mod Properties
mod_version = 1.5.3
mod_version = 1.6.0
maven_group = org.samo_lego
archives_base_name = simpleauth

View File

@ -5,7 +5,6 @@ import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.player.*;
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;
@ -30,17 +29,16 @@ import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static org.iq80.leveldb.impl.Iq80DBFactory.bytes;
import static org.samo_lego.simpleauth.utils.CarpetHelper.isPlayerCarpetFake;
import static org.samo_lego.simpleauth.commands.AuthCommand.reloadConfig;
import static org.samo_lego.simpleauth.event.AuthEventHandler.*;
import static org.samo_lego.simpleauth.utils.SimpleLogger.logError;
import static org.samo_lego.simpleauth.utils.SimpleLogger.logInfo;
import static org.samo_lego.simpleauth.utils.UuidConverter.convertUuid;
public class SimpleAuth implements DedicatedServerModInitializer {
@ -48,27 +46,13 @@ public class SimpleAuth implements DedicatedServerModInitializer {
public static final ExecutorService THREADPOOL = Executors.newCachedThreadPool();
private static final Timer TIMER = new Timer();
/**
* HashMap of players that have joined the server.
* It's cleared on server stop in order to save some interactions with database during runtime.
* Stores their data as {@link org.samo_lego.simpleauth.storage.PlayerCache PlayerCache} object
* Stores their data as {@link org.samo_lego.simpleauth.storage.PlayerCache PlayerCache} object.
*/
public static HashMap<String, PlayerCache> playerCacheMap = new HashMap<>();
/**
* Checks whether player is authenticated.
* Fake players always count as authenticated.
*
* @param player player that needs to be checked
* @return false if player is not authenticated, otherwise true
*/
public static boolean isAuthenticated(ServerPlayerEntity player) {
String uuid = convertUuid(player);
return playerCacheMap.containsKey(uuid) && playerCacheMap.get(uuid).isAuthenticated;
}
// Getting game directory
public static final Path gameDirectory = FabricLoader.getInstance().getGameDir();
@ -139,8 +123,6 @@ public class SimpleAuth implements DedicatedServerModInitializer {
// Closing threads
try {
THREADPOOL.shutdownNow();
TIMER.cancel();
TIMER.purge();
if (!THREADPOOL.awaitTermination(100, TimeUnit.MICROSECONDS)) {
Thread.currentThread().interrupt();
}
@ -152,156 +134,4 @@ public class SimpleAuth implements DedicatedServerModInitializer {
// Closing DB connection
DB.close();
}
/**
* Gets the text which tells the player
* to login or register, depending on account status.
*
* @param player player who will get the message
* @return LiteralText with appropriate string (login or register)
*/
public static LiteralText notAuthenticated(PlayerEntity player) {
final PlayerCache cache = playerCacheMap.get(convertUuid(player));
if(SimpleAuth.config.main.enableGlobalPassword || cache.isRegistered)
return new LiteralText(
SimpleAuth.config.lang.notAuthenticated + "\n" + SimpleAuth.config.lang.loginRequired
);
return new LiteralText(
SimpleAuth.config.lang.notAuthenticated+ "\n" + SimpleAuth.config.lang.registerRequired
);
}
/**
* Authenticates player and sends the success message.
*
* @param player player that needs to be authenticated
* @param msg message to be send to the player
*/
public static void authenticatePlayer(ServerPlayerEntity player, Text msg) {
PlayerCache playerCache = playerCacheMap.get(convertUuid(player));
// Teleporting player back
if(config.main.spawnOnJoin)
teleportPlayer(player, false);
// Updating blocks if needed (if portal rescue action happened)
if(playerCache.wasInPortal) {
World world = player.getEntityWorld();
BlockPos pos = player.getBlockPos();
// Sending updates to portal blocks
// This is technically not needed, but it cleans the "messed portal" on the client
world.updateListeners(pos, world.getBlockState(pos), world.getBlockState(pos), 3);
world.updateListeners(pos.up(), world.getBlockState(pos.up()), world.getBlockState(pos.up()), 3);
}
// Setting last air to player
if(player.isSubmergedInWater())
player.setAir(playerCache.lastAir);
// In case player is in lava during authentication proccess
if(!playerCache.wasOnFire)
player.setFireTicks(0);
playerCache.isAuthenticated = true;
// Player no longer needs to be invisible and invulnerable
player.setInvulnerable(false);
player.setInvisible(false);
player.sendMessage(msg, false);
}
/**
* De-authenticates the player.
*
* @param player player that needs to be de-authenticated
*/
public static void deauthenticatePlayer(ServerPlayerEntity player) {
if(DB.isClosed())
return;
// Marking player as not authenticated
String uuid = convertUuid(player);
playerCacheMap.put(uuid, PlayerCache.fromJson(player, DB.getUserData(uuid)));
playerCacheMap.get(uuid).isAuthenticated = false;
// Teleporting player to spawn to hide its position
if(config.main.spawnOnJoin)
teleportPlayer(player, true);
// Player is now not authenticated
player.sendMessage(notAuthenticated(player), false);
// Setting the player to be invisible to mobs and also invulnerable
player.setInvulnerable(SimpleAuth.config.experimental.playerInvulnerable);
player.setInvisible(SimpleAuth.config.experimental.playerInvisible);
// Kicks player after a certain amount of time if not authenticated
TIMER.schedule(new TimerTask() {
@Override
public void run() {
// Kicking player if not authenticated
if(!isAuthenticated(player) && player.networkHandler.getConnection().isOpen())
player.networkHandler.disconnect(new LiteralText(SimpleAuth.config.lang.timeExpired));
}
}, SimpleAuth.config.main.kickTime * 1000);
}
/**
* Checks whether player is a fake player (from CarpetMod).
*
* @param player player that needs to be checked
* @return true if player is fake, otherwise false
*/
public static boolean isPlayerFake(PlayerEntity player) {
// We ask CarpetHelper class since it has the imports needed
return FabricLoader.getInstance().isModLoaded("carpet") && isPlayerCarpetFake(player);
}
/**
* Teleports player to spawn or last location that is recorded.
* Last location means the location before de-authentication.
*
* @param player player that needs to be teleported
* @param toSpawn whether to teleport player to spawn (provided in config) or last recorded position
*/
public static void teleportPlayer(ServerPlayerEntity player, boolean toSpawn) {
MinecraftServer server = player.getServer();
if(server == null || config.worldSpawn.dimension == null)
return;
if (toSpawn) {
// Teleports player to spawn
player.teleport(
server.getWorld(RegistryKey.of(Registry.DIMENSION, new Identifier(config.worldSpawn.dimension))),
config.worldSpawn.x,
config.worldSpawn.y,
config.worldSpawn.z,
90,
90
);
return;
}
PlayerCache cache = playerCacheMap.get(convertUuid(player));
// Puts player to last cached position
try {
player.teleport(
server.getWorld(RegistryKey.of(Registry.DIMENSION, new Identifier(cache.lastLocation.lastDim))),
cache.lastLocation.lastX,
cache.lastLocation.lastY,
cache.lastLocation.lastZ,
cache.lastLocation.lastYaw,
cache.lastLocation.lastPitch
);
} catch (Error e) {
player.sendMessage(new LiteralText(config.lang.corruptedPlayerData), false);
logError("Couldn't teleport player " + player.getName().asString());
logError(
String.format("Last recorded position is X: %s, Y: %s, Z: %s in dimension %s",
cache.lastLocation.lastX,
cache.lastLocation.lastY,
cache.lastLocation.lastZ,
cache.lastLocation.lastDim
));
}
}
}

View File

@ -5,15 +5,14 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.LiteralText;
import org.samo_lego.simpleauth.SimpleAuth;
import org.samo_lego.simpleauth.utils.AuthHelper;
import org.samo_lego.simpleauth.utils.PlayerAuth;
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.*;
import static org.samo_lego.simpleauth.utils.UuidConverter.convertUuid;
public class AccountCommand {
@ -36,7 +35,7 @@ public class AccountCommand {
)
)
)
.then(literal("changePassword")
.then(literal("changePassword") //todo mongodb update
.then(argument("old password", word())
.executes(ctx -> {
ctx.getSource().getPlayer().sendMessage(
@ -72,13 +71,10 @@ public class AccountCommand {
// Different thread to avoid lag spikes
THREADPOOL.submit(() -> {
if (AuthHelper.checkPassword(convertUuid(player), pass.toCharArray()) == 1) {
DB.deleteUserData(convertUuid(player));
player.sendMessage(
new LiteralText(config.lang.accountDeleted),
false
);
SimpleAuth.deauthenticatePlayer(player);
if (AuthHelper.checkPassword(((PlayerAuth) player).getFakeUuid(), pass.toCharArray()) == 1) {
DB.deleteUserData(((PlayerAuth) player).getFakeUuid());
player.sendMessage(new LiteralText(config.lang.accountDeleted), false);
((PlayerAuth) player).setAuthenticated(false);
return;
}
player.sendMessage(
@ -103,7 +99,7 @@ public class AccountCommand {
}
// Different thread to avoid lag spikes
THREADPOOL.submit(() -> {
if (AuthHelper.checkPassword(convertUuid(player), oldPass.toCharArray()) == 1) {
if (AuthHelper.checkPassword(((PlayerAuth) player).getFakeUuid(), oldPass.toCharArray()) == 1) {
if (newPass.length() < config.main.minPasswordChars) {
player.sendMessage(new LiteralText(
String.format(config.lang.minPasswordChars, config.main.minPasswordChars)
@ -117,7 +113,7 @@ public class AccountCommand {
return;
}
// Changing password in playercache
playerCacheMap.get(convertUuid(player)).password = AuthHelper.hashPassword(newPass.toCharArray());
playerCacheMap.get(((PlayerAuth) player).getFakeUuid()).password = AuthHelper.hashPassword(newPass.toCharArray());
player.sendMessage(
new LiteralText(config.lang.passwordUpdated),
false

View File

@ -6,13 +6,13 @@ import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.LiteralText;
import org.samo_lego.simpleauth.utils.AuthHelper;
import org.samo_lego.simpleauth.utils.PlayerAuth;
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.*;
import static org.samo_lego.simpleauth.utils.UuidConverter.convertUuid;
public class LoginCommand {
@ -32,8 +32,8 @@ public class LoginCommand {
private static int login(ServerCommandSource source, String pass) throws CommandSyntaxException {
// Getting the player who send the command
ServerPlayerEntity player = source.getPlayer();
String uuid = convertUuid(player);
if (isAuthenticated(player)) {
String uuid = ((PlayerAuth) player).getFakeUuid();
if (((PlayerAuth) player).isAuthenticated()) {
player.sendMessage(new LiteralText(config.lang.alreadyAuthenticated), false);
return 0;
}
@ -47,7 +47,8 @@ public class LoginCommand {
return;
}
else if(passwordResult == 1) {
authenticatePlayer(player, new LiteralText(config.lang.successfullyAuthenticated));
player.sendMessage(new LiteralText(config.lang.successfullyAuthenticated), false);
((PlayerAuth) player).setAuthenticated(true);
return;
}
else if(passwordResult == -1) {

View File

@ -5,11 +5,10 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.LiteralText;
import org.samo_lego.simpleauth.storage.PlayerCache;
import org.samo_lego.simpleauth.utils.PlayerAuth;
import static net.minecraft.server.command.CommandManager.literal;
import static org.samo_lego.simpleauth.SimpleAuth.*;
import static org.samo_lego.simpleauth.utils.UuidConverter.convertUuid;
import static org.samo_lego.simpleauth.SimpleAuth.config;
public class LogoutCommand {
@ -22,15 +21,8 @@ public class LogoutCommand {
private static int logout(ServerCommandSource serverCommandSource) throws CommandSyntaxException {
ServerPlayerEntity player = serverCommandSource.getPlayer();
PlayerCache playerCache = playerCacheMap.get(convertUuid(player));
playerCache.lastLocation.lastDim = String.valueOf(player.getEntityWorld().getRegistryKey().getValue());
playerCache.lastLocation.lastX = player.getX();
playerCache.lastLocation.lastY = player.getY();
playerCache.lastLocation.lastZ = player.getZ();
playerCache.lastLocation.lastYaw = player.yaw;
playerCache.lastLocation.lastPitch = player.pitch;
deauthenticatePlayer(player);
((PlayerAuth) player).setAuthenticated(false);
player.sendMessage(new LiteralText(config.lang.successfulLogout), false);
return 1;
}

View File

@ -6,6 +6,7 @@ import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.LiteralText;
import org.samo_lego.simpleauth.storage.PlayerCache;
import org.samo_lego.simpleauth.utils.PlayerAuth;
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import static com.mojang.brigadier.arguments.StringArgumentType.word;
@ -13,7 +14,6 @@ import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
import static org.samo_lego.simpleauth.SimpleAuth.*;
import static org.samo_lego.simpleauth.utils.AuthHelper.hashPassword;
import static org.samo_lego.simpleauth.utils.UuidConverter.convertUuid;
public class RegisterCommand {
@ -39,7 +39,7 @@ public class RegisterCommand {
player.sendMessage(new LiteralText(config.lang.loginRequired), false);
return 0;
}
else if(isAuthenticated(player)) {
else if(((PlayerAuth) player).isAuthenticated()) {
player.sendMessage(new LiteralText(config.lang.alreadyAuthenticated), false);
return 0;
}
@ -62,9 +62,11 @@ public class RegisterCommand {
return;
}
PlayerCache playerCache = playerCacheMap.get(convertUuid(player));
PlayerCache playerCache = playerCacheMap.get(((PlayerAuth) player).getFakeUuid());
if (!playerCache.isRegistered) {
authenticatePlayer(player, new LiteralText(config.lang.registerSuccess));
((PlayerAuth) player).setAuthenticated(true);
player.sendMessage(new LiteralText(config.lang.registerSuccess), false);
playerCache.password = hashPassword(pass1.toCharArray());
playerCache.isRegistered = true;
return;

View File

@ -13,13 +13,14 @@ import net.minecraft.util.ActionResult;
import net.minecraft.util.TypedActionResult;
import net.minecraft.util.math.BlockPos;
import org.samo_lego.simpleauth.storage.PlayerCache;
import org.samo_lego.simpleauth.utils.PlayerAuth;
import java.net.SocketAddress;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.samo_lego.simpleauth.SimpleAuth.*;
import static org.samo_lego.simpleauth.utils.UuidConverter.convertUuid;
import static org.samo_lego.simpleauth.SimpleAuth.config;
import static org.samo_lego.simpleauth.SimpleAuth.playerCacheMap;
/**
* This class will take care of actions players try to do,
@ -40,7 +41,7 @@ public class AuthEventHandler {
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(incomingPlayerUsername);
if((onlinePlayer != null && !isPlayerFake(onlinePlayer)) && config.experimental.preventAnotherLocationKick) {
if((onlinePlayer != null && !((PlayerAuth) onlinePlayer).canSkipAuth()) && config.experimental.preventAnotherLocationKick) {
// Player needs to be kicked, since there's already a player with that name
// playing on the server
return new LiteralText(
@ -63,10 +64,10 @@ public class AuthEventHandler {
// Player joining the server
public static void onPlayerJoin(ServerPlayerEntity player) {
// If player is fake auth is not needed
if (isPlayerFake(player))
if (((PlayerAuth) player).canSkipAuth())
return;
// Checking if session is still valid
String uuid = convertUuid(player);
String uuid = ((PlayerAuth) player).getFakeUuid();
PlayerCache playerCache = playerCacheMap.getOrDefault(uuid, null);
if (playerCache != null) {
@ -75,7 +76,7 @@ public class AuthEventHandler {
playerCache.validUntil >= System.currentTimeMillis() &&
player.getIp().equals(playerCache.lastIp)
) {
authenticatePlayer(player, null); // Makes player authenticated
((PlayerAuth) player).setAuthenticated(true);
return;
}
player.setInvulnerable(config.experimental.playerInvulnerable);
@ -83,17 +84,15 @@ public class AuthEventHandler {
// Invalidating session
playerCache.isAuthenticated = false;
player.sendMessage(notAuthenticated(player), false);
if(config.main.spawnOnJoin)
((PlayerAuth) player).hidePosition(true);
}
else {
deauthenticatePlayer(player);
((PlayerAuth) player).setAuthenticated(false);
playerCache = playerCacheMap.get(uuid);
playerCache.wasOnFire = false;
}
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)) {
@ -112,27 +111,24 @@ public class AuthEventHandler {
}
public static void onPlayerLeave(ServerPlayerEntity player) {
if(isPlayerFake(player))
if(((PlayerAuth) player).canSkipAuth())
return;
String uuid = convertUuid(player);
String uuid = ((PlayerAuth) player).getFakeUuid();
PlayerCache playerCache = playerCacheMap.get(uuid);
playerCache.lastIp = player.getIp();
playerCache.lastAir = player.getAir();
playerCache.wasOnFire = player.isOnFire();
playerCache.wasInPortal = player.getBlockState().getBlock().equals(Blocks.NETHER_PORTAL);
if(isAuthenticated(player)) {
playerCache.lastLocation.lastDim = String.valueOf(player.getEntityWorld().getRegistryKey().getValue());
playerCache.lastLocation.lastX = player.getX();
playerCache.lastLocation.lastY = player.getY();
playerCache.lastLocation.lastZ = player.getZ();
playerCache.lastLocation.lastYaw = player.yaw;
playerCache.lastLocation.lastPitch = player.pitch;
if(((PlayerAuth) player).isAuthenticated()) {
playerCache.lastIp = player.getIp();
playerCache.lastAir = player.getAir();
playerCache.wasOnFire = player.isOnFire();
playerCache.wasInPortal = player.getBlockState().getBlock().equals(Blocks.NETHER_PORTAL);
// Setting the session expire time
if(config.main.sessionTimeoutTime != -1)
playerCache.validUntil = System.currentTimeMillis() + config.main.sessionTimeoutTime * 1000;
}
else {
((PlayerAuth) player).hidePosition(false);
}
}
// Player chatting
@ -140,12 +136,12 @@ public class AuthEventHandler {
// Getting the message to then be able to check it
String msg = chatMessageC2SPacket.getChatMessage();
if(
!isAuthenticated((ServerPlayerEntity) player) &&
!((PlayerAuth) player).isAuthenticated() &&
!msg.startsWith("/login") &&
!msg.startsWith("/register") &&
(!config.experimental.allowChat || msg.startsWith("/"))
) {
player.sendMessage(notAuthenticated(player), false);
player.sendMessage(((PlayerAuth) player).getAuthMessage(), false);
return ActionResult.FAIL;
}
return ActionResult.PASS;
@ -154,7 +150,7 @@ public class AuthEventHandler {
// Player movement
public static ActionResult onPlayerMove(PlayerEntity player) {
// Player will fall if enabled (prevent fly kick)
boolean auth = isAuthenticated((ServerPlayerEntity) player);
boolean auth = ((PlayerAuth) player).isAuthenticated();
if(!auth && config.main.allowFalling && !player.isOnGround() && !player.isInsideWaterOrBubbleColumn()) {
if(player.isInvulnerable())
player.setInvulnerable(false);
@ -171,8 +167,8 @@ public class AuthEventHandler {
// Using a block (right-click function)
public static ActionResult onUseBlock(PlayerEntity player) {
if(!isAuthenticated((ServerPlayerEntity) player) && !config.experimental.allowBlockUse) {
player.sendMessage(notAuthenticated(player), false);
if(!((PlayerAuth) player).isAuthenticated() && !config.experimental.allowBlockUse) {
player.sendMessage(((PlayerAuth) player).getAuthMessage(), false);
return ActionResult.FAIL;
}
return ActionResult.PASS;
@ -180,8 +176,8 @@ public class AuthEventHandler {
// Breaking a block
public static boolean onBreakBlock(PlayerEntity player) {
if(!isAuthenticated((ServerPlayerEntity) player) && !config.experimental.allowBlockPunch) {
player.sendMessage(notAuthenticated(player), false);
if(!((PlayerAuth) player).isAuthenticated() && !config.experimental.allowBlockPunch) {
player.sendMessage(((PlayerAuth) player).getAuthMessage(), false);
return false;
}
return true;
@ -189,8 +185,8 @@ public class AuthEventHandler {
// Using an item
public static TypedActionResult<ItemStack> onUseItem(PlayerEntity player) {
if(!isAuthenticated((ServerPlayerEntity) player) && !config.experimental.allowItemUse) {
player.sendMessage(notAuthenticated(player), false);
if(!((PlayerAuth) player).isAuthenticated() && !config.experimental.allowItemUse) {
player.sendMessage(((PlayerAuth) player).getAuthMessage(), false);
return TypedActionResult.fail(ItemStack.EMPTY);
}
@ -198,16 +194,16 @@ public class AuthEventHandler {
}
// Dropping an item
public static ActionResult onDropItem(PlayerEntity player) {
if(!isAuthenticated((ServerPlayerEntity) player) && !config.experimental.allowItemDrop) {
player.sendMessage(notAuthenticated(player), false);
if(!((PlayerAuth) player).isAuthenticated() && !config.experimental.allowItemDrop) {
player.sendMessage(((PlayerAuth) player).getAuthMessage(), false);
return ActionResult.FAIL;
}
return ActionResult.PASS;
}
// Changing inventory (item moving etc.)
public static ActionResult onTakeItem(PlayerEntity player) {
if(!isAuthenticated((ServerPlayerEntity) player) && !config.experimental.allowItemMoving) {
player.sendMessage(notAuthenticated(player), false);
if(!((PlayerAuth) player).isAuthenticated() && !config.experimental.allowItemMoving) {
player.sendMessage(((PlayerAuth) player).getAuthMessage(), false);
return ActionResult.FAIL;
}
@ -215,8 +211,8 @@ public class AuthEventHandler {
}
// Attacking an entity
public static ActionResult onAttackEntity(PlayerEntity player) {
if(!isAuthenticated((ServerPlayerEntity) player) && !config.experimental.allowEntityPunch) {
player.sendMessage(notAuthenticated(player), false);
if(!((PlayerAuth) player).isAuthenticated() && !config.experimental.allowEntityPunch) {
player.sendMessage(((PlayerAuth) player).getAuthMessage(), false);
return ActionResult.FAIL;
}
@ -224,8 +220,8 @@ public class AuthEventHandler {
}
// Interacting with entity
public static ActionResult onUseEntity(PlayerEntity player) {
if(!isAuthenticated((ServerPlayerEntity) player) && !config.main.allowEntityInteract) {
player.sendMessage(notAuthenticated(player), false);
if(!((PlayerAuth) player).isAuthenticated() && !config.main.allowEntityInteract) {
player.sendMessage(((PlayerAuth) player).getAuthMessage(), false);
return ActionResult.FAIL;
}

View File

@ -1,17 +1,216 @@
package org.samo_lego.simpleauth.mixin;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.LiteralText;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.World;
import org.samo_lego.simpleauth.SimpleAuth;
import org.samo_lego.simpleauth.event.item.DropItemCallback;
import org.samo_lego.simpleauth.storage.PlayerCache;
import org.samo_lego.simpleauth.utils.PlayerAuth;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import static org.samo_lego.simpleauth.SimpleAuth.config;
import static org.samo_lego.simpleauth.SimpleAuth.playerCacheMap;
import static org.samo_lego.simpleauth.utils.CarpetHelper.isPlayerCarpetFake;
@Mixin(PlayerEntity.class)
public abstract class MixinPlayerEntity {
public abstract class MixinPlayerEntity implements PlayerAuth {
private final ServerPlayerEntity player = (ServerPlayerEntity) (Object) this;
// * 20 for 20 ticks in second
private int kickTimer = config.main.kickTime * 20;
private final boolean isRunningCarpet = FabricLoader.getInstance().isModLoaded("carpet");
private final MinecraftServer server = player.getServer();
/**
* Teleports player to spawn or last location that is recorded.
* Last location means the location before de-authentication.
*
* @param hide whether to teleport player to spawn (provided in config) or last recorded position
*/
@Override
public void hidePosition(boolean hide) {
assert server != null;
PlayerCache cache = playerCacheMap.get(this.getFakeUuid());
System.out.println("Teleporting Player. hide:" + hide);
if (hide) {
// Saving position
cache.lastLocation.dimension = player.getServerWorld();
cache.lastLocation.position = player.getPos();
cache.lastLocation.yaw = player.yaw;
cache.lastLocation.pitch = player.pitch;
// Teleports player to spawn
player.teleport(
server.getWorld(RegistryKey.of(Registry.DIMENSION, new Identifier(config.worldSpawn.dimension))),
config.worldSpawn.x,
config.worldSpawn.y,
config.worldSpawn.z,
config.worldSpawn.yaw,
config.worldSpawn.pitch
);
return;
}
// Puts player to last cached position
player.teleport(
cache.lastLocation.dimension,
cache.lastLocation.position.getX(),
cache.lastLocation.position.getY(),
cache.lastLocation.position.getZ(),
cache.lastLocation.yaw,
cache.lastLocation.pitch
);
}
/**
* Converts player uuid, to ensure player with "nAmE" and "NamE" get same uuid.
* Both players are not allowed to play, since mod mimics Mojang behaviour.
* of not allowing accounts with same names but different capitalization.
*
* @return converted UUID as string
*/
@Override
public String getFakeUuid() {
// If server is in online mode online-mode UUIDs should be used
assert server != null;
if(server.isOnlineMode())
return player.getUuidAsString();
/*
Lower case is used for Player and PlAyEr to get same UUID (for password storing)
Mimicking Mojang behaviour, where players cannot set their name to
ExAmple if Example is already taken.
*/
String playername = player.getName().asString().toLowerCase();
return PlayerEntity.getOfflinePlayerUuid(playername).toString();
}
/**
* Sets the authentication status of the player.
*
* @param authenticated whether player should be authenticated
*/
@Override
public void setAuthenticated(boolean authenticated) {
PlayerCache playerCache;
if(!playerCacheMap.containsKey(this.getFakeUuid())) {
// First join
playerCache = new PlayerCache(this.getFakeUuid(), player);
playerCacheMap.put(this.getFakeUuid(), playerCache);
}
else {
playerCache = playerCacheMap.get(this.getFakeUuid());
if(this.isAuthenticated() == authenticated)
return;
playerCache.isAuthenticated = authenticated;
}
player.setInvulnerable(!authenticated && config.experimental.playerInvulnerable);
player.setInvisible(!authenticated && config.experimental.playerInvisible);
// Teleporting player (hiding / restoring position)
if(config.main.spawnOnJoin)
this.hidePosition(!authenticated);
if(authenticated) {
kickTimer = config.main.kickTime * 20;
// Updating blocks if needed (if portal rescue action happened)
if(playerCache.wasInPortal) {
World world = player.getEntityWorld();
BlockPos pos = player.getBlockPos();
// Sending updates to portal blocks
// This is technically not needed, but it cleans the "messed portal" on the client
world.updateListeners(pos, world.getBlockState(pos), world.getBlockState(pos), 3);
world.updateListeners(pos.up(), world.getBlockState(pos.up()), world.getBlockState(pos.up()), 3);
}
// Setting last air to player
if(player.isSubmergedInWater())
player.setAir(playerCache.lastAir);
// In case player is in lava during authentication proccess
if(!playerCache.wasOnFire)
player.setFireTicks(0);
}
}
/**
* Gets the text which tells the player
* to login or register, depending on account status.
*
* @return LiteralText with appropriate string (login or register)
*/
@Override
public Text getAuthMessage() {
final PlayerCache cache = playerCacheMap.get(((PlayerAuth) player).getFakeUuid());
if(SimpleAuth.config.main.enableGlobalPassword || cache.isRegistered)
return new LiteralText(
SimpleAuth.config.lang.notAuthenticated + "\n" + SimpleAuth.config.lang.loginRequired
);
return new LiteralText(
SimpleAuth.config.lang.notAuthenticated+ "\n" + SimpleAuth.config.lang.registerRequired
);
}
/**
* Checks whether player is a fake player (from CarpetMod).
*
* @return true if player is fake (can skip authentication process), otherwise false
*/
@Override
public boolean canSkipAuth() {
// We ask CarpetHelper class since it has the imports needed
return this.isRunningCarpet && isPlayerCarpetFake(this.player);
}
/**
* Checks whether player is authenticated.
*
* @return false if player is not authenticated, otherwise true.
*/
@Override
public boolean isAuthenticated() {
String uuid = ((PlayerAuth) player).getFakeUuid();
return playerCacheMap.containsKey(uuid) && playerCacheMap.get(uuid).isAuthenticated;
}
@Inject(method = "tick()V", at = @At("HEAD"), cancellable = true)
private void tick(CallbackInfo ci) {
if(!this.isAuthenticated()) {
// Checking player timer
if(kickTimer <= 0 && player.networkHandler.getConnection().isOpen()) {
player.networkHandler.disconnect(new LiteralText(config.lang.timeExpired));
}
else {
// Sending authentication prompt every 10 seconds
if(kickTimer % 200 == 0)
player.sendMessage(this.getAuthMessage(), false);
kickTimer--;
}
ci.cancel();
}
}
// Player item dropping
@Inject(method = "dropSelectedItem(Z)Z", at = @At("HEAD"), cancellable = true)
private void dropSelectedItem(boolean dropEntireStack, CallbackInfoReturnable<Boolean> cir) {

View File

@ -25,7 +25,7 @@ public abstract class MixinPlayerManager {
PlayerJoinServerCallback.EVENT.invoker().onPlayerJoin(serverPlayerEntity);
}
@Inject(method = "remove(Lnet/minecraft/server/network/ServerPlayerEntity;)V", at = @At("RETURN"))
@Inject(method = "remove(Lnet/minecraft/server/network/ServerPlayerEntity;)V", at = @At("HEAD"))
private void onPlayerLeave(ServerPlayerEntity serverPlayerEntity, CallbackInfo ci) {
PlayerLeaveServerCallback.EVENT.invoker().onPlayerLeave(serverPlayerEntity);
}

View File

@ -86,12 +86,12 @@ public class AuthConfig {
public boolean allowFalling = false;
/**
* Whether to tp player to spawn when joining (to hide coordinates).
* Whether to tp player to spawn when joining (to hide original player coordinates).
*/
public boolean spawnOnJoin = false;
/**
* Data for spawn (where deauthenticated players are teleported).
* Data for spawn (where deauthenticated players are teleported temporarily).
* @see <a href="https://github.com/samolego/SimpleAuth/wiki/Coordinate-Hiding" target="_blank">wiki</a>
*/
public static class WorldSpawn {

View File

@ -4,6 +4,14 @@ import com.google.gson.Gson;
import com.google.gson.JsonObject;
import net.minecraft.block.Blocks;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.LiteralText;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey;
import java.util.Objects;
import java.util.Objects;
@ -52,12 +60,10 @@ public class PlayerCache {
* Last recorded position before de-authentication.
*/
public static class LastLocation {
public String lastDim;
public double lastX;
public double lastY;
public double lastZ;
public float lastYaw;
public float lastPitch;
public ServerWorld dimension;
public Vec3d position;
public float yaw;
public float pitch;
}
public PlayerCache.LastLocation lastLocation = new PlayerCache.LastLocation();
@ -79,13 +85,12 @@ public class PlayerCache {
this.lastAir = player.getAir();
// Setting position cache
this.lastLocation.lastDim = String.valueOf(player.getEntityWorld().getRegistryKey().getValue());
this.lastLocation.dimension = player.getServerWorld();
this.lastLocation.position = player.getPos();
this.lastLocation.yaw = player.yaw;
this.lastLocation.pitch = player.pitch;
this.wasInPortal = player.getBlockState().getBlock().equals(Blocks.NETHER_PORTAL);
this.lastLocation.lastX = player.getX();
this.lastLocation.lastY = player.getY();
this.lastLocation.lastZ = player.getZ();
this.lastLocation.lastYaw = player.yaw;
this.lastLocation.lastPitch = player.pitch;
}
else {
this.wasOnFire = false;

View File

@ -0,0 +1,61 @@
package org.samo_lego.simpleauth.utils;
import net.minecraft.text.Text;
/**
* PLayer authentication extension.
*/
public interface PlayerAuth {
/**
* Teleports player to spawn or last location that is recorded.
* Last location means the location before de-authentication.
*
* @param hide whether to teleport player to spawn (provided in config) or last recorded position
* @see <a href="https://samolego.github.io/SimpleAuth/org/samo_lego/simpleauth/mixin/MixinPlayerEntity.html">See implementation</a>
*/
void hidePosition(boolean hide);
/**
* Converts player uuid, to ensure player with "nAmE" and "NamE" get same uuid.
* Both players are not allowed to play, since mod mimics Mojang behaviour.
* of not allowing accounts with same names but different capitalization.
*
* @return converted UUID as string
* @see <a href="https://samolego.github.io/SimpleAuth/org/samo_lego/simpleauth/mixin/MixinPlayerEntity.html">See implementation</a>
*/
String getFakeUuid();
/**
* Sets the authentication status of the player.
*
* @param authenticated whether player should be authenticated
* @see <a href="https://samolego.github.io/SimpleAuth/org/samo_lego/simpleauth/mixin/MixinPlayerEntity.html">See implementation</a>
*/
void setAuthenticated(boolean authenticated);
/**
* Checks whether player is authenticated.
*
* @return false if player is not authenticated, otherwise true.
* @see <a href="https://samolego.github.io/SimpleAuth/org/samo_lego/simpleauth/mixin/MixinPlayerEntity.html">See implementation</a>
*/
boolean isAuthenticated();
/**
* Gets the text which tells the player
* to login or register, depending on account status.
*
* @return LiteralText with appropriate string (login or register)
* @see <a href="https://samolego.github.io/SimpleAuth/org/samo_lego/simpleauth/mixin/MixinPlayerEntity.html">See implementation</a>
*/
Text getAuthMessage();
/**
* Checks whether player is a fake player (from CarpetMod).
*
* @return true if player is fake (can skip authentication process), otherwise false
* @see <a href="https://samolego.github.io/SimpleAuth/org/samo_lego/simpleauth/mixin/MixinPlayerEntity.html">See implementation</a>
*/
boolean canSkipAuth();
}

View File

@ -1,33 +0,0 @@
package org.samo_lego.simpleauth.utils;
import net.minecraft.entity.player.PlayerEntity;
import static org.samo_lego.simpleauth.SimpleAuth.serverProp;
/**
* Converts player uuid, to ensure player with "nAmE" and "NamE" get same uuid
* Both players are not allowed to play, since mod mimics Mojang behaviour
* of not allowing accounts with same names but different capitalization
*/
public class UuidConverter {
private static final boolean isOnline = Boolean.parseBoolean(serverProp.getProperty("online-mode"));
/** Gets player UUID.
*
* @param player player to get UUID for
* @return converted UUID as string
*/
public static String convertUuid(PlayerEntity player) {
// If server is in online mode online-mode UUIDs should be used
if(isOnline)
return player.getUuidAsString();
/*
Lower case is used for Player and PlAyEr to get same UUID (for password storing)
Mimicking Mojang behaviour, where players cannot set their name to
ExAmple if Example is already taken.
*/
String playername = player.getName().asString().toLowerCase();
return PlayerEntity.getOfflinePlayerUuid(playername).toString();
}
}