[17.4k Lines] Added a QR code image to the two factor authentication system

This commit is contained in:
Rainnny7 2021-02-20 20:35:53 -05:00
parent d360fabcc8
commit bad226c19b
6 changed files with 115 additions and 6 deletions

@ -4,4 +4,5 @@ dependencies {
compileOnly("com.destroystokyo:paperspigot:1.12.2") compileOnly("com.destroystokyo:paperspigot:1.12.2")
implementation("com.github.cryptomorin:XSeries:7.8.0") implementation("com.github.cryptomorin:XSeries:7.8.0")
implementation("com.warrenstrange:googleauth:1.4.0") implementation("com.warrenstrange:googleauth:1.4.0")
implementation("com.google.zxing:javase:3.4.1")
} }

@ -2,7 +2,6 @@ package zone.themcgamer.core.account;
import lombok.Getter; import lombok.Getter;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import zone.themcgamer.core.account.Account;
import zone.themcgamer.core.module.Module; import zone.themcgamer.core.module.Module;
import java.sql.ResultSet; import java.sql.ResultSet;

@ -1,7 +1,11 @@
package zone.themcgamer.core.twoFactor; package zone.themcgamer.core.twoFactor;
import com.cryptomorin.xseries.XMaterial;
import com.cryptomorin.xseries.XSound; import com.cryptomorin.xseries.XSound;
import com.warrenstrange.googleauth.GoogleAuthenticator; import com.warrenstrange.googleauth.GoogleAuthenticator;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
@ -13,13 +17,18 @@ import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.FoodLevelChangeEvent; import org.bukkit.event.entity.FoodLevelChangeEvent;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.*; import org.bukkit.event.player.*;
import org.bukkit.inventory.ItemStack;
import org.bukkit.map.MapView;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import zone.themcgamer.common.EnumUtils; import zone.themcgamer.common.EnumUtils;
import zone.themcgamer.core.account.Account; import zone.themcgamer.core.account.Account;
import zone.themcgamer.core.account.AccountManager; import zone.themcgamer.core.account.AccountManager;
import zone.themcgamer.core.account.MiniAccount; import zone.themcgamer.core.account.MiniAccount;
import zone.themcgamer.core.common.ItemBuilder;
import zone.themcgamer.core.common.Style; import zone.themcgamer.core.common.Style;
import zone.themcgamer.core.module.ModuleInfo; import zone.themcgamer.core.module.ModuleInfo;
import zone.themcgamer.core.twoFactor.image.QRImageGenerator;
import zone.themcgamer.core.twoFactor.image.QRMapRenderer;
import zone.themcgamer.data.Rank; import zone.themcgamer.data.Rank;
import zone.themcgamer.data.mysql.MySQLController; import zone.themcgamer.data.mysql.MySQLController;
@ -109,11 +118,25 @@ public class TwoFactorAuthentication extends MiniAccount<TwoFactorClient> {
String secretKey = googleAuthenticator.createCredentials().getKey(); String secretKey = googleAuthenticator.createCredentials().getKey();
twoFactorClient.setSecretKey(secretKey); 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 // QR Map
ItemStack firstSlotItem = player.getInventory().getItem(0);
if (firstSlotItem != null)
twoFactorClient.setFirstSlotItem(firstSlotItem);
// Creating the map
Bukkit.getScheduler().runTaskAsynchronously(getPlugin(), new QRImageGenerator(player, twoFactorClient.getSecretKey(), image -> {
MapView mapView = Bukkit.createMap(player.getWorld());
mapView.getRenderers().removeIf(mapView::removeRenderer);
mapView.addRenderer(new QRMapRenderer(player, image));
player.getInventory().setItem(0, new ItemBuilder(XMaterial.FILLED_MAP, 1, (byte) mapView.getId()).toItemStack());
player.getInventory().setHeldItemSlot(0);
}));
player.sendMessage(Style.main(getName(), "Hey §b" + player.getName() + "§7, you have not setup your two factor authentication yet!")); 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(new ComponentBuilder(Style.main(getName(), "To begin, open your authentication app of choice and scan the QR code on the map or enter " +
player.sendMessage(Style.main(getName(), "§f" + secretKey)); "the code §6" + secretKey + " §7manually. Once done, type the 6 digit code provided by your authentication app into the chat"))
player.sendMessage(Style.main(getName(), "Once done, type the 6 digit code provided by your authentication app into the chat")); .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("§aClick to copy").create()))
.event(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, secretKey)).create());
return; return;
} }
player.sendMessage(Style.main(getName(), "§cYou need to re-authenticate!")); player.sendMessage(Style.main(getName(), "§cYou need to re-authenticate!"));
@ -143,13 +166,16 @@ public class TwoFactorAuthentication extends MiniAccount<TwoFactorClient> {
} }
TwoFactorClient twoFactorClient = optionalTwoFactorClient.get(); TwoFactorClient twoFactorClient = optionalTwoFactorClient.get();
String secretKey = twoFactorClient.getSecretKey(); String secretKey = twoFactorClient.getSecretKey();
if (!googleAuthenticator.authorize(secretKey, code)) if (!googleAuthenticator.authorize(secretKey, code)) // If the provided code is incorrect, show an error to the player
player.sendMessage(Style.main(getName(), "§cInvalid authentication code!")); player.sendMessage(Style.main(getName(), "§cInvalid authentication code!"));
else { else {
// If the code is correct, we wanna authenticate the player
twoFactorClient.setLastAuthentication(System.currentTimeMillis()); twoFactorClient.setLastAuthentication(System.currentTimeMillis());
repository.authenticate(optionalAccount.get().getId(), secretKey, twoFactorClient.getLastAuthentication()); repository.authenticate(optionalAccount.get().getId(), secretKey, twoFactorClient.getLastAuthentication());
authenticating.remove(player.getUniqueId()); authenticating.remove(player.getUniqueId());
player.playSound(player.getEyeLocation(), XSound.ENTITY_PLAYER_LEVELUP.parseSound(), 0.9f, 1f); player.playSound(player.getEyeLocation(), XSound.ENTITY_PLAYER_LEVELUP.parseSound(), 0.9f, 1f);
if (twoFactorClient.getFirstSlotItem() != null)
player.getInventory().setItem(0, twoFactorClient.getFirstSlotItem());
player.sendMessage(Style.main(getName(), "§aAuthenticated!")); player.sendMessage(Style.main(getName(), "§aAuthenticated!"));
} }
} }
@ -194,6 +220,16 @@ public class TwoFactorAuthentication extends MiniAccount<TwoFactorClient> {
authenticating.remove(event.getPlayer().getUniqueId()); authenticating.remove(event.getPlayer().getUniqueId());
} }
/**
* Check whetherr or not the provided {@link Player} is authenticating
*
* @param player the player to check
* @return whether or not they're authenticating
*/
public boolean isAuthenticating(Player player) {
return authenticating.contains(player.getUniqueId());
}
private void cancelEvent(Cancellable cancellable) { private void cancelEvent(Cancellable cancellable) {
if (!(cancellable instanceof PlayerEvent)) if (!(cancellable instanceof PlayerEvent))
return; return;

@ -3,6 +3,7 @@ package zone.themcgamer.core.twoFactor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.bukkit.inventory.ItemStack;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -14,6 +15,11 @@ public class TwoFactorClient {
private String secretKey; private String secretKey;
private long lastAuthentication; private long lastAuthentication;
// This is not stored in MySQL, this is just a copy of the item in the first slot of the player's inventory
// when they join. We save this item as we place the QR map in that slot and we give them the item when they
// authenticate
private ItemStack firstSlotItem;
public boolean requiresAuthentication() { public boolean requiresAuthentication() {
return secretKey == null || (System.currentTimeMillis() - lastAuthentication) >= TimeUnit.DAYS.toMillis(1L); return secretKey == null || (System.currentTimeMillis() - lastAuthentication) >= TimeUnit.DAYS.toMillis(1L);
} }

@ -0,0 +1,35 @@
package zone.themcgamer.core.twoFactor.image;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.qrcode.QRCodeWriter;
import lombok.AllArgsConstructor;
import org.bukkit.entity.Player;
import java.awt.image.BufferedImage;
import java.util.function.Consumer;
/**
* @author Braydon
*/
@AllArgsConstructor
public class QRImageGenerator implements Runnable {
private static final int MAP_SIZE = 132;
private final Player player;
private final String secretKey;
private final Consumer<BufferedImage> imageConsumer;
@Override
public void run() {
QRCodeWriter qrWriter = new QRCodeWriter();
try {
imageConsumer.accept(MatrixToImageWriter.toBufferedImage(qrWriter.encode(
"otpauth://totp/" + player.getName() + "@McGamerZone?secret=" + secretKey,
BarcodeFormat.QR_CODE, MAP_SIZE, MAP_SIZE)));
} catch (WriterException ex) {
ex.printStackTrace();
}
}
}

@ -0,0 +1,32 @@
package zone.themcgamer.core.twoFactor.image;
import org.bukkit.entity.Player;
import org.bukkit.map.MapCanvas;
import org.bukkit.map.MapRenderer;
import org.bukkit.map.MapView;
import java.awt.image.BufferedImage;
/**
* @author Braydon
*/
public class QRMapRenderer extends MapRenderer {
private final Player player;
private BufferedImage bufferedImage;
public QRMapRenderer(Player player, BufferedImage bufferedImage) {
super(true);
this.player = player;
this.bufferedImage = bufferedImage;
}
@Override
public void render(MapView mapView, MapCanvas mapCanvas, Player player) {
if (bufferedImage == null)
return;
if (this.player.equals(player)) {
mapCanvas.drawImage(0, 0, bufferedImage);
bufferedImage = null;
}
}
}