diff --git a/src/com/redstoner/misc/Main.java b/src/com/redstoner/misc/Main.java index a630548..2c3bd38 100644 --- a/src/com/redstoner/misc/Main.java +++ b/src/com/redstoner/misc/Main.java @@ -12,6 +12,7 @@ import com.redstoner.modules.check.Check; import com.redstoner.modules.damnspam.DamnSpam; import com.redstoner.modules.imout.Imout; import com.redstoner.modules.lagchunks.LagChunks; +import com.redstoner.modules.loginsecurity.LoginSecurity; import com.redstoner.modules.scriptutils.Scriptutils; import com.redstoner.modules.skullclick.SkullClick; import com.redstoner.modules.warn.Warn; @@ -47,7 +48,7 @@ public class Main extends JavaPlugin // TODO: ModuleLoader.addModule(Imbusy.class); ModuleLoader.addModule(Imout.class); ModuleLoader.addModule(LagChunks.class); - // TODO: ModuleLoader.addModule(Loginsecurity.class); + ModuleLoader.addModule(LoginSecurity.class); // TODO: ModuleLoader.addModule(Mentio.class); // TODO: ModuleLoader.addModule(Misc.class); ModuleLoader.addModule(Motd.class); diff --git a/src/com/redstoner/modules/loginsecurity/CancelledEventsHandler.java b/src/com/redstoner/modules/loginsecurity/CancelledEventsHandler.java new file mode 100644 index 0000000..e39d781 --- /dev/null +++ b/src/com/redstoner/modules/loginsecurity/CancelledEventsHandler.java @@ -0,0 +1,95 @@ +package com.redstoner.modules.loginsecurity; + +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerPickupArrowEvent; +import org.bukkit.event.player.PlayerPickupItemEvent; + +public class CancelledEventsHandler implements Listener { + private LoginSecurity mainClass; + + public CancelledEventsHandler(LoginSecurity mainClass) { + this.mainClass = mainClass; + } + + @EventHandler + public void onMove(PlayerMoveEvent e) { + if (isLoggingIn(e.getPlayer())) { + e.getPlayer().teleport(LoginSecurity.loggingIn.get(e.getPlayer().getUniqueId())); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onChat(AsyncPlayerChatEvent e) { + if (isLoggingIn(e.getPlayer())) { + e.getPlayer().sendMessage(ChatColor.RED + "You must login before you can chat!"); + e.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onCommand(PlayerCommandPreprocessEvent e) { + String command = e.getMessage(); + + if (!command.startsWith("/login") && isLoggingIn(e.getPlayer())) { + e.getPlayer().sendMessage(ChatColor.RED + "You must login before you can execute commands!"); + e.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onItemHold(PlayerItemHeldEvent e) { + if (isLoggingIn(e.getPlayer())) { + e.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onItemPickup(PlayerPickupItemEvent e) { + if (isLoggingIn(e.getPlayer())) { + e.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onItemDrop(PlayerDropItemEvent e) { + if (isLoggingIn(e.getPlayer())) { + e.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onInteract(PlayerInteractEvent e) { + if (isLoggingIn(e.getPlayer())) { + e.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onArrowPickup(PlayerPickupArrowEvent e) { + if (isLoggingIn(e.getPlayer())) { + e.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onInvClick(InventoryClickEvent e) { + if (e.getWhoClicked() instanceof Player && isLoggingIn((Player) e.getWhoClicked())) { + e.setCancelled(true); + } + } + + private boolean isLoggingIn(Player player) { + return mainClass.isLoggingIn(player); + } +} diff --git a/src/com/redstoner/modules/loginsecurity/CryptographyHandler.java b/src/com/redstoner/modules/loginsecurity/CryptographyHandler.java new file mode 100644 index 0000000..48e81a9 --- /dev/null +++ b/src/com/redstoner/modules/loginsecurity/CryptographyHandler.java @@ -0,0 +1,53 @@ +package com.redstoner.modules.loginsecurity; + +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Base64; + +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + +public class CryptographyHandler { + public static String hash(String password, String salt) { + String algorithm = "PBKDF2WithHmacSHA256"; + int derivedKeyLength = 256; + int iterations = 200000; + byte[] decodedSalt = Base64.getDecoder().decode(salt.getBytes()); + + KeySpec spec = new PBEKeySpec(password.toCharArray(), decodedSalt, iterations, derivedKeyLength); + + byte[] hashed = null; + + try { + SecretKeyFactory f = SecretKeyFactory.getInstance(algorithm); + + hashed = f.generateSecret(spec).getEncoded(); + } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { + e.printStackTrace(); + } + + return Base64.getEncoder().encodeToString(hashed).substring(0, 43); + } + + public static boolean verify(String password, String salt, String hash) { + return hash(password, salt).equals(hash); + } + + public static boolean verify(String password, String stored) { + String[] split = stored.split("\\$"); + + return verify(password, split[3], split[4]); + } + + public static String generateSalt() throws NoSuchAlgorithmException, NoSuchProviderException { + SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); + + byte[] salt = new byte[16]; + random.nextBytes(salt); + + return Base64.getEncoder().encodeToString(salt).substring(0, 22); + } +} diff --git a/src/com/redstoner/modules/loginsecurity/LoginSecurity.java b/src/com/redstoner/modules/loginsecurity/LoginSecurity.java new file mode 100644 index 0000000..e8b6835 --- /dev/null +++ b/src/com/redstoner/modules/loginsecurity/LoginSecurity.java @@ -0,0 +1,324 @@ +package com.redstoner.modules.loginsecurity; + +import java.io.Serializable; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.scheduler.BukkitScheduler; + +import com.nemez.cmdmgr.Command; +import com.redstoner.misc.Main; +import com.redstoner.misc.mysql.JSONManager; +import com.redstoner.misc.mysql.MysqlHandler; +import com.redstoner.misc.mysql.elements.ConstraintOperator; +import com.redstoner.misc.mysql.elements.MysqlConstraint; +import com.redstoner.misc.mysql.elements.MysqlDatabase; +import com.redstoner.misc.mysql.elements.MysqlField; +import com.redstoner.misc.mysql.elements.MysqlTable; +import com.redstoner.misc.mysql.types.text.VarChar; +import com.redstoner.modules.Module; + +public class LoginSecurity implements Module, Listener +{ + private boolean enabled = false; + protected static Map loggingIn; + private MysqlTable table; + + @Override + public void onEnable() + { + Map config = JSONManager.getConfiguration("LoginSecurity.json"); + if (config == null || !config.containsKey("database") || !config.containsKey("table")) + { + Bukkit.getConsoleSender() + .sendMessage(ChatColor.RED + "Could not load the LoginSecurity config file, disabling!"); + enabled = false; + } + try + { + MysqlDatabase database = MysqlHandler.INSTANCE.getDatabase((String) config.get("database")); + MysqlField uuid = new MysqlField("uuid", new VarChar(36), true); + MysqlField pass = new MysqlField("pass", new VarChar(88), true); + database.createTableIfNotExists((String) config.get("table"), uuid, pass); + table = database.getTable((String) config.get("table")); + } + catch (NullPointerException e) + { + Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "Could not use the LoginSecurity config, disabling!"); + enabled = false; + } + loggingIn = new HashMap<>(); + Bukkit.getServer().getPluginManager().registerEvents(new CancelledEventsHandler(this), Main.plugin); + enabled = true; + } + + public static Map getLoggingIn() + { + return loggingIn; + } + + @Command(hook = "register") + public void register(CommandSender sender, String password) + { + Player player = (Player) sender; + if (isRegistered(player)) + { + player.sendMessage(ChatColor.GREEN + "You are already registered!"); + return; + } + try + { + if (registerPlayer(player, password)) + { + player.sendMessage(ChatColor.GREEN + "Succesfully registered!"); + return; + } + } + catch (NoSuchAlgorithmException | NoSuchProviderException e) + { + e.printStackTrace(); + } + player.sendMessage(ChatColor.RED + "Failed to register, please contact an admin!"); + } + + @Command(hook = "login") + public void login(CommandSender sender, String password) + { + Player player = (Player) sender; + if (!isRegistered(player)) + { + player.sendMessage(ChatColor.RED + "You are not registered!"); + return; + } + if (CryptographyHandler.verify(password, getHash(player))) + { + loggingIn.remove(player.getUniqueId()); + } + else + { + player.sendMessage(ChatColor.RED + "Wrong password!"); + } + } + + @Command(hook = "cgpass") + public void cgpass(CommandSender sender, String oldPassword, String newPassword) + { + Player player = (Player) sender; + if (!isRegistered(player)) + { + player.sendMessage(ChatColor.RED + "You are not registered!"); + return; + } + if (!CryptographyHandler.verify(oldPassword, getHash(player))) + { + player.sendMessage(ChatColor.RED + "The old password you entered is wrong!"); + return; + } + if (oldPassword.equals(newPassword)) + { + player.sendMessage(ChatColor.RED + "You entered the same password!"); + return; + } + if (table.delete(getUuidConstraint(player))) + { + try + { + registerPlayer(player, newPassword); + player.sendMessage(ChatColor.GREEN + "Succesfully changed password!"); + } + catch (NoSuchAlgorithmException | NoSuchProviderException e) + { + e.printStackTrace(); + player.sendMessage(ChatColor.RED + "Failed to set new password!"); + } + } + else + { + player.sendMessage(ChatColor.RED + "Failed to remove old password from database!"); + } + } + + @Command(hook = "rmpass") + public void rmpass(CommandSender sender, String oldPassword) + { + Player player = (Player) sender; + if (!isRegistered(player)) + { + player.sendMessage(ChatColor.RED + "You are not registered!"); + return; + } + if (!CryptographyHandler.verify(oldPassword, getHash(player))) + { + player.sendMessage(ChatColor.RED + "The old password you entered is wrong!"); + return; + } + if (table.delete(getUuidConstraint(player))) + { + player.sendMessage(ChatColor.GREEN + "Succesfully removed password!"); + } + else + { + player.sendMessage(ChatColor.RED + "Failed to remove old password from database!"); + } + } + + @Command(hook = "rmotherpass") + public void rmotherpass(CommandSender sender, String playerName) + { + if (playerName.equals("")) + { + sender.sendMessage(ChatColor.RED + "That's not a valid player!"); + return; + } + @SuppressWarnings("deprecation") + OfflinePlayer player = Bukkit.getOfflinePlayer(playerName); + if (!isRegistered(player)) + { + sender.sendMessage(ChatColor.RED + "That player is not registered!"); + return; + } + if (table.delete(getUuidConstraint(player))) + { + sender.sendMessage(ChatColor.GREEN + "Successfully removed " + playerName + "'s password!"); + } + else + { + sender.sendMessage(ChatColor.RED + "Failed to remove " + playerName + "'s password!"); + } + } + + @EventHandler + public void onJoin(PlayerJoinEvent e) + { + Player player = e.getPlayer(); + if (!isRegistered(player)) + { + return; + } + loggingIn.put(player.getUniqueId(), player.getLocation()); + BukkitScheduler scheduler = Bukkit.getScheduler(); + RepeatingLoginRunnable repeatingRunnable = new RepeatingLoginRunnable(this, player); + repeatingRunnable.setId(scheduler.scheduleSyncRepeatingTask(Main.plugin, repeatingRunnable, 0L, 2L)); + scheduler.scheduleSyncDelayedTask(Main.plugin, new Runnable() + { + @Override + public void run() + { + if (isLoggingIn(player)) + { + scheduler.cancelTask(repeatingRunnable.getId()); + player.kickPlayer("You didn't login in time!"); + } + } + }, 1200L); + } + + public boolean isLoggingIn(Player player) + { + return loggingIn.containsKey(player.getUniqueId()); + } + + public MysqlConstraint getUuidConstraint(OfflinePlayer player) + { + return new MysqlConstraint("uuid", ConstraintOperator.EQUAL, player.getUniqueId().toString()); + } + + public boolean isRegistered(OfflinePlayer player) + { + return table.get("uuid", getUuidConstraint(player)).length > 0; + } + + public String getHash(OfflinePlayer player) + { + return (String) table.get("pass", getUuidConstraint(player))[0]; + } + + public boolean registerPlayer(Player player, String password) + throws NoSuchAlgorithmException, NoSuchProviderException + { + String salt = CryptographyHandler.generateSalt(); + String hash = CryptographyHandler.hash(password, salt); + String toInsert = "$pbkdf2-sha256$200000$" + salt + "$" + hash; + return table.insert(player.getUniqueId().toString(), toInsert); + } + + @Override + public boolean enabled() + { + return enabled; + } + + @Override + public void onDisable() + { + enabled = false; + } + + // @noformat + @Override + public String getCommandString() + { + return "command register {\n" + + " perm utils.loginsecurity;\n" + + " \n" + + " [string:password] {\n" + + " run register password;\n" + + " help Protects your account with a password;\n" + + " type player;\n" + + " }\n" + + "}\n" + + "\n" + + "command login {\n" + + " perm utils.loginsecurity;\n" + + " \n" + + " [string:password] {\n" + + " run login password;\n" + + " help Logs you in;\n" + + " type player;\n" + + " }\n" + + "}\n" + + "\n" + + "command cgpass {\n" + + " perm utils.loginsecurity;\n" + + " \n" + + " [string:oldPassword] [string:newPassword] {\n" + + " run cgpass oldPassword newPassword;\n" + + " help Changes your password to the specified one;\n" + + " type player;\n" + + " }\n" + + "}\n" + + "\n" + + "command rmpass {\n" + + " perm utils.loginsecurity;\n" + + " \n" + + " [string:oldPassword] {\n" + + " run rmpass oldPassword;\n" + + " help Removes the password of your account;\n" + + " type player;\n" + + " }\n" + + "}\n" + + "\n" + + "command rmotherpass {\n" + + " perm utils.loginsecurity.admin;\n" + + " \n" + + " [string:playerName] {\n" + + " run rmotherpass playerName;\n" + + " help removes the password of another player;\n" + + " perm utils.loginsecurity.admin;\n" + + " }\n" + + "}"; + } + // @format +} diff --git a/src/com/redstoner/modules/loginsecurity/RepeatingLoginRunnable.java b/src/com/redstoner/modules/loginsecurity/RepeatingLoginRunnable.java new file mode 100644 index 0000000..4e8db6d --- /dev/null +++ b/src/com/redstoner/modules/loginsecurity/RepeatingLoginRunnable.java @@ -0,0 +1,37 @@ +package com.redstoner.modules.loginsecurity; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public class RepeatingLoginRunnable implements Runnable { + private int id = -1; + private Player player; + private LoginSecurity mainClass; + + public RepeatingLoginRunnable(LoginSecurity mainClass, Player player) { + this.player = player; + this.mainClass = mainClass; + } + + @Override + public void run() { + if (!player.isOnline()) { + LoginSecurity.loggingIn.remove(player.getUniqueId()); + Bukkit.getScheduler().cancelTask(id); + } + + if (!mainClass.isLoggingIn(player)) { + player.sendMessage(ChatColor.GREEN + "Successfully logged in!"); + Bukkit.getScheduler().cancelTask(id); + } + } + + public void setId(int id) { + this.id = id; + } + + public int getId() { + return id; + } +}