WIP
This commit is contained in:
@@ -4,7 +4,8 @@ import eu.oskar3123.spigot2fa.command.TFACommand;
|
|||||||
import eu.oskar3123.spigot2fa.config.Config;
|
import eu.oskar3123.spigot2fa.config.Config;
|
||||||
import eu.oskar3123.spigot2fa.config.ConfigHandler;
|
import eu.oskar3123.spigot2fa.config.ConfigHandler;
|
||||||
import eu.oskar3123.spigot2fa.handler.TFAHandler;
|
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.PluginManager;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
@@ -41,7 +42,8 @@ public class Main extends JavaPlugin
|
|||||||
private void registerListeners()
|
private void registerListeners()
|
||||||
{
|
{
|
||||||
PluginManager pm = this.getServer().getPluginManager();
|
PluginManager pm = this.getServer().getPluginManager();
|
||||||
pm.registerEvents(new JoinQuitListener(this), this);
|
pm.registerEvents(new AuthListener(this), this);
|
||||||
|
pm.registerEvents(new MapListener(this), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,28 @@ package eu.oskar3123.spigot2fa.command;
|
|||||||
|
|
||||||
import eu.oskar3123.spigot2fa.Main;
|
import eu.oskar3123.spigot2fa.Main;
|
||||||
import eu.oskar3123.spigot2fa.handler.TFAHandler;
|
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.apache.commons.lang.StringUtils;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
import org.bukkit.entity.Player;
|
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 Main plugin;
|
||||||
private TFAHandler th;
|
private TFAHandler th;
|
||||||
|
private final Map<String, String[]> TAB_COMPLETE_MAP = new HashMap<>();
|
||||||
|
{
|
||||||
|
TAB_COMPLETE_MAP.put("", new String[]{"add", "remove", "<code>"});
|
||||||
|
TAB_COMPLETE_MAP.put(":remove", new String[]{"[player]"});
|
||||||
|
}
|
||||||
|
|
||||||
public TFACommand(Main plugin)
|
public TFACommand(Main plugin)
|
||||||
{
|
{
|
||||||
@@ -25,29 +35,123 @@ public class TFACommand implements CommandExecutor
|
|||||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args)
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args)
|
||||||
{
|
{
|
||||||
Player player = (Player) sender;
|
Player player = (Player) sender;
|
||||||
if (!th.isInProcess(player.getUniqueId()))
|
if (args.length >= 1 && args[0].equalsIgnoreCase("remove"))
|
||||||
{
|
{
|
||||||
th.startCreating(player);
|
if (!player.hasPermission("2fa.remove"))
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
String secret = th.getKey(player.getUniqueId());
|
|
||||||
String code = TFA.getTOTPCode(secret);
|
|
||||||
String pCode = StringUtils.join(args);
|
|
||||||
if (th.matchCode(code, pCode))
|
|
||||||
{
|
{
|
||||||
th.creatingSuccess(player);
|
player.sendMessage("You don't have permission to do that");
|
||||||
player.sendMessage("Successfully activated 2FA");
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
th.creatingFailed(player);
|
player.sendMessage("You don't have two-factor authentication enabled.");
|
||||||
player.sendMessage("That code is incorrect, aborting");
|
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 <code> 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 <code>.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
player.sendMessage("Usage: " + command.getUsage().replace("<command>", label));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> 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<String> completions = new ArrayList<>();
|
||||||
|
StringUtil.copyPartialMatches(args[args.length - 1], Arrays.asList(commands), completions);
|
||||||
|
Collections.sort(completions);
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package eu.oskar3123.spigot2fa.config;
|
package eu.oskar3123.spigot2fa.config;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.configuration.file.FileConfiguration;
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
@@ -55,18 +56,40 @@ public class ConfigHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void saveConfig(Config config)
|
public void saveConfig(Config config)
|
||||||
|
{
|
||||||
|
saveConfig(config, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveConfig(Config config, boolean async)
|
||||||
{
|
{
|
||||||
if (config.fileConfig == null || config.file == null)
|
if (config.fileConfig == null || config.file == null)
|
||||||
{
|
{
|
||||||
return;
|
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);
|
plugin.saveResource(config.name + ".yml", false);
|
||||||
}
|
}
|
||||||
|
reloadConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.bukkit.inventory.meta.MapMeta;
|
|||||||
import org.bukkit.map.MapView;
|
import org.bukkit.map.MapView;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -27,21 +28,67 @@ public class TFAHandler
|
|||||||
this.plugin = plugin;
|
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)
|
public String formatSecret(String secret)
|
||||||
{
|
{
|
||||||
return secret.toLowerCase().replaceAll("(.{4})(?=.{4})", "$1 ");
|
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)
|
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)
|
public void creatingSuccess(Player player)
|
||||||
{
|
{
|
||||||
String secret = remove(player.getUniqueId());
|
String secret = remove(player.getUniqueId());
|
||||||
plugin.configHandler.getConfig(plugin.players).set(player.getUniqueId().toString(), secret);
|
player.getInventory().setItemInMainHand(new ItemStack(Material.AIR));
|
||||||
plugin.configHandler.saveConfig(plugin.players);
|
plugin.configHandler.getConfig(plugin.players).set(player.getUniqueId().toString() + ".secret", secret);
|
||||||
|
plugin.configHandler.saveConfig(plugin.players, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void creatingFailed(Player player)
|
public void creatingFailed(Player player)
|
||||||
@@ -49,15 +96,22 @@ public class TFAHandler
|
|||||||
remove(player.getUniqueId());
|
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)
|
public boolean matchCode(Player player, String code)
|
||||||
{
|
{
|
||||||
String actualCode = TFA.getTOTPCode(getKey(player.getUniqueId()));
|
return matchCode(getKey(player.getUniqueId()), code);
|
||||||
return matchCode(actualCode, code);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String remove(UUID uuid)
|
private String remove(UUID uuid)
|
||||||
@@ -65,33 +119,42 @@ public class TFAHandler
|
|||||||
return isInProcess.remove(uuid);
|
return isInProcess.remove(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String startCreating(Player player)
|
public boolean startCreating(Player player)
|
||||||
{
|
{
|
||||||
String key = TFA.getRandomSecretKey();
|
String key = TFA.getRandomSecretKey();
|
||||||
isInProcess.put(player.getUniqueId(), key);
|
boolean couldShow = showQRCode(player, key);
|
||||||
showQRCode(player, key);
|
if (couldShow)
|
||||||
return key;
|
{
|
||||||
|
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
|
try
|
||||||
{
|
{
|
||||||
ItemStack map = new ItemStack(Material.FILLED_MAP);
|
ItemStack map = new ItemStack(Material.FILLED_MAP);
|
||||||
MapView view = Bukkit.createMap(player.getWorld());
|
MapView view = Bukkit.createMap(player.getWorld());
|
||||||
view.getRenderers().clear();
|
view.getRenderers().clear();
|
||||||
view.addRenderer(new QRMapRenderer(secret, player));
|
view.addRenderer(new QRMapRenderer(plugin, formatSecret(secret), player));
|
||||||
MapMeta mapMeta = (MapMeta) map.getItemMeta();
|
MapMeta mapMeta = (MapMeta) map.getItemMeta();
|
||||||
mapMeta.setMapId(view.getId());
|
mapMeta.setMapId(view.getId());
|
||||||
map.setItemMeta(mapMeta);
|
map.setItemMeta(mapMeta);
|
||||||
player.getInventory().setItemInMainHand(map);
|
player.getInventory().setItemInMainHand(map);
|
||||||
player.sendMap(view);
|
player.sendMap(view);
|
||||||
player.sendMessage("Secret Key: " + secret);
|
return true;
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
player.sendMessage("Failed to generate qr code");
|
player.sendMessage("Failed to generate qr code");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +170,16 @@ public class TFAHandler
|
|||||||
return isInProcess.get(uuid);
|
return isInProcess.get(uuid);
|
||||||
}
|
}
|
||||||
Configuration players = plugin.configHandler.getConfig(plugin.players);
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<UUID, WaitingInfo> 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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<UUID> 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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -25,10 +25,14 @@ public class QRMapRenderer extends MapRenderer
|
|||||||
{
|
{
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.uuid = player.getUniqueId();
|
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.line1 = formattedSecretKey[0].trim();
|
||||||
this.line2 = formattedSecretKey[1].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
|
private void initImage(String qrValue) throws IOException
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class TFA
|
|||||||
return secretKey.toUpperCase();
|
return secretKey.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getTOTPCode(String secretKey)
|
public static String[] getTOTPCodes(String secretKey)
|
||||||
{
|
{
|
||||||
String normalizedBase32Key = secretKey.replace(" ", "").toUpperCase();
|
String normalizedBase32Key = secretKey.replace(" ", "").toUpperCase();
|
||||||
Base32 base32 = new Base32();
|
Base32 base32 = new Base32();
|
||||||
@@ -30,7 +30,11 @@ public class TFA
|
|||||||
String hexKey = Hex.encodeHexString(bytes);
|
String hexKey = Hex.encodeHexString(bytes);
|
||||||
long time = (System.currentTimeMillis() / 1000) / 30;
|
long time = (System.currentTimeMillis() / 1000) / 30;
|
||||||
String hexTime = Long.toHexString(time);
|
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)
|
public static String getGoogleAuthenticatorBarCode(String secretKey, String issuer, String account)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
2fa:
|
2fa:
|
||||||
issuer: 'Mineworlds'
|
issuer: 'Mineworlds'
|
||||||
account: '{NAME}'
|
account: '{NAME}'
|
||||||
|
# session time in minutes
|
||||||
|
sessiontime: 1440
|
||||||
|
|||||||
@@ -5,3 +5,5 @@ authors: [oskar3123]
|
|||||||
main: eu.oskar3123.spigot2fa.Main
|
main: eu.oskar3123.spigot2fa.Main
|
||||||
commands:
|
commands:
|
||||||
2fa:
|
2fa:
|
||||||
|
usage: '/<command> add/remove/<code>'
|
||||||
|
description: 'Two-factor authorization command'
|
||||||
|
|||||||
Reference in New Issue
Block a user