feat: re-add AuthPlugin
This commit is contained in:
parent
d77c05389e
commit
3b200d8a2b
5 changed files with 188 additions and 31 deletions
|
@ -1 +1 @@
|
|||
2075
|
||||
2094
|
|
@ -52,8 +52,7 @@ public class Configuration {
|
|||
|
||||
public static class OwnerAuthentication {
|
||||
public boolean enabled = false;
|
||||
public String muteReason = "";
|
||||
public Map<String, String> ips = new HashMap<>();
|
||||
public int timeout = 10 * 1000;
|
||||
}
|
||||
|
||||
public static class Backup {
|
||||
|
|
|
@ -133,6 +133,7 @@ public class Main {
|
|||
// initialize plugins
|
||||
console = new ConsolePlugin(config);
|
||||
LoggerPlugin.init();
|
||||
AuthPlugin.init(config);
|
||||
if (config.database.enabled) database = new DatabasePlugin(config);
|
||||
if (config.discord.enabled) discord = new DiscordPlugin(config);
|
||||
if (config.irc.enabled) new IRCPlugin(config);
|
||||
|
|
|
@ -1,59 +1,218 @@
|
|||
package me.chayapak1.chomens_bot.plugins;
|
||||
|
||||
import me.chayapak1.chomens_bot.Bot;
|
||||
import me.chayapak1.chomens_bot.Configuration;
|
||||
import me.chayapak1.chomens_bot.data.logging.LogType;
|
||||
import me.chayapak1.chomens_bot.data.player.PlayerEntry;
|
||||
import me.chayapak1.chomens_bot.util.LoggerUtilities;
|
||||
import me.chayapak1.chomens_bot.util.UUIDUtilities;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.geysermc.mcprotocollib.network.event.session.DisconnectedEvent;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import javax.crypto.Cipher;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class AuthPlugin extends PlayersPlugin.Listener {
|
||||
private static final String ID = "chomens_bot_verify";
|
||||
|
||||
private static PrivateKey PRIVATE_KEY;
|
||||
private static final Path PRIVATE_KEY_PATH = Path.of("private.key");
|
||||
private static final Path PUBLIC_KEY_PATH = Path.of("public.key");
|
||||
|
||||
private static final String BEGIN_PRIVATE_KEY = "-----BEGIN CHOMENS BOT PRIVATE KEY-----";
|
||||
private static final String END_PRIVATE_KEY = "-----END CHOMENS BOT PRIVATE KEY-----";
|
||||
|
||||
private static final String BEGIN_PUBLIC_KEY = "-----BEGIN CHOMENS BOT PUBLIC KEY-----";
|
||||
private static final String END_PUBLIC_KEY = "-----END CHOMENS BOT PUBLIC KEY-----";
|
||||
|
||||
public static void init (Configuration config) {
|
||||
if (!config.ownerAuthentication.enabled) return;
|
||||
|
||||
try {
|
||||
// let's only check for the private key here
|
||||
if (!Files.exists(PRIVATE_KEY_PATH)) {
|
||||
final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
||||
keyGen.initialize(2048);
|
||||
|
||||
final KeyPair pair = keyGen.generateKeyPair();
|
||||
|
||||
// write the keys
|
||||
// (note: no newline split is intentional)
|
||||
final String encodedPrivateKey =
|
||||
BEGIN_PRIVATE_KEY + "\n" +
|
||||
Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded()) +
|
||||
"\n" + END_PRIVATE_KEY;
|
||||
final String encodedPublicKey =
|
||||
BEGIN_PUBLIC_KEY + "\n" +
|
||||
Base64.getEncoder().encodeToString(pair.getPublic().getEncoded()) +
|
||||
"\n" + END_PUBLIC_KEY;
|
||||
|
||||
final BufferedWriter privateKeyWriter = Files.newBufferedWriter(PRIVATE_KEY_PATH);
|
||||
privateKeyWriter.write(encodedPrivateKey);
|
||||
privateKeyWriter.close();
|
||||
|
||||
final BufferedWriter publicKeyWriter = Files.newBufferedWriter(PUBLIC_KEY_PATH);
|
||||
publicKeyWriter.write(encodedPublicKey);
|
||||
publicKeyWriter.close();
|
||||
}
|
||||
|
||||
// is this a good way to remove the things?
|
||||
final String privateKeyString = new String(Files.readAllBytes(PRIVATE_KEY_PATH))
|
||||
.replace(BEGIN_PRIVATE_KEY + "\n", "")
|
||||
.replace("\n" + END_PRIVATE_KEY, "")
|
||||
.replace("\n", "")
|
||||
.trim();
|
||||
|
||||
final byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyString);
|
||||
final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
|
||||
PRIVATE_KEY = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
|
||||
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException | IllegalArgumentException e) {
|
||||
LoggerUtilities.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private final Bot bot;
|
||||
|
||||
private final String ownerIpForServer;
|
||||
public boolean isAuthenticating = false;
|
||||
public long startTime;
|
||||
|
||||
public AuthPlugin (Bot bot) {
|
||||
this.bot = bot;
|
||||
|
||||
this.ownerIpForServer = bot.config.ownerAuthentication.ips.get(bot.getServerString(true));
|
||||
if (!bot.config.ownerAuthentication.enabled) return;
|
||||
|
||||
if (!bot.config.ownerAuthentication.enabled || ownerIpForServer == null) return;
|
||||
bot.executor.scheduleAtFixedRate(() -> {
|
||||
if (!isAuthenticating || !bot.config.ownerAuthentication.enabled) return;
|
||||
|
||||
timeoutCheck();
|
||||
sendAuthRequestMessage();
|
||||
}, 500, 500, TimeUnit.MILLISECONDS);
|
||||
|
||||
bot.addListener(new Bot.Listener() {
|
||||
@Override
|
||||
public void disconnected (DisconnectedEvent event) {
|
||||
AuthPlugin.this.disconnected();
|
||||
}
|
||||
});
|
||||
|
||||
bot.chat.addListener(new ChatPlugin.Listener() {
|
||||
@Override
|
||||
public boolean systemMessageReceived (Component component, String string, String ansi) {
|
||||
return AuthPlugin.this.systemMessageReceived(component);
|
||||
}
|
||||
});
|
||||
|
||||
bot.players.addListener(this);
|
||||
}
|
||||
|
||||
private String decrypt (byte[] data) throws Exception {
|
||||
final Cipher cipher = Cipher.getInstance("RSA");
|
||||
cipher.init(Cipher.DECRYPT_MODE, PRIVATE_KEY);
|
||||
|
||||
final byte[] decryptedBytes = cipher.doFinal(data);
|
||||
|
||||
return new String(decryptedBytes);
|
||||
}
|
||||
|
||||
private void timeoutCheck () {
|
||||
if (System.currentTimeMillis() - startTime < bot.config.ownerAuthentication.timeout) return;
|
||||
|
||||
final PlayerEntry target = bot.players.getEntry(bot.config.ownerName);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
bot.filterManager.add(target, "Authentication timed out");
|
||||
}
|
||||
|
||||
private void sendAuthRequestMessage () {
|
||||
final PlayerEntry target = bot.players.getEntry(bot.config.ownerName);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
final Component component = Component
|
||||
.text(ID)
|
||||
.append(Component.text(UUIDUtilities.selector(bot.profile.getId())));
|
||||
|
||||
bot.chat.tellraw(component, target.profile.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playerJoined(PlayerEntry target) {
|
||||
public void playerJoined (PlayerEntry target) {
|
||||
if (!target.profile.getName().equals(bot.config.ownerName) || !bot.options.useCore) return;
|
||||
|
||||
final CompletableFuture<String> future = bot.players.getPlayerIP(target, true);
|
||||
startTime = System.currentTimeMillis();
|
||||
isAuthenticating = true;
|
||||
}
|
||||
|
||||
future.completeOnTimeout("", 10, TimeUnit.SECONDS);
|
||||
private boolean systemMessageReceived (Component component) {
|
||||
if (!bot.config.ownerAuthentication.enabled) return true;
|
||||
|
||||
if (!(component instanceof TextComponent textComponent)) return true;
|
||||
|
||||
final String id = textComponent.content();
|
||||
|
||||
if (!id.equals(ID)) return true;
|
||||
|
||||
if (!isAuthenticating) return false;
|
||||
|
||||
if (component.children().size() != 1) return true;
|
||||
|
||||
if (!(component.children().getFirst() instanceof TextComponent dataComponent)) return true;
|
||||
|
||||
final String data = dataComponent.content();
|
||||
|
||||
try {
|
||||
final String decrypted = decrypt(Base64.getDecoder().decode(data));
|
||||
|
||||
// what should i use here? should it be the ID?
|
||||
// or does it even matter?
|
||||
if (!decrypted.equals(ID)) return false;
|
||||
|
||||
isAuthenticating = false;
|
||||
|
||||
future.thenApply(ip -> {
|
||||
bot.logger.log(
|
||||
LogType.AUTH,
|
||||
Component.translatable(
|
||||
"Authenticating with user IP %s and configured owner IP %s",
|
||||
Component.text(ip),
|
||||
Component.text(ownerIpForServer)
|
||||
)
|
||||
Component
|
||||
.text("Player has been verified")
|
||||
.color(NamedTextColor.GREEN)
|
||||
);
|
||||
|
||||
if (ip.equals(ownerIpForServer)) {
|
||||
bot.chat.tellraw(
|
||||
Component
|
||||
.text("You have been verified")
|
||||
.color(NamedTextColor.GREEN),
|
||||
target.profile.getId()
|
||||
);
|
||||
} else {
|
||||
bot.filterManager.doAll(target, bot.config.ownerAuthentication.muteReason);
|
||||
}
|
||||
final PlayerEntry target = bot.players.getEntry(bot.config.ownerName);
|
||||
|
||||
return ip;
|
||||
});
|
||||
if (target == null) return false; // sad :(
|
||||
|
||||
bot.chat.tellraw(
|
||||
Component
|
||||
.text("You have been verified")
|
||||
.color(NamedTextColor.GREEN),
|
||||
target.profile.getId()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
bot.logger.error(e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playerLeft (PlayerEntry target) {
|
||||
if (!target.profile.getName().equals(bot.config.ownerName)) return;
|
||||
|
||||
isAuthenticating = false;
|
||||
}
|
||||
|
||||
private void disconnected () {
|
||||
isAuthenticating = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,9 +77,7 @@ imposterFormatChecker:
|
|||
|
||||
ownerAuthentication:
|
||||
enabled: false
|
||||
muteReason: ''
|
||||
ips:
|
||||
localhost:25565: '127.0.0.1'
|
||||
timeout: 10000 # 10 seconds - 10 * 1000
|
||||
|
||||
# if this is enabled when someone does a clear chat command the bot
|
||||
# will tellraw `{player} cleared the chat`
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue