Skip to content

Commit

Permalink
auth part 1 - snowflake based uuids
Browse files Browse the repository at this point in the history
  • Loading branch information
MeiNanziiii committed Nov 19, 2024
1 parent ded7b38 commit c7c8495
Show file tree
Hide file tree
Showing 18 changed files with 283 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package ua.mei.minekord.mixin;

import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.authlib.GameProfile;
import dev.kord.core.entity.Member;
import net.minecraft.network.packet.c2s.login.LoginHelloC2SPacket;
import net.minecraft.network.packet.c2s.login.LoginKeyC2SPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerLoginNetworkHandler;
import net.minecraft.text.Text;
import net.minecraft.util.logging.UncaughtExceptionLogger;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import ua.mei.minekord.config.MinekordConfig;
import ua.mei.minekord.utils.AuthUtils;

import java.util.concurrent.atomic.AtomicInteger;

@Mixin(ServerLoginNetworkHandler.class)
public abstract class ServerLoginNetworkHandlerMixin {
@Shadow
@Final
static Logger LOGGER;
@Shadow
@Final
private static AtomicInteger NEXT_AUTHENTICATOR_THREAD_ID;
@Shadow
@Nullable
GameProfile profile;
@Shadow
@Final
MinecraftServer server;
@Shadow
ServerLoginNetworkHandler.State state;

@Shadow
public abstract void disconnect(Text text);

@Inject(method = "onHello", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;isOnlineMode()Z"), cancellable = true)
public void minekord$replaceUuid(LoginHelloC2SPacket loginHelloC2SPacket, CallbackInfo ci) {
if (MinekordConfig.Auth.INSTANCE.getSnowflakeBasedUuid() && !server.isOnlineMode()) {
Member member = AuthUtils.INSTANCE.findMember(loginHelloC2SPacket.comp_765());

if (member == null) {
this.disconnect(Text.translatable("multiplayer.disconnect.unverified_username"));
ci.cancel();
} else {
this.profile = new GameProfile(AuthUtils.INSTANCE.uuidFromMember(member), loginHelloC2SPacket.comp_765());
LOGGER.info("Snowflake based UUID of player {} is {}", this.profile.getName(), this.profile.getId());
}
}
}

@Inject(method = "onKey", at = @At(value = "INVOKE", target = "Ljava/lang/Thread;setUncaughtExceptionHandler(Ljava/lang/Thread$UncaughtExceptionHandler;)V"), cancellable = true)
public void minekord$replaceThread(LoginKeyC2SPacket loginKeyC2SPacket, CallbackInfo ci, @Local String string) {
if (MinekordConfig.Auth.INSTANCE.getSnowflakeBasedUuid()) {
Thread thread = new Thread("User Authenticator #" + NEXT_AUTHENTICATOR_THREAD_ID.incrementAndGet()) {
public void run() {
GameProfile gameProfile = ServerLoginNetworkHandlerMixin.this.profile;

if (gameProfile != null) {
Member member = AuthUtils.INSTANCE.findMember(gameProfile.getName());

if (member != null) {
ServerLoginNetworkHandlerMixin.this.profile = new GameProfile(AuthUtils.INSTANCE.uuidFromMember(member), gameProfile.getName());
LOGGER.info("Snowflake based UUID of player {} is {}", ServerLoginNetworkHandlerMixin.this.profile.getName(), ServerLoginNetworkHandlerMixin.this.profile.getId());
}
}
if (ServerLoginNetworkHandlerMixin.this.profile != null) {
LOGGER.info("UUID of player {} is {}", ServerLoginNetworkHandlerMixin.this.profile.getName(), ServerLoginNetworkHandlerMixin.this.profile.getId());
ServerLoginNetworkHandlerMixin.this.state = ServerLoginNetworkHandler.State.READY_TO_ACCEPT;
} else if (ServerLoginNetworkHandlerMixin.this.server.isSingleplayer()) {
LOGGER.warn("Failed to verify username but will let them in anyway!");
ServerLoginNetworkHandlerMixin.this.profile = gameProfile;
ServerLoginNetworkHandlerMixin.this.state = ServerLoginNetworkHandler.State.READY_TO_ACCEPT;
} else {
ServerLoginNetworkHandlerMixin.this.disconnect(Text.translatable("multiplayer.disconnect.unverified_username"));
LOGGER.error("Username '{}' tried to join with an invalid session", gameProfile.getName());
}
}
};
thread.setUncaughtExceptionHandler(new UncaughtExceptionLogger(LOGGER));
thread.start();
ci.cancel();
}
}
}
13 changes: 9 additions & 4 deletions src/main/kotlin/ua/mei/minekord/Minekord.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import org.apache.logging.log4j.Logger
import ua.mei.minekord.bot.MinekordBot
import ua.mei.minekord.bot.extension.MessagesExtension
import ua.mei.minekord.bot.extension.PlayerListExtension
import ua.mei.minekord.cache.IPCache
import ua.mei.minekord.config.MinekordConfig
import ua.mei.minekord.config.MinekordConfig.CONFIG_PATH
import ua.mei.minekord.config.MinekordConfig.Chat
import ua.mei.minekord.config.MinekordConfig.Commands
import ua.mei.minekord.event.ChatMessageEvent
import ua.mei.minekord.utils.MessageSender
import ua.mei.minekord.utils.avatar
import ua.mei.minekord.utils.avatarUrl
import java.nio.file.Files

object Minekord : ModInitializer {
Expand All @@ -29,6 +30,9 @@ object Minekord : ModInitializer {

val loader: FabricLoader = FabricLoader.getInstance()

if (!Files.exists(loader.gameDir.resolve("minekord"))) {
Files.createDirectories(loader.gameDir.resolve("minekord"))
}
if (!Files.exists(loader.configDir.resolve(CONFIG_PATH))) {
Files.copy(
loader.getModContainer(MOD_ID).get().findPath(CONFIG_PATH).get(),
Expand All @@ -44,16 +48,17 @@ object Minekord : ModInitializer {
}

ServerMessageEvents.CHAT_MESSAGE.register { message, sender, type ->
ChatMessageEvent.EVENT.invoker().message(message.content, MessageSender(sender.gameProfile.name, sender.avatar))
ChatMessageEvent.EVENT.invoker().message(message.content, MessageSender(sender.gameProfile.name, sender.avatarUrl))
}
ServerMessageEvents.COMMAND_MESSAGE.register { message, source, parameters ->
if (source.isExecutedByPlayer) {
ChatMessageEvent.EVENT.invoker().message(message.content, MessageSender(source.player!!.gameProfile.name, source.player!!.avatar))
ChatMessageEvent.EVENT.invoker().message(message.content, MessageSender(source.player!!.gameProfile.name, source.player!!.avatarUrl))
} else {
ChatMessageEvent.EVENT.invoker().message(message.content, MessageSender(Chat.Webhook.webhookName, Chat.Webhook.webhookAvatar))
}
}

ServerLifecycleEvents.SERVER_STARTING.register(IPCache)
ServerLifecycleEvents.SERVER_STOPPED.register(IPCache)
ServerLifecycleEvents.SERVER_STARTING.register(MinekordBot)
CommandRegistrationCallback.EVENT.register(MinekordCommands)
}
Expand Down
8 changes: 4 additions & 4 deletions src/main/kotlin/ua/mei/minekord/bot/MinekordBot.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package ua.mei.minekord.bot

import dev.kord.common.entity.AllowedMentionType
import dev.kord.common.entity.Snowflake
import dev.kord.core.entity.Guild
import dev.kord.core.entity.Webhook
import dev.kord.core.entity.channel.TopGuildMessageChannel
Expand Down Expand Up @@ -39,6 +38,7 @@ import ua.mei.minekord.event.ChatMessageEvent
import ua.mei.minekord.utils.MinekordActivityType
import ua.mei.minekord.utils.MinekordMinecraftRenderer
import ua.mei.minekord.utils.SerializerUtils
import ua.mei.minekord.utils.asSnowflake
import ua.mei.minekord.utils.toText
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.KCallable
Expand Down Expand Up @@ -79,7 +79,7 @@ object MinekordBot : CoroutineScope, ServerLifecycleEvents.ServerStarting {
+Intent.GuildMembers
}
members {
fill(Main.token)
fill(Main.guild)
}
hooks {
afterKoinSetup {
Expand All @@ -89,8 +89,8 @@ object MinekordBot : CoroutineScope, ServerLifecycleEvents.ServerStarting {
}
}
}
guild = bot.kordRef.getGuild(Snowflake(Main.guild))
channel = guild.getChannel(Snowflake(Main.channel)) as TopGuildMessageChannel
guild = bot.kordRef.getGuild(Main.guild.asSnowflake)
channel = guild.getChannel(Main.channel.asSnowflake) as TopGuildMessageChannel
webhook = ensureWebhook(channel, Chat.Webhook.webhookName)

mentions.add(AllowedMentionType.UserMentions)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package ua.mei.minekord.bot.extension

import dev.kord.common.entity.Snowflake
import dev.kord.core.entity.Member
import dev.kord.core.entity.Message
import dev.kord.core.entity.effectiveName
Expand All @@ -24,7 +23,8 @@ import ua.mei.minekord.config.MinekordConfig.Main
import ua.mei.minekord.utils.MessageSender
import ua.mei.minekord.utils.SerializerUtils
import ua.mei.minekord.utils.adventure
import ua.mei.minekord.utils.avatar
import ua.mei.minekord.utils.asSnowflake
import ua.mei.minekord.utils.avatarUrl
import ua.mei.minekord.utils.literal
import ua.mei.minekord.utils.native
import ua.mei.minekord.utils.summary
Expand All @@ -36,7 +36,7 @@ class MessagesExtension : MinekordExtension() {
override suspend fun setup() {
event<MessageCreateEvent> {
check { isNotBot() }
check { inChannel(Snowflake(Main.channel)) }
check { inChannel(Main.channel.asSnowflake) }

action {
val message: Message = event.message
Expand Down Expand Up @@ -73,7 +73,7 @@ class MessagesExtension : MinekordExtension() {
override suspend fun onChatMessage(message: Text, sender: MessageSender) {
webhookMessage {
username = sender.name
avatarUrl = sender.avatar
avatarUrl = sender.avatarUrl

content = DiscordSerializer.INSTANCE.serialize(message.adventure(), MinekordBot.discordOptions).let {
if (Chat.convertMentions) {
Expand All @@ -97,7 +97,7 @@ class MessagesExtension : MinekordExtension() {

webhookEmbed {
author {
icon = player.avatar
icon = player.avatarUrl
name = message
}
footer {
Expand All @@ -111,7 +111,7 @@ class MessagesExtension : MinekordExtension() {
webhookEmbed {
author {
name = Chat.Discord.joinMessage.toText(player).string
icon = player.avatar
icon = player.avatarUrl
}
color = Colors.green
}
Expand All @@ -121,7 +121,7 @@ class MessagesExtension : MinekordExtension() {
webhookEmbed {
author {
name = Chat.Discord.leaveMessage.toText(player).string
icon = player.avatar
icon = player.avatarUrl
}
color = Colors.red
}
Expand All @@ -130,7 +130,7 @@ class MessagesExtension : MinekordExtension() {
override suspend fun onPlayerDeath(player: ServerPlayerEntity, source: DamageSource) {
webhookEmbed {
author {
icon = player.avatar
icon = player.avatarUrl
name = Chat.Discord.deathMessage.toText(player) {
"message" to source.getDeathMessage(player)
}.string
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package ua.mei.minekord.bot.extension

import dev.kord.common.entity.Snowflake
import dev.kord.rest.builder.message.embed
import dev.kordex.core.extensions.ephemeralSlashCommand
import dev.kordex.core.i18n.toKey
import ua.mei.minekord.bot.MinekordExtension
import ua.mei.minekord.config.MinekordConfig.Colors
import ua.mei.minekord.config.MinekordConfig.Commands
import ua.mei.minekord.config.MinekordConfig.Main
import ua.mei.minekord.utils.asSnowflake
import ua.mei.minekord.utils.toText

class PlayerListExtension : MinekordExtension() {
Expand All @@ -18,7 +18,7 @@ class PlayerListExtension : MinekordExtension() {
name = Commands.PlayerList.name.toKey()
description = Commands.PlayerList.description.toKey()

guild(Snowflake(Main.guild))
guild(Main.guild.asSnowflake)

action {
respond {
Expand Down
40 changes: 40 additions & 0 deletions src/main/kotlin/ua/mei/minekord/cache/IPCache.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package ua.mei.minekord.cache

import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents
import net.fabricmc.loader.api.FabricLoader
import net.minecraft.server.MinecraftServer
import java.io.FileReader
import java.io.FileWriter
import java.nio.file.Files
import java.nio.file.Path

object IPCache : ServerLifecycleEvents.ServerStarting, ServerLifecycleEvents.ServerStopped {
var ipCache: MutableMap<String, String> = mutableMapOf()
val alreadyRequestedIps: MutableMap<String, String> = mutableMapOf()
val blockedIps: MutableList<String> = mutableListOf()
val path: Path = FabricLoader.getInstance().gameDir.resolve("minekord/ip-cache.json")
val type: TypeToken<MutableMap<String, String>> = object : TypeToken<MutableMap<String, String>>() {}
val gson: Gson = GsonBuilder()
.setPrettyPrinting()
.create()

override fun onServerStarting(server: MinecraftServer) {
if (!Files.exists(path)) {
Files.createFile(path)
Files.write(path, "{}".toByteArray(Charsets.UTF_8))
}

FileReader(path.toFile()).use { reader ->
ipCache = gson.fromJson(reader, type)
}
}

override fun onServerStopped(server: MinecraftServer) {
FileWriter(path.toFile()).use { writer ->
writer.write(gson.toJson(ipCache))
}
}
}
39 changes: 38 additions & 1 deletion src/main/kotlin/ua/mei/minekord/config/MinekordConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ua.mei.minekord.config
import com.uchuhimo.konf.Config
import com.uchuhimo.konf.source.toml
import dev.kord.common.Color
import eu.pb4.placeholders.api.ParserContext
import eu.pb4.placeholders.api.Placeholders
import eu.pb4.placeholders.api.node.EmptyNode
import eu.pb4.placeholders.api.node.TextNode
Expand All @@ -12,12 +13,13 @@ import eu.pb4.placeholders.api.parsers.StaticPreParser
import eu.pb4.placeholders.api.parsers.TextParserV1
import net.fabricmc.loader.api.FabricLoader
import net.kyori.adventure.text.format.TextColor
import net.minecraft.text.Text
import ua.mei.minekord.config.spec.AuthSpec
import ua.mei.minekord.config.spec.ChatSpec
import ua.mei.minekord.config.spec.ColorsSpec
import ua.mei.minekord.config.spec.CommandsSpec
import ua.mei.minekord.config.spec.MainSpec
import ua.mei.minekord.config.spec.PresenceSpec
import ua.mei.minekord.parser.DynamicNode
import ua.mei.minekord.utils.MinekordActivityType
import ua.mei.minekord.utils.toColor

Expand All @@ -39,6 +41,7 @@ object MinekordConfig {
addSpec(PresenceSpec)
addSpec(CommandsSpec)
addSpec(ColorsSpec)
addSpec(AuthSpec)
}.from.toml.file(FabricLoader.getInstance().configDir.resolve(CONFIG_PATH).toFile())

config.validateRequired()
Expand All @@ -48,6 +51,7 @@ object MinekordConfig {
Presence.load()
Commands.load()
Colors.load()
Auth.load()
}

private fun parseNode(text: String): TextNode {
Expand Down Expand Up @@ -224,4 +228,37 @@ object MinekordConfig {
link = TextColor.fromHexString(config[ColorsSpec.link])!!
}
}

object Auth {
var snowflakeBasedUuid: Boolean = false
private set
var requiredRoles: List<ULong> = emptyList()
private set
var ipBasedLogin: Boolean = false
private set

fun load() {
snowflakeBasedUuid = config[AuthSpec.snowflakeBasedUuid]
requiredRoles = config[AuthSpec.requiredRoles]
ipBasedLogin = config[AuthSpec.ipBasedLogin]
}
}

data class DynamicNode(val key: String, val text: Text) : TextNode {
companion object {
fun of(key: String): DynamicNode {
return DynamicNode(key, Text.literal("{$key}"))
}

val NODES = ParserContext.Key<Map<String, Text>>("minekord:dynamic", null)
}

override fun toText(context: ParserContext, removeBackslashes: Boolean): Text {
return context.get(NODES)?.getOrDefault(this.key, text) ?: text
}

override fun isDynamic(): Boolean {
return true
}
}
}
Loading

0 comments on commit c7c8495

Please sign in to comment.