diff --git a/src/main/java/eu/oskar3123/spigot2fa/Main.java b/src/main/java/eu/oskar3123/spigot2fa/Main.java index 9a41d6d..a345b56 100644 --- a/src/main/java/eu/oskar3123/spigot2fa/Main.java +++ b/src/main/java/eu/oskar3123/spigot2fa/Main.java @@ -4,7 +4,8 @@ import eu.oskar3123.spigot2fa.command.TFACommand; import eu.oskar3123.spigot2fa.config.Config; import eu.oskar3123.spigot2fa.config.ConfigHandler; import eu.oskar3123.spigot2fa.handler.TFAHandler; -import eu.oskar3123.spigot2fa.listener.JoinQuitListener; +import eu.oskar3123.spigot2fa.listener.AuthListener; +import eu.oskar3123.spigot2fa.listener.MapListener; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; @@ -41,7 +42,8 @@ public class Main extends JavaPlugin private void registerListeners() { PluginManager pm = this.getServer().getPluginManager(); - pm.registerEvents(new JoinQuitListener(this), this); + pm.registerEvents(new AuthListener(this), this); + pm.registerEvents(new MapListener(this), this); } } diff --git a/src/main/java/eu/oskar3123/spigot2fa/command/TFACommand.java b/src/main/java/eu/oskar3123/spigot2fa/command/TFACommand.java index d957441..cd30a72 100644 --- a/src/main/java/eu/oskar3123/spigot2fa/command/TFACommand.java +++ b/src/main/java/eu/oskar3123/spigot2fa/command/TFACommand.java @@ -2,18 +2,28 @@ package eu.oskar3123.spigot2fa.command; import eu.oskar3123.spigot2fa.Main; import eu.oskar3123.spigot2fa.handler.TFAHandler; -import eu.oskar3123.spigot2fa.tfa.TFA; +import eu.oskar3123.spigot2fa.util.PlayerUtils; import org.apache.commons.lang.StringUtils; +import org.bukkit.OfflinePlayer; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; import org.bukkit.entity.Player; +import org.bukkit.util.StringUtil; -public class TFACommand implements CommandExecutor +import java.util.*; + +public class TFACommand implements CommandExecutor, TabCompleter { private Main plugin; private TFAHandler th; + private final Map TAB_COMPLETE_MAP = new HashMap<>(); + { + TAB_COMPLETE_MAP.put("", new String[]{"add", "remove", ""}); + TAB_COMPLETE_MAP.put(":remove", new String[]{"[player]"}); + } public TFACommand(Main plugin) { @@ -25,29 +35,123 @@ public class TFACommand implements CommandExecutor public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { Player player = (Player) sender; - if (!th.isInProcess(player.getUniqueId())) + if (args.length >= 1 && args[0].equalsIgnoreCase("remove")) { - th.startCreating(player); - return true; - } - else - { - String secret = th.getKey(player.getUniqueId()); - String code = TFA.getTOTPCode(secret); - String pCode = StringUtils.join(args); - if (th.matchCode(code, pCode)) + if (!player.hasPermission("2fa.remove")) { - th.creatingSuccess(player); - player.sendMessage("Successfully activated 2FA"); + player.sendMessage("You don't have permission to do that"); + return true; + } + if (args.length >= 2 && player.hasPermission("2fa.remove.other")) + { + OfflinePlayer otherPlayer = PlayerUtils.getOfflinePlayer(args[1]); + if (otherPlayer == null) + { + player.sendMessage("Could not find player \"" + args[1] + "\"."); + return true; + } + if (th.hasEnabled2FA(otherPlayer.getUniqueId())) + { + th.remove2FA(otherPlayer.getUniqueId()); + player.sendMessage("Removed two-factor authentication for " + otherPlayer.getName() + "."); + return true; + } + else + { + player.sendMessage(otherPlayer.getName() + " does not have two-factor authentication enabled."); + return true; + } + } + if (th.hasEnabled2FA(player.getUniqueId())) + { + th.remove2FA(player.getUniqueId()); + player.sendMessage("Removed two-factor authentication."); return true; } else { - th.creatingFailed(player); - player.sendMessage("That code is incorrect, aborting"); + player.sendMessage("You don't have two-factor authentication enabled."); + return true; + } + } + else if (args.length >= 1 && args[0].equalsIgnoreCase("add")) + { + if (!player.hasPermission("2fa.activate")) + { + player.sendMessage("You don't have permission to do that"); + return true; + } + if (th.hasEnabled2FA(player.getUniqueId())) + { + player.sendMessage("You have already enabled two-factor authentication."); + return true; + } + if (!th.isInProcess(player.getUniqueId())) + { + boolean canStart = th.startCreating(player); + if (!canStart) + { + player.sendMessage("You can't hold anything when starting the process."); + return true; + } + player.sendMessage("Scan the barcode or enter the secret manually in your 2FA app."); + player.sendMessage("Use /2fa to verify the authenticator."); + return true; + } + else + { + String secret = th.getKey(player.getUniqueId()); + String pCode = StringUtils.join(args); + if (th.matchCode(secret, pCode)) + { + th.creatingSuccess(player); + player.sendMessage("Successfully activated two-factor authentication."); + return true; + } + else + { + th.creatingFailed(player); + player.sendMessage("That code is incorrect, aborting."); + return true; + } + } + } + else + { + if (th.isInProcess(player.getUniqueId())) + { + player.sendMessage("Confirm the activation by typing /2fa ."); + return true; + } + else + { + player.sendMessage("Usage: " + command.getUsage().replace("", label)); return true; } } } + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) + { + if (args.length < 1) + { + return new ArrayList<>(); + } + StringBuilder search = new StringBuilder(); + for (int i = 0; i < args.length - 1; i++) + { + search.append(":").append(args[i]); + } + if (!TAB_COMPLETE_MAP.containsKey(search.toString())) + { + return new ArrayList<>(); + } + String[] commands = TAB_COMPLETE_MAP.get(search.toString()); + final List completions = new ArrayList<>(); + StringUtil.copyPartialMatches(args[args.length - 1], Arrays.asList(commands), completions); + Collections.sort(completions); + return completions; + } + } diff --git a/src/main/java/eu/oskar3123/spigot2fa/config/ConfigHandler.java b/src/main/java/eu/oskar3123/spigot2fa/config/ConfigHandler.java index 1458389..52b4f7f 100644 --- a/src/main/java/eu/oskar3123/spigot2fa/config/ConfigHandler.java +++ b/src/main/java/eu/oskar3123/spigot2fa/config/ConfigHandler.java @@ -1,6 +1,7 @@ package eu.oskar3123.spigot2fa.config; import com.google.common.base.Charsets; +import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.java.JavaPlugin; @@ -55,18 +56,40 @@ public class ConfigHandler } public void saveConfig(Config config) + { + saveConfig(config, false); + } + + public void saveConfig(Config config, boolean async) { if (config.fileConfig == null || config.file == null) { return; } - try + if (async) { - getConfig(config).save(config.file); + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> + { + try + { + getConfig(config).save(config.file); + } + catch (IOException e) + { + plugin.getLogger().log(Level.SEVERE, "Could not save config to " + config.file, e); + } + }); } - catch (IOException ex) + else { - plugin.getLogger().log(Level.SEVERE, "Could not save config to " + config.file, ex); + try + { + getConfig(config).save(config.file); + } + catch (IOException e) + { + plugin.getLogger().log(Level.SEVERE, "Could not save config to " + config.file, e); + } } } @@ -80,6 +103,7 @@ public class ConfigHandler { plugin.saveResource(config.name + ".yml", false); } + reloadConfig(config); } } diff --git a/src/main/java/eu/oskar3123/spigot2fa/handler/TFAHandler.java b/src/main/java/eu/oskar3123/spigot2fa/handler/TFAHandler.java index 2aee8aa..957d20c 100644 --- a/src/main/java/eu/oskar3123/spigot2fa/handler/TFAHandler.java +++ b/src/main/java/eu/oskar3123/spigot2fa/handler/TFAHandler.java @@ -12,6 +12,7 @@ import org.bukkit.inventory.meta.MapMeta; import org.bukkit.map.MapView; import java.io.IOException; +import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -27,21 +28,67 @@ public class TFAHandler this.plugin = plugin; } + public boolean shouldBypassCode(Player player) + { + UUID uuid = player.getUniqueId(); + String ip = player.getAddress().getAddress().getHostAddress(); + if (!hasEnabled2FA(uuid)) + { + return true; + } + long now = new Date().getTime(); + long sessionTimeInMs = plugin.configHandler.getConfig(plugin.config).getLong("2fa.sessiontime") * 60000L; + Configuration players = plugin.configHandler.getConfig(plugin.players); + long last = players.getLong(uuid.toString() + ".lastlogin"); + String lastIp = players.getString(uuid.toString() + ".lastip"); + return last >= now - sessionTimeInMs && ip.equalsIgnoreCase(lastIp); + } + + public void setLastLogin(Player player, Long time) + { + UUID uuid = player.getUniqueId(); + String ip = player.getAddress().getAddress().getHostAddress(); + if (!hasEnabled2FA(uuid)) + { + return; + } + long now; + if (time == null) + { + now = new Date().getTime(); + } + else + { + now = time; + } + Configuration players = plugin.configHandler.getConfig(plugin.players); + players.set(uuid.toString() + ".lastlogin", now); + players.set(uuid.toString() + ".lastip", ip); + plugin.configHandler.saveConfig(plugin.players, true); + } + public String formatSecret(String secret) { return secret.toLowerCase().replaceAll("(.{4})(?=.{4})", "$1 "); } + public void remove2FA(UUID uuid) + { + plugin.configHandler.getConfig(plugin.players).set(uuid.toString(), null); + plugin.configHandler.saveConfig(plugin.players, true); + } + public boolean hasEnabled2FA(UUID uuid) { - return plugin.configHandler.getConfig(plugin.players).isString(uuid.toString()); + return plugin.configHandler.getConfig(plugin.players).isString(uuid.toString() + ".secret"); } public void creatingSuccess(Player player) { String secret = remove(player.getUniqueId()); - plugin.configHandler.getConfig(plugin.players).set(player.getUniqueId().toString(), secret); - plugin.configHandler.saveConfig(plugin.players); + player.getInventory().setItemInMainHand(new ItemStack(Material.AIR)); + plugin.configHandler.getConfig(plugin.players).set(player.getUniqueId().toString() + ".secret", secret); + plugin.configHandler.saveConfig(plugin.players, true); } public void creatingFailed(Player player) @@ -49,15 +96,22 @@ public class TFAHandler remove(player.getUniqueId()); } - public boolean matchCode(String code1, String code2) + public boolean matchCode(String secret, String code) { - return code1.equalsIgnoreCase(code2); + String[] actualCodes = TFA.getTOTPCodes(secret); + for (String actualCode : actualCodes) + { + if (actualCode.equalsIgnoreCase(code)) + { + return true; + } + } + return false; } public boolean matchCode(Player player, String code) { - String actualCode = TFA.getTOTPCode(getKey(player.getUniqueId())); - return matchCode(actualCode, code); + return matchCode(getKey(player.getUniqueId()), code); } private String remove(UUID uuid) @@ -65,33 +119,42 @@ public class TFAHandler return isInProcess.remove(uuid); } - public String startCreating(Player player) + public boolean startCreating(Player player) { String key = TFA.getRandomSecretKey(); - isInProcess.put(player.getUniqueId(), key); - showQRCode(player, key); - return key; + boolean couldShow = showQRCode(player, key); + if (couldShow) + { + isInProcess.put(player.getUniqueId(), key); + } + return couldShow; } - private void showQRCode(Player player, String secret) + private boolean showQRCode(Player player, String secret) { + ItemStack inHand = player.getInventory().getItemInMainHand(); + if (inHand != null && inHand.getType() != Material.AIR) + { + return false; + } try { ItemStack map = new ItemStack(Material.FILLED_MAP); MapView view = Bukkit.createMap(player.getWorld()); view.getRenderers().clear(); - view.addRenderer(new QRMapRenderer(secret, player)); + view.addRenderer(new QRMapRenderer(plugin, formatSecret(secret), player)); MapMeta mapMeta = (MapMeta) map.getItemMeta(); mapMeta.setMapId(view.getId()); map.setItemMeta(mapMeta); player.getInventory().setItemInMainHand(map); player.sendMap(view); - player.sendMessage("Secret Key: " + secret); + return true; } catch (IOException e) { e.printStackTrace(); player.sendMessage("Failed to generate qr code"); + return false; } } @@ -107,7 +170,16 @@ public class TFAHandler return isInProcess.get(uuid); } Configuration players = plugin.configHandler.getConfig(plugin.players); - return players.getString(uuid.toString()); + return players.getString(uuid.toString() + ".secret"); + } + + public void stopCreating(Player player) + { + if (!isInProcess(player.getUniqueId())) + { + return; + } + isInProcess.remove(player.getUniqueId()); } } diff --git a/src/main/java/eu/oskar3123/spigot2fa/listener/AuthListener.java b/src/main/java/eu/oskar3123/spigot2fa/listener/AuthListener.java new file mode 100644 index 0000000..04eaecc --- /dev/null +++ b/src/main/java/eu/oskar3123/spigot2fa/listener/AuthListener.java @@ -0,0 +1,105 @@ +package eu.oskar3123.spigot2fa.listener; + +import eu.oskar3123.spigot2fa.Main; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitTask; + +import java.util.*; + +public class AuthListener implements Listener +{ + + private class WaitingInfo + { + private BukkitTask kickTask; + private BukkitTask teleportTask; + + private WaitingInfo(BukkitTask kickTask, BukkitTask teleportTask) + { + this.kickTask = kickTask; + this.teleportTask = teleportTask; + } + + private void cancel(Player player) + { + this.kickTask.cancel(); + this.teleportTask.cancel(); + player.removePotionEffect(PotionEffectType.BLINDNESS); + } + } + + private Main plugin; + private Map waitingForCode = new HashMap<>(); + + public AuthListener(Main plugin) + { + this.plugin = plugin; + } + + @EventHandler + public void onJoin(PlayerJoinEvent event) + { + if (plugin.tfaHandler.shouldBypassCode(event.getPlayer())) + { + event.getPlayer().sendMessage("bypassed 2fa check"); + return; + } + final Player player = event.getPlayer(); + BukkitTask task = Bukkit.getScheduler().runTaskLater(plugin, () -> + { + waitingForCode.remove(player.getUniqueId()); + player.kickPlayer("You did not enter the 2FA code in time."); + }, 800L); + // 800 ticks == 40 secs + final Location loginLocation = player.getLocation().clone(); + BukkitTask tpTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> + { + player.teleport(loginLocation); + player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 45, 0, false, false), true); + }, 0L, 20L); + waitingForCode.put(player.getUniqueId(), new WaitingInfo(task, tpTask)); + Bukkit.getScheduler().runTaskLater(plugin, () -> player.sendMessage("Please enter your 2FA code in the chat."), 1L); + } + + @EventHandler + public void chat(AsyncPlayerChatEvent event) + { + if (!waitingForCode.containsKey(event.getPlayer().getUniqueId())) + { + return; + } + boolean correct = plugin.tfaHandler.matchCode(event.getPlayer(), event.getMessage()); + event.setCancelled(true); + if (!correct) + { + event.getPlayer().sendMessage("Incorrect code, try again."); + } + else + { + waitingForCode.remove(event.getPlayer().getUniqueId()).cancel(event.getPlayer()); + plugin.tfaHandler.setLastLogin(event.getPlayer(), null); + event.getPlayer().sendMessage("You have been authorized."); + } + } + + @EventHandler + public void command(PlayerCommandPreprocessEvent event) + { + if (!waitingForCode.containsKey(event.getPlayer().getUniqueId())) + { + return; + } + event.setCancelled(true); + event.getPlayer().sendMessage("You have to authorize first."); + } + +} diff --git a/src/main/java/eu/oskar3123/spigot2fa/listener/JoinQuitListener.java b/src/main/java/eu/oskar3123/spigot2fa/listener/JoinQuitListener.java deleted file mode 100644 index 71d97fb..0000000 --- a/src/main/java/eu/oskar3123/spigot2fa/listener/JoinQuitListener.java +++ /dev/null @@ -1,59 +0,0 @@ -package eu.oskar3123.spigot2fa.listener; - -import eu.oskar3123.spigot2fa.Main; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerJoinEvent; - -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -public class JoinQuitListener implements Listener -{ - - private Main plugin; - private Set waitingForCode = new HashSet<>(); - - public JoinQuitListener(Main plugin) - { - this.plugin = plugin; - } - - @EventHandler - public void onJoin(PlayerJoinEvent event) - { - boolean enabled = plugin.tfaHandler.hasEnabled2FA(event.getPlayer().getUniqueId()); - if (!enabled) - { - return; - } - event.getPlayer().sendMessage("Enter 2FA code"); - waitingForCode.add(event.getPlayer().getUniqueId()); - } - - @EventHandler - public void chat(AsyncPlayerChatEvent event) - { - if (!waitingForCode.contains(event.getPlayer().getUniqueId())) - { - return; - } - boolean correct = plugin.tfaHandler.matchCode(event.getPlayer(), event.getMessage()); - waitingForCode.remove(event.getPlayer().getUniqueId()); - event.setCancelled(true); - if (!correct) - { - final Player player = event.getPlayer(); - Bukkit.getScheduler().runTask(plugin, () -> player.kickPlayer("Wrong code!")); - } - else - { - event.getPlayer().sendMessage("Logged in"); - } - } - -} diff --git a/src/main/java/eu/oskar3123/spigot2fa/listener/MapListener.java b/src/main/java/eu/oskar3123/spigot2fa/listener/MapListener.java new file mode 100644 index 0000000..f767eea --- /dev/null +++ b/src/main/java/eu/oskar3123/spigot2fa/listener/MapListener.java @@ -0,0 +1,100 @@ +package eu.oskar3123.spigot2fa.listener; + +import eu.oskar3123.spigot2fa.Main; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.*; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; + +public class MapListener implements Listener +{ + + private Main plugin; + + public MapListener(Main plugin) + { + this.plugin = plugin; + } + + @EventHandler + public void quit(PlayerQuitEvent event) + { + if (!plugin.tfaHandler.isInProcess(event.getPlayer().getUniqueId())) + { + return; + } + plugin.tfaHandler.stopCreating(event.getPlayer()); + event.getPlayer().getInventory().setItemInMainHand(new ItemStack(Material.AIR)); + } + + @EventHandler + public void drop(PlayerDropItemEvent event) + { + if (!plugin.tfaHandler.isInProcess(event.getPlayer().getUniqueId())) + { + return; + } + if (event.getItemDrop().getItemStack().getType() != Material.FILLED_MAP) + { + return; + } + plugin.tfaHandler.stopCreating(event.getPlayer()); + event.getPlayer().sendMessage("Aborted adding two-factor authentication."); + event.getItemDrop().remove(); + } + + @EventHandler + public void slot(PlayerItemHeldEvent event) + { + if (!plugin.tfaHandler.isInProcess(event.getPlayer().getUniqueId())) + { + return; + } + event.setCancelled(true); + } + + @EventHandler + public void open(InventoryOpenEvent event) + { + if (!plugin.tfaHandler.isInProcess(event.getPlayer().getUniqueId())) + { + return; + } + event.setCancelled(true); + } + + @EventHandler + public void open(InventoryClickEvent event) + { + if (!plugin.tfaHandler.isInProcess(event.getWhoClicked().getUniqueId())) + { + return; + } + event.setCancelled(true); + } + + @EventHandler + public void open(InventoryCreativeEvent event) + { + if (!plugin.tfaHandler.isInProcess(event.getWhoClicked().getUniqueId())) + { + return; + } + event.setCancelled(true); + } + + @EventHandler + public void open(InventoryDragEvent event) + { + if (!plugin.tfaHandler.isInProcess(event.getWhoClicked().getUniqueId())) + { + return; + } + event.setCancelled(true); + } + +} diff --git a/src/main/java/eu/oskar3123/spigot2fa/map/CreateMapRunnable.java b/src/main/java/eu/oskar3123/spigot2fa/map/CreateMapRunnable.java deleted file mode 100644 index f7e74e7..0000000 --- a/src/main/java/eu/oskar3123/spigot2fa/map/CreateMapRunnable.java +++ /dev/null @@ -1,47 +0,0 @@ -package eu.oskar3123.spigot2fa.map; - -import eu.oskar3123.spigot2fa.tfa.TFA; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.MapMeta; -import org.bukkit.map.MapView; - -import java.io.IOException; - -public class CreateMapRunnable implements Runnable -{ - - private Player player; - - public CreateMapRunnable(Player player) - { - this.player = player; - } - - @Override - public void run() - { - try - { - ItemStack map = new ItemStack(Material.FILLED_MAP); - MapView view = Bukkit.createMap(player.getWorld()); - view.getRenderers().clear(); - String secret = TFA.getRandomSecretKey(); - view.addRenderer(new QRMapRenderer(secret, player)); - MapMeta mapMeta = (MapMeta) map.getItemMeta(); - mapMeta.setMapId(view.getId()); - map.setItemMeta(mapMeta); - player.getInventory().setItemInMainHand(map); - player.sendMap(view); - player.sendMessage("Secret Key: " + secret); - } - catch (IOException e) - { - e.printStackTrace(); - player.sendMessage("Failed to generate qr code"); - } - } - -} diff --git a/src/main/java/eu/oskar3123/spigot2fa/map/QRMapRenderer.java b/src/main/java/eu/oskar3123/spigot2fa/map/QRMapRenderer.java index 80ae6ce..f50b390 100644 --- a/src/main/java/eu/oskar3123/spigot2fa/map/QRMapRenderer.java +++ b/src/main/java/eu/oskar3123/spigot2fa/map/QRMapRenderer.java @@ -25,10 +25,14 @@ public class QRMapRenderer extends MapRenderer { this.plugin = plugin; this.uuid = player.getUniqueId(); - String[] formattedSecretKey = secretKey .replaceAll("((?:.{4} ){4})", "$1;").split(";"); + String[] formattedSecretKey = secretKey.replaceAll("((?:.{4} ){4})", "$1;").split(";"); this.line1 = formattedSecretKey[0].trim(); this.line2 = formattedSecretKey[1].trim(); - initImage(TFA.getGoogleAuthenticatorBarCode(secretKey, "Mineworlds", player.getName() + " (" + player.getUniqueId().toString() + ")")); + String issuer = plugin.configHandler.getConfig(plugin.config).getString("2fa.issuer"); + String account = plugin.configHandler.getConfig(plugin.config).getString("2fa.account") + .replace("{NAME}", player.getName()) + .replace("{UUID}", player.getUniqueId().toString()); + initImage(TFA.getGoogleAuthenticatorBarCode(secretKey, issuer, account)); } private void initImage(String qrValue) throws IOException diff --git a/src/main/java/eu/oskar3123/spigot2fa/tfa/TFA.java b/src/main/java/eu/oskar3123/spigot2fa/tfa/TFA.java index 3d57211..eeed56b 100644 --- a/src/main/java/eu/oskar3123/spigot2fa/tfa/TFA.java +++ b/src/main/java/eu/oskar3123/spigot2fa/tfa/TFA.java @@ -22,7 +22,7 @@ public class TFA return secretKey.toUpperCase(); } - public static String getTOTPCode(String secretKey) + public static String[] getTOTPCodes(String secretKey) { String normalizedBase32Key = secretKey.replace(" ", "").toUpperCase(); Base32 base32 = new Base32(); @@ -30,7 +30,11 @@ public class TFA String hexKey = Hex.encodeHexString(bytes); long time = (System.currentTimeMillis() / 1000) / 30; String hexTime = Long.toHexString(time); - return TOTP.generateTOTP(hexKey, hexTime, "6"); + String code1 = TOTP.generateTOTP(hexKey, hexTime, "6"); + time = time - 1; + hexTime = Long.toHexString(time); + String code2 = TOTP.generateTOTP(hexKey, hexTime, "6"); + return new String[] {code1, code2}; } public static String getGoogleAuthenticatorBarCode(String secretKey, String issuer, String account) diff --git a/src/main/java/eu/oskar3123/spigot2fa/util/PlayerUtils.java b/src/main/java/eu/oskar3123/spigot2fa/util/PlayerUtils.java new file mode 100644 index 0000000..d549a7b --- /dev/null +++ b/src/main/java/eu/oskar3123/spigot2fa/util/PlayerUtils.java @@ -0,0 +1,25 @@ +package eu.oskar3123.spigot2fa.util; + +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; + +public class PlayerUtils +{ + + public static OfflinePlayer getOfflinePlayer(String name) + { + for (OfflinePlayer ply : Bukkit.getOfflinePlayers()) + { + if (ply == null || ply.getName() == null || ply.getName().isEmpty()) + { + continue; + } + if (ply.getName().toLowerCase().startsWith(name.toLowerCase())) + { + return ply; + } + } + return null; + } + +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index b8dc8a9..fa3136c 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,3 +1,5 @@ 2fa: issuer: 'Mineworlds' account: '{NAME}' + # session time in minutes + sessiontime: 1440 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 1b6c206..b84944d 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,3 +5,5 @@ authors: [oskar3123] main: eu.oskar3123.spigot2fa.Main commands: 2fa: + usage: '/ add/remove/' + description: 'Two-factor authorization command'