[17.3k Lines] Started on the base of two factor authentication
This commit is contained in:
parent
0c664d2ef1
commit
d360fabcc8
@ -3,4 +3,5 @@ dependencies {
|
||||
implementation("com.zaxxer:HikariCP:3.4.5")
|
||||
compileOnly("com.destroystokyo:paperspigot:1.12.2")
|
||||
implementation("com.github.cryptomorin:XSeries:7.8.0")
|
||||
implementation("com.warrenstrange:googleauth:1.4.0")
|
||||
}
|
@ -117,6 +117,7 @@ public class AccountRepository extends MySQLRepository {
|
||||
}
|
||||
Bukkit.getPluginManager().callEvent(new AccountPreLoadEvent(uuid, name, ipAddress));
|
||||
int finalAccountId = accountId;
|
||||
|
||||
query+= AccountManager.MINI_ACCOUNTS.parallelStream().map(miniAccount -> miniAccount.getQuery(finalAccountId, uuid, name, ipAddress, encryptedIpAddress)).collect(Collectors.joining());
|
||||
if (!query.trim().isEmpty()) {
|
||||
log.info("Executing mini account tasks (" + AccountManager.MINI_ACCOUNTS.size() + ") for " + name);
|
||||
|
@ -2,6 +2,7 @@ package zone.themcgamer.core.account;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import zone.themcgamer.core.account.Account;
|
||||
import zone.themcgamer.core.module.Module;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
|
@ -8,6 +8,7 @@ import net.md_5.bungee.api.chat.TextComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import zone.themcgamer.core.account.Account;
|
||||
@ -65,8 +66,10 @@ public class ChatManager extends Module {
|
||||
registerCommand(new EmotesCommand(this));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
private void onChat(AsyncPlayerChatEvent event) {
|
||||
if (event.isCancelled())
|
||||
return;
|
||||
Player player = event.getPlayer();
|
||||
String message = event.getMessage();
|
||||
|
||||
|
@ -38,6 +38,10 @@ public abstract class Module implements Listener {
|
||||
public void onEnable() {} // Called when the module is enabled
|
||||
public void onDisable() {} // Called when the module is disabled
|
||||
|
||||
public String getName() {
|
||||
return info.name();
|
||||
}
|
||||
|
||||
public void registerCommand(Object command) {
|
||||
CommandManager commandManager = getModule(CommandManager.class);
|
||||
if (commandManager == null)
|
||||
@ -50,7 +54,7 @@ public abstract class Module implements Listener {
|
||||
* @param message the message to log
|
||||
*/
|
||||
public void log(String message) {
|
||||
System.out.println(info.name() + " » " + message);
|
||||
Bukkit.getLogger().info("§b" + info.name() + " §8» §7" + message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,6 +20,7 @@ import zone.themcgamer.core.plugin.command.PluginsCommand;
|
||||
import zone.themcgamer.core.server.ServerManager;
|
||||
import zone.themcgamer.core.task.TaskManager;
|
||||
import zone.themcgamer.core.traveler.ServerTraveler;
|
||||
import zone.themcgamer.core.twoFactor.TwoFactorAuthentication;
|
||||
import zone.themcgamer.core.update.ServerUpdater;
|
||||
import zone.themcgamer.data.jedis.JedisController;
|
||||
import zone.themcgamer.data.jedis.data.server.MinecraftServer;
|
||||
@ -168,6 +169,7 @@ public abstract class MGZPlugin extends JavaPlugin {
|
||||
commandManager.registerCommand(this, new PluginsCommand());
|
||||
|
||||
new CooldownHandler(this);
|
||||
AccountManager.addMiniAccount(new TwoFactorAuthentication(this, mySQLController));
|
||||
nametagManager = new NametagManager(this);
|
||||
|
||||
accountManager = new AccountManager(this, mySQLController, nametagManager);
|
||||
|
@ -0,0 +1,207 @@
|
||||
package zone.themcgamer.core.twoFactor;
|
||||
|
||||
import com.cryptomorin.xseries.XSound;
|
||||
import com.warrenstrange.googleauth.GoogleAuthenticator;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.entity.FoodLevelChangeEvent;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.player.*;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import zone.themcgamer.common.EnumUtils;
|
||||
import zone.themcgamer.core.account.Account;
|
||||
import zone.themcgamer.core.account.AccountManager;
|
||||
import zone.themcgamer.core.account.MiniAccount;
|
||||
import zone.themcgamer.core.common.Style;
|
||||
import zone.themcgamer.core.module.ModuleInfo;
|
||||
import zone.themcgamer.data.Rank;
|
||||
import zone.themcgamer.data.mysql.MySQLController;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@ModuleInfo(name = "Two Factor")
|
||||
public class TwoFactorAuthentication extends MiniAccount<TwoFactorClient> {
|
||||
private final TwoFactorRepository repository;
|
||||
private final GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();
|
||||
private final List<UUID> authenticating = new ArrayList<>();
|
||||
|
||||
public TwoFactorAuthentication(JavaPlugin plugin, MySQLController mySQLController) {
|
||||
super(plugin);
|
||||
repository = new TwoFactorRepository(mySQLController.getDataSource());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TwoFactorClient getAccount(int accountId, UUID uuid, String name, String ip, String encryptedIp) {
|
||||
return new TwoFactorClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQuery(int accountId, UUID uuid, String name, String ip, String encryptedIp) {
|
||||
return "SELECT dbKey, value FROM `twoFactor` WHERE `accountId` = '" + accountId + "';";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAccount(int accountId, UUID uuid, String name, String ip, String encryptedIp, ResultSet resultSet) throws SQLException {
|
||||
Optional<TwoFactorClient> optionalTwoFactorClient = lookup(uuid);
|
||||
if (optionalTwoFactorClient.isEmpty())
|
||||
return;
|
||||
TwoFactorClient twoFactorClient = optionalTwoFactorClient.get();
|
||||
while (resultSet.next()) {
|
||||
TwoFactorDBKey databaseKey = EnumUtils.fromString(TwoFactorDBKey.class, resultSet.getString("dbKey"));
|
||||
if (databaseKey == null)
|
||||
continue;
|
||||
if (databaseKey == TwoFactorDBKey.SECRET_KEY)
|
||||
twoFactorClient.setSecretKey(resultSet.getString("value"));
|
||||
else twoFactorClient.setLastAuthentication(resultSet.getLong("value"));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
private void onLogin(PlayerLoginEvent event) {
|
||||
UUID uuid = event.getPlayer().getUniqueId();
|
||||
Optional<Account> optionalAccount = AccountManager.fromCache(uuid);
|
||||
if (optionalAccount.isEmpty())
|
||||
return;
|
||||
Account account = optionalAccount.get();
|
||||
if (!account.hasRank(Rank.HELPER))
|
||||
return;
|
||||
Optional<TwoFactorClient> optionalTwoFactorClient = lookup(uuid);
|
||||
if (optionalTwoFactorClient.isEmpty())
|
||||
return;
|
||||
// If the player hasn't setup 2fa, the player hasn't authenticated in 24 hours, or their ip has changed, make them authenticate
|
||||
if (optionalTwoFactorClient.get().requiresAuthentication() || !account.getLastEncryptedIpAddress().equals(account.getEncryptedIpAddress()))
|
||||
authenticating.add(uuid);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
private void onJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
Optional<Account> optionalAccount = AccountManager.fromCache(player.getUniqueId());
|
||||
if (optionalAccount.isEmpty())
|
||||
return;
|
||||
Account account = optionalAccount.get();
|
||||
if (!account.hasRank(Rank.HELPER))
|
||||
return;
|
||||
if (!authenticating.contains(player.getUniqueId())) {
|
||||
player.sendMessage(Style.main(getName(), "§aAuthenticated!"));
|
||||
return;
|
||||
}
|
||||
Optional<TwoFactorClient> optionalTwoFactorClient = lookup(player.getUniqueId());
|
||||
if (optionalTwoFactorClient.isEmpty())
|
||||
return;
|
||||
TwoFactorClient twoFactorClient = optionalTwoFactorClient.get();
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(getPlugin(), () -> {
|
||||
if (twoFactorClient.getSecretKey() == null) {
|
||||
String secretKey = googleAuthenticator.createCredentials().getKey();
|
||||
twoFactorClient.setSecretKey(secretKey);
|
||||
|
||||
// TODO: 2/20/21 generate a qr map and give it to the player so they can scan it on their twoFactor app
|
||||
player.sendMessage(Style.main(getName(), "Hey §b" + player.getName() + "§7, you have not setup your two factor authentication yet!"));
|
||||
player.sendMessage(Style.main(getName(), "To begin, open your authentication app of choice and insert the secret key"));
|
||||
player.sendMessage(Style.main(getName(), "§f" + secretKey));
|
||||
player.sendMessage(Style.main(getName(), "Once done, type the 6 digit code provided by your authentication app into the chat"));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(Style.main(getName(), "§cYou need to re-authenticate!"));
|
||||
player.sendMessage(Style.main(getName(), "Type the 6 digit code provided by your authentication app into the chat"));
|
||||
}, 1L);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
private void onChat(AsyncPlayerChatEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
if (!authenticating.contains(player.getUniqueId()))
|
||||
return;
|
||||
Optional<Account> optionalAccount = AccountManager.fromCache(player.getUniqueId());
|
||||
if (optionalAccount.isEmpty())
|
||||
return;
|
||||
Optional<TwoFactorClient> optionalTwoFactorClient = lookup(player.getUniqueId());
|
||||
if (optionalTwoFactorClient.isEmpty())
|
||||
return;
|
||||
event.setCancelled(true);
|
||||
String message = event.getMessage().replaceAll(" ", "");
|
||||
int code;
|
||||
try {
|
||||
code = Integer.parseInt(message);
|
||||
} catch (NumberFormatException ex) {
|
||||
player.sendMessage(Style.main(getName(), "§cInvalid authentication code!"));
|
||||
return;
|
||||
}
|
||||
TwoFactorClient twoFactorClient = optionalTwoFactorClient.get();
|
||||
String secretKey = twoFactorClient.getSecretKey();
|
||||
if (!googleAuthenticator.authorize(secretKey, code))
|
||||
player.sendMessage(Style.main(getName(), "§cInvalid authentication code!"));
|
||||
else {
|
||||
twoFactorClient.setLastAuthentication(System.currentTimeMillis());
|
||||
repository.authenticate(optionalAccount.get().getId(), secretKey, twoFactorClient.getLastAuthentication());
|
||||
authenticating.remove(player.getUniqueId());
|
||||
player.playSound(player.getEyeLocation(), XSound.ENTITY_PLAYER_LEVELUP.parseSound(), 0.9f, 1f);
|
||||
player.sendMessage(Style.main(getName(), "§aAuthenticated!"));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
private void onCommand(PlayerCommandPreprocessEvent event) {
|
||||
cancelEvent(event);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
private void onMove(PlayerMoveEvent event) {
|
||||
Location from = event.getFrom();
|
||||
Location to = event.getTo();
|
||||
if (from.getBlockX() != to.getBlockX() || from.getBlockZ() != to.getBlockZ())
|
||||
cancelEvent(event);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
private void onInventoryClick(InventoryClickEvent event) {
|
||||
cancelEvent((Player) event.getWhoClicked(), event);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
private void onFoodLevelChange(FoodLevelChangeEvent event) {
|
||||
cancelEvent(event);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
private void onDamage(EntityDamageEvent event) {
|
||||
Entity entity = event.getEntity();
|
||||
if (entity instanceof Player)
|
||||
cancelEvent((Player) entity, event);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
private void onHeldItemChange(PlayerItemHeldEvent event) {
|
||||
cancelEvent(event);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
private void onQuit(PlayerQuitEvent event) {
|
||||
authenticating.remove(event.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
private void cancelEvent(Cancellable cancellable) {
|
||||
if (!(cancellable instanceof PlayerEvent))
|
||||
return;
|
||||
cancelEvent(((PlayerEvent) cancellable).getPlayer(), cancellable);
|
||||
}
|
||||
|
||||
private void cancelEvent(Player player, Cancellable cancellable) {
|
||||
if (authenticating.contains(player.getUniqueId()))
|
||||
cancellable.setCancelled(true);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package zone.themcgamer.core.twoFactor;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@NoArgsConstructor @Setter @Getter
|
||||
public class TwoFactorClient {
|
||||
private String secretKey;
|
||||
private long lastAuthentication;
|
||||
|
||||
public boolean requiresAuthentication() {
|
||||
return secretKey == null || (System.currentTimeMillis() - lastAuthentication) >= TimeUnit.DAYS.toMillis(1L);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package zone.themcgamer.core.twoFactor;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
public enum TwoFactorDBKey {
|
||||
SECRET_KEY, LAST_AUTHENTICATION
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package zone.themcgamer.core.twoFactor;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import zone.themcgamer.data.mysql.data.column.Column;
|
||||
import zone.themcgamer.data.mysql.data.column.impl.IntegerColumn;
|
||||
import zone.themcgamer.data.mysql.data.column.impl.LongColumn;
|
||||
import zone.themcgamer.data.mysql.data.column.impl.StringColumn;
|
||||
import zone.themcgamer.data.mysql.repository.MySQLRepository;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
public class TwoFactorRepository extends MySQLRepository {
|
||||
private static final String INSERT_TWO_FACTOR = "INSERT INTO `twoFactor` " +
|
||||
"(`accountId`, `dbKey`, `value`) VALUES " +
|
||||
"(?, ?, ?) ON DUPLICATE KEY UPDATE `value`=?";
|
||||
|
||||
public TwoFactorRepository(HikariDataSource dataSource) {
|
||||
super(dataSource);
|
||||
}
|
||||
|
||||
public void authenticate(int accountId, String secretKey, long lastAuthentication) {
|
||||
StringColumn secretKeyColumn = new StringColumn("value", secretKey);
|
||||
LongColumn lastAuthenticationColumn = new LongColumn("value", lastAuthentication);
|
||||
CompletableFuture.runAsync(() -> {
|
||||
for (TwoFactorDBKey dbKey : TwoFactorDBKey.values()) {
|
||||
Column<?> valueColumn = dbKey == TwoFactorDBKey.SECRET_KEY ? secretKeyColumn : lastAuthenticationColumn;
|
||||
executeInsert(INSERT_TWO_FACTOR, new Column[] {
|
||||
new IntegerColumn("accountId", accountId),
|
||||
new StringColumn("dbKey", dbKey.name()),
|
||||
valueColumn,
|
||||
valueColumn
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -45,6 +45,12 @@ public class MySQLController {
|
||||
new LongColumn("lastLogin", false)
|
||||
}, new String[] { "id" }),
|
||||
|
||||
new Table("twoFactor", new Column[] {
|
||||
new IntegerColumn("accountId", false, false),
|
||||
new StringColumn("dbKey", 50, false),
|
||||
new StringColumn("value", 50, false)
|
||||
}, new String[] { "accountId", "dbKey" }),
|
||||
|
||||
new Table("punishments", new Column[] {
|
||||
new IntegerColumn("id", true, false),
|
||||
new StringColumn("targetIp", 255, false),
|
||||
|
Reference in New Issue
Block a user