make kira auth a proxy command

This commit is contained in:
Husky
2025-09-20 13:35:46 -04:00
committed by okx-code
parent b99e3e481d
commit 41711d6280
16 changed files with 389 additions and 41 deletions

View File

@@ -67,6 +67,7 @@ dependencies {
proxyPlugin(project(path = ":plugins:civproxy-velocity", configuration = "shadow")) proxyPlugin(project(path = ":plugins:civproxy-velocity", configuration = "shadow"))
proxyPlugin(project(path = ":plugins:announcements-velocity", configuration = "shadow")) proxyPlugin(project(path = ":plugins:announcements-velocity", configuration = "shadow"))
proxyPlugin(project(path = ":plugins:kiragateway-velocity", configuration = "shadow"))
} }
val copyPaperPlugins = tasks.register<Copy>("copyPaperPlugins") { val copyPaperPlugins = tasks.register<Copy>("copyPaperPlugins") {

View File

@@ -0,0 +1,8 @@
# Your RabbitMQ credentials
rabbitmq:
user: ${CIV_RABBITMQ_USERNAME}
password: ${CIV_RABBITMQ_PASSWORD}
host: ${CIV_RABBITMQ_HOST}
port: 5672
incomingQueue: kira-to-gateway
outgoingQueue: gateway-to-kira

View File

@@ -30,6 +30,10 @@ services:
CIV_POSTGRES_USERNAME: postgres CIV_POSTGRES_USERNAME: postgres
CIV_POSTGRES_PASSWORD: postgres CIV_POSTGRES_PASSWORD: postgres
CIV_RABBITMQ_HOST: rabbitmq
CIV_RABBITMQ_USERNAME: rabbitmq
CIV_RABBITMQ_PASSWORD: rabbitmq
CIV_TIMEOUT_TIME: 3000000 CIV_TIMEOUT_TIME: 3000000
CIV_PVP_HOSTNAME: 'pvp.localhost' CIV_PVP_HOSTNAME: 'pvp.localhost'
CIV_GAMMA_HOSTNAME: 'mini.localhost' CIV_GAMMA_HOSTNAME: 'mini.localhost'

View File

@@ -25,7 +25,7 @@ cron-utils = { group = "com.cronutils", name = "cron-utils", version = "9.2.1" }
configurate-yaml = { group = "org.spongepowered", name = "configurate-yaml", version.ref = "configurate" } configurate-yaml = { group = "org.spongepowered", name = "configurate-yaml", version.ref = "configurate" }
rabbitmq-client = { group = "com.rabbitmq", name = "amqp-client", version = "5.17.1" } rabbitmq-client = { group = "com.rabbitmq", name = "amqp-client", version = "5.26.0" }
nuvotifier-api = { group = "com.github.NuVotifier.NuVotifier", name = "nuvotifier-api", version.ref = "nuotifier" } nuvotifier-api = { group = "com.github.NuVotifier.NuVotifier", name = "nuvotifier-api", version.ref = "nuotifier" }
nuvotifier-bukkit = { group = "com.github.NuVotifier.NuVotifier", name = "nuvotifier-bukkit", version.ref = "nuotifier" } nuvotifier-bukkit = { group = "com.github.NuVotifier.NuVotifier", name = "nuvotifier-bukkit", version.ref = "nuotifier" }

View File

@@ -1,9 +1,7 @@
package com.github.maxopoly.KiraBukkitGateway; package com.github.maxopoly.KiraBukkitGateway;
import com.github.maxopoly.KiraBukkitGateway.auth.AuthcodeManager;
import com.github.maxopoly.KiraBukkitGateway.command.CreateDiscordGroupChatCommand; import com.github.maxopoly.KiraBukkitGateway.command.CreateDiscordGroupChatCommand;
import com.github.maxopoly.KiraBukkitGateway.command.DeleteDiscordGroupChatCommand; import com.github.maxopoly.KiraBukkitGateway.command.DeleteDiscordGroupChatCommand;
import com.github.maxopoly.KiraBukkitGateway.command.GenerateDiscordAuthCodeCommand;
import com.github.maxopoly.KiraBukkitGateway.command.ReloadKiraCommand; import com.github.maxopoly.KiraBukkitGateway.command.ReloadKiraCommand;
import com.github.maxopoly.KiraBukkitGateway.command.SyncDiscordChannelAccessCommand; import com.github.maxopoly.KiraBukkitGateway.command.SyncDiscordChannelAccessCommand;
import com.github.maxopoly.KiraBukkitGateway.impersonation.KiraLuckPermsWrapper; import com.github.maxopoly.KiraBukkitGateway.impersonation.KiraLuckPermsWrapper;
@@ -29,7 +27,6 @@ public class KiraBukkitGatewayPlugin extends ACivMod {
private RabbitHandler rabbit; private RabbitHandler rabbit;
private RabbitCommands rabbitCommands; private RabbitCommands rabbitCommands;
private AuthcodeManager authcodeManager;
private KiraLuckPermsWrapper permsWrapper; private KiraLuckPermsWrapper permsWrapper;
private ConfigParser config; private ConfigParser config;
private List<KiraLogAppender> logAppenders; private List<KiraLogAppender> logAppenders;
@@ -38,7 +35,6 @@ public class KiraBukkitGatewayPlugin extends ACivMod {
public void onEnable() { public void onEnable() {
super.onEnable(); super.onEnable();
instance = this; instance = this;
authcodeManager = new AuthcodeManager(12);
reload(); reload();
setupPermissions(); setupPermissions();
this.permsWrapper = new KiraLuckPermsWrapper(); this.permsWrapper = new KiraLuckPermsWrapper();
@@ -54,7 +50,6 @@ public class KiraBukkitGatewayPlugin extends ACivMod {
private void registerCommands() { private void registerCommands() {
commandManager.registerCommand(new CreateDiscordGroupChatCommand()); commandManager.registerCommand(new CreateDiscordGroupChatCommand());
commandManager.registerCommand(new DeleteDiscordGroupChatCommand()); commandManager.registerCommand(new DeleteDiscordGroupChatCommand());
commandManager.registerCommand(new GenerateDiscordAuthCodeCommand());
commandManager.registerCommand(new ReloadKiraCommand()); commandManager.registerCommand(new ReloadKiraCommand());
commandManager.registerCommand(new SyncDiscordChannelAccessCommand()); commandManager.registerCommand(new SyncDiscordChannelAccessCommand());
} }
@@ -102,10 +97,6 @@ public class KiraBukkitGatewayPlugin extends ACivMod {
return instance; return instance;
} }
public AuthcodeManager getAuthcodeManager() {
return authcodeManager;
}
public RabbitCommands getRabbit() { public RabbitCommands getRabbit() {
return rabbitCommands; return rabbitCommands;
} }

View File

@@ -1,30 +0,0 @@
package com.github.maxopoly.KiraBukkitGateway.command;
import co.aikar.commands.BaseCommand;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.Description;
import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin;
import com.github.maxopoly.KiraBukkitGateway.rabbit.RabbitCommands;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
public class GenerateDiscordAuthCodeCommand extends BaseCommand {
@CommandAlias("discordauth")
@Description("Create an auth code for linking your ingame account to your Discord account")
public void execute(Player sender) {
Player p = (Player) sender;
String code = KiraBukkitGatewayPlugin.getInstance().getAuthcodeManager().getNewCode();
if (code == null) {
sender.sendMessage(ChatColor.RED + "Failed to generate code. You should probably tell an admin about this");
return;
}
// lets make the code upper case to make it easier on people
code = code.toUpperCase();
RabbitCommands rabbit = KiraBukkitGatewayPlugin.getInstance().getRabbit();
rabbit.sendAuthCode(code, p.getName(), p.getUniqueId());
sender.sendMessage(String.format(
"%sYour code is '%s'. Execute '/auth %s' in the official discord to authenticate and link your account. Note that upper/lower case does not matter.",
ChatColor.GOLD, code, code));
}
}

View File

@@ -0,0 +1,35 @@
import org.gradle.api.tasks.Copy
plugins {
alias(libs.plugins.shadow)
}
group = "xyz.huskydog"
version = "1.0-SNAPSHOT"
dependencies {
compileOnly(libs.velocity.api)
annotationProcessor(libs.velocity.api)
implementation(libs.configurate.yaml)
api(libs.rabbitmq.client)
compileOnly(libs.luckperms.api)
}
val templateSource = file("src/main/templates")
val templateDest = layout.buildDirectory.dir("generated/sources/templates")
val generateTemplates by tasks.registering(Copy::class) {
val props = mapOf(
"version" to project.version
)
inputs.properties(props)
from(templateSource)
into(templateDest)
expand(props)
}
sourceSets.named("main") {
java.srcDir(generateTemplates.map { it.outputs })
}

View File

@@ -0,0 +1,84 @@
package xyz.huskydog.kiragatewayVelocity;
import com.rabbitmq.client.ConnectionFactory;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.configurate.CommentedConfigurationNode;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
public class Config {
/**
* Loads the config from disk, and creates it if necessary
*/
public static @Nullable CommentedConfigurationNode loadConfig() {
KiragatewayVelocity plugin = KiragatewayVelocity.getInstance();
try {
// ensure data directory exists
if (!Files.exists(plugin.dataDirectory)) {
Files.createDirectories(plugin.dataDirectory);
}
} catch (IOException e) {
plugin.logger.error("Could not create data directory at {}", plugin.dataDirectory);
plugin.logger.error("Failed to create data directory", e);
return null;
}
// create config file if it doesn't exist
Path configFile = plugin.dataDirectory.resolve("config.yml");
if (!Files.exists(configFile)) {
try (InputStream in = plugin.getClass().getResourceAsStream("/config.yml")) {
if (in != null) {
Files.copy(in, configFile);
plugin.logger.info("Default configuration file created.");
} else {
plugin.logger.error("Default configuration file is missing in resources!");
return null;
}
} catch (IOException e) {
plugin.logger.error("Could not create default configuration file at {}", configFile);
plugin.logger.error("Failed to create default config", e);
}
}
YamlConfigurationLoader loader = YamlConfigurationLoader.builder().path(configFile).build();
try {
return loader.load();
} catch (IOException e) {
throw new RuntimeException("Could not load configuration file: " + configFile, e);
}
}
/**
* Reads the RabbitMQ configuration from the config file
* @return
*/
public static @Nullable ConnectionFactory getRabbitConfig(CommentedConfigurationNode config) {
if (config == null) {
return null;
}
ConnectionFactory connFac = new ConnectionFactory();
var user = config.node("rabbitmq", "user");
if (!user.empty()) {
connFac.setUsername(user.getString());
}
var password = config.node("rabbitmq", "password");
if (!password.empty()) {
connFac.setPassword(password.getString());
}
var host = config.node("rabbitmq", "host");
if (!host.empty()) {
connFac.setHost(host.getString());
}
var port = config.node("rabbitmq", "port");
if (!port.empty() && port.getInt(-1) != -1) {
connFac.setPort(port.getInt());
}
return connFac;
}
}

View File

@@ -0,0 +1,103 @@
package xyz.huskydog.kiragatewayVelocity;
import com.google.inject.Inject;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import java.nio.file.Path;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.spongepowered.configurate.CommentedConfigurationNode;
import xyz.huskydog.kiragatewayVelocity.auth.AuthcodeManager;
import xyz.huskydog.kiragatewayVelocity.commands.DiscordAuth;
import xyz.huskydog.kiragatewayVelocity.rabbit.RabbitCommands;
import xyz.huskydog.kiragatewayVelocity.rabbit.RabbitHandler;
@Plugin(
id = "kiragateway-velocity",
name = "kiragateway-velocity",
version = BuildConstants.VERSION,
url = "https://civmc.net",
authors = {"Huskydog9988"}
)
public class KiragatewayVelocity {
public final ProxyServer proxy;
public final Logger logger;
public final Path dataDirectory;
private static KiragatewayVelocity instance;
private AuthcodeManager authcodeManager;
private RabbitHandler rabbitHandler;
private RabbitCommands rabbitCommands;
private @Nullable CommentedConfigurationNode config;
@Inject
public KiragatewayVelocity(ProxyServer proxy, Logger logger, @DataDirectory Path dataDirectory) {
this.proxy = proxy;
this.logger = logger;
this.dataDirectory = dataDirectory;
}
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent event) {
instance = this;
config = Config.loadConfig();
if (config == null) {
logger.error("Failed to load configuration, shutting down");
proxy.shutdown();
return;
}
authcodeManager = new AuthcodeManager(12);
rabbitHandler = new RabbitHandler(
Config.getRabbitConfig(config),
config.getString("rabbitmq.incomingQueue"),
config.getString("rabbitmq.outgoingQueue"), // Outgoing queue name
logger
);
if (!rabbitHandler.setup()) {
logger.error("Failed to setup rabbitmq, shutting down");
proxy.shutdown();
return;
}
rabbitCommands = new RabbitCommands(rabbitHandler);
registerCommands();
}
@Subscribe
public void onProxyShutdown(ProxyShutdownEvent event) {
rabbitHandler.shutdown();
}
private void registerCommands() {
CommandManager commandManager = proxy.getCommandManager();
// discordauth command
CommandMeta discordAuthMeta = commandManager.metaBuilder("discordauth")
// .aliases("auth")
.plugin(this)
.build();
commandManager.register(discordAuthMeta, new DiscordAuth());
}
public AuthcodeManager getAuthcodeManager() {
return authcodeManager;
}
public RabbitCommands getRabbit() {
return rabbitCommands;
}
public static KiragatewayVelocity getInstance() {
return instance;
}
}

View File

@@ -1,4 +1,4 @@
package com.github.maxopoly.KiraBukkitGateway.auth; package xyz.huskydog.kiragatewayVelocity.auth;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.HashSet; import java.util.HashSet;

View File

@@ -0,0 +1,36 @@
package xyz.huskydog.kiragatewayVelocity.commands;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.proxy.Player;
import net.kyori.adventure.text.minimessage.MiniMessage;
import xyz.huskydog.kiragatewayVelocity.KiragatewayVelocity;
import xyz.huskydog.kiragatewayVelocity.rabbit.RabbitCommands;
public final class DiscordAuth implements SimpleCommand {
@Override
public void execute(final Invocation invocation) {
CommandSource source = invocation.source();
var mm = MiniMessage.miniMessage();
if (source instanceof Player player) {
String code = KiragatewayVelocity.getInstance().getAuthcodeManager().getNewCode();
if (code == null) {
player.sendMessage(mm.deserialize("<red>Failed to generate auth code. You should probably tell an admin about this</red>"));
return;
}
// lets make the code upper case to make it easier on people
code = code.toUpperCase();
RabbitCommands rabbit = KiragatewayVelocity.getInstance().getRabbit();
// TODO: handle civ username not just mojang username
rabbit.sendAuthCode(code, player.getUsername(), player.getUniqueId());
source.sendMessage(mm.deserialize(String.format(
"<hover:show_text:'Click to copy the auth code'><click:copy_to_clipboard:'%s'><gold>Your code is '%s'. Execute '/auth %s' in the official discord to authenticate and link your account. Note that upper/lower case does not matter.</gold></click></hover>",
code, code, code)));
} else {
source.sendMessage(mm.deserialize("<red>This command can only be run by a player.</red>"));
}
}
}

View File

@@ -0,0 +1,40 @@
package xyz.huskydog.kiragatewayVelocity.rabbit;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.util.UUID;
public class RabbitCommands {
private final RabbitHandler internal;
public RabbitCommands(RabbitHandler internalRabbit) {
this.internal = internalRabbit;
}
public void sendAuthCode(String code, String playerName, UUID playerUUID) {
nonNullArgs(code, playerName, playerUUID);
JsonObject json = new JsonObject();
json.addProperty("uuid", playerUUID.toString());
json.addProperty("name", playerName);
json.addProperty("code", code);
sendInternal("addauth", json);
}
private void sendInternal(String id, JsonObject json) {
json.addProperty("timestamp", System.currentTimeMillis());
json.addProperty("packettype", id);
Gson gson = new Gson();
String payload = gson.toJson(json);
internal.sendMessage(payload);
}
private void nonNullArgs(Object... objects) {
for (Object o : objects) {
if (o == null) {
throw new IllegalArgumentException("Arguments cant be null");
}
}
}
}

View File

@@ -0,0 +1,60 @@
package xyz.huskydog.kiragatewayVelocity.rabbit;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import org.slf4j.Logger;
public class RabbitHandler {
private ConnectionFactory connectionFactory;
private String incomingQueue;
private String outgoingQueue;
private Logger logger;
private Connection conn;
private Channel incomingChannel;
private Channel outgoingChannel;
public RabbitHandler(ConnectionFactory connFac, String incomingQueue, String outgoingQueue, Logger logger) {
this.connectionFactory = connFac;
this.incomingQueue = incomingQueue;
this.outgoingQueue = outgoingQueue;
this.logger = logger;
}
public boolean setup() {
try {
conn = connectionFactory.newConnection();
incomingChannel = conn.createChannel();
outgoingChannel = conn.createChannel();
incomingChannel.queueDeclare(incomingQueue, false, false, false, null);
outgoingChannel.queueDeclare(outgoingQueue, false, false, false, null);
return true;
} catch (IOException | TimeoutException e) {
logger.error("Failed to setup rabbit connection", e);
return false;
}
}
public void shutdown() {
try {
incomingChannel.close();
outgoingChannel.close();
conn.close();
} catch (IOException | TimeoutException e) {
logger.error("Failed to close rabbit connection", e);
}
}
public boolean sendMessage(String msg) {
try {
outgoingChannel.basicPublish("", outgoingQueue, null, msg.getBytes("UTF-8"));
return true;
} catch (IOException e) {
logger.error("Failed to send rabbit message", e);
return false;
}
}
}

View File

@@ -0,0 +1,8 @@
# Your RabbitMQ credentials
rabbitmq:
user: username
password: password
host: localhost
port: 5672
incomingQueue: kira-to-gateway
outgoingQueue: gateway-to-kira

View File

@@ -0,0 +1,7 @@
package xyz.huskydog.kiragatewayVelocity;
// The constants are replaced before compilation
public class BuildConstants {
public static final String VERSION = "${version}";
}

View File

@@ -43,3 +43,4 @@ include(":plugins:kitpvp-paper")
include(":plugins:voidworld-paper") include(":plugins:voidworld-paper")
include(":plugins:heliodor-paper") include(":plugins:heliodor-paper")
include(":plugins:civproxy-velocity") include(":plugins:civproxy-velocity")
include(":plugins:kiragateway-velocity")