Skip to content

Commit

Permalink
part 3 - ip cache (requires refactor)
Browse files Browse the repository at this point in the history
  • Loading branch information
MeiNanziiii committed Nov 19, 2024
1 parent 0be4a07 commit 7ece993
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
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.cache.IPCache;
import ua.mei.minekord.config.MinekordConfig;
import ua.mei.minekord.event.IPCheckEvent;
import ua.mei.minekord.utils.AuthUtils;

import java.util.concurrent.atomic.AtomicInteger;

// TODO: improve this fuckin mixin
@Mixin(ServerLoginNetworkHandler.class)
public abstract class ServerLoginNetworkHandlerMixin {
@Shadow
Expand All @@ -49,6 +52,9 @@ public abstract class ServerLoginNetworkHandlerMixin {
@Final
ClientConnection connection;

@Unique
Member member = null;

@Shadow
public abstract void disconnect(Text text);

Expand All @@ -62,9 +68,11 @@ public abstract class ServerLoginNetworkHandlerMixin {

@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 (MinekordConfig.Auth.INSTANCE.getSnowflakeBasedUuid() || MinekordConfig.Auth.INSTANCE.getIpBasedLogin()) {
member = AuthUtils.INSTANCE.findMember(loginHelloC2SPacket.comp_765());
}

if (MinekordConfig.Auth.INSTANCE.getSnowflakeBasedUuid() && !server.isOnlineMode()) {
if (member == null) {
this.disconnect(Text.translatable("multiplayer.disconnect.unverified_username"));
ci.cancel();
Expand All @@ -73,16 +81,33 @@ public abstract class ServerLoginNetworkHandlerMixin {
LOGGER.info("Snowflake based UUID of player {} is {}", this.profile.getName(), this.profile.getId());
}
}

if (MinekordConfig.Auth.INSTANCE.getIpBasedLogin() && this.profile != null) {
if (member == null) {
this.disconnect(Text.translatable("multiplayer.disconnect.unverified_username"));
ci.cancel();
} else {
if (!IPCache.INSTANCE.containsInCache(this.connection.getAddress(), this.profile)) {
if (!IPCache.INSTANCE.isRequested(this.connection.getAddress(), this.profile)) {
IPCheckEvent.Companion.getEVENT().invoker().check(this.connection.getAddress(), this.profile);
}
this.disconnect(Text.literal(MinekordConfig.Messages.INSTANCE.getIpKickMessage()));
ci.cancel();
}
}
}
}

@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()) {
Thread thread = new Thread("Minekord User Authenticator #" + NEXT_AUTHENTICATOR_THREAD_ID.incrementAndGet()) {
public void run() {
GameProfile gameProfile = ServerLoginNetworkHandlerMixin.this.profile;

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

if (member != null) {
ServerLoginNetworkHandlerMixin.this.profile = new GameProfile(AuthUtils.INSTANCE.uuidFromMember(member), gameProfile.getName());
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/ua/mei/minekord/Minekord.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import net.fabricmc.loader.api.FabricLoader
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import ua.mei.minekord.bot.MinekordBot
import ua.mei.minekord.bot.extension.IPCheckExtension
import ua.mei.minekord.bot.extension.MessagesExtension
import ua.mei.minekord.bot.extension.PlayerListExtension
import ua.mei.minekord.cache.IPCache
Expand Down Expand Up @@ -43,6 +44,7 @@ object Minekord : ModInitializer {
MinekordConfig.load()

MinekordBot.registerExtension(::MessagesExtension)
MinekordBot.registerExtension(::IPCheckExtension)
if (Commands.PlayerList.enabled) {
MinekordBot.registerExtension(::PlayerListExtension)
}
Expand Down
141 changes: 141 additions & 0 deletions src/main/kotlin/ua/mei/minekord/bot/extension/IPCheckExtensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package ua.mei.minekord.bot.extension

import com.mojang.authlib.GameProfile
import dev.kord.common.entity.ButtonStyle
import dev.kord.core.behavior.channel.createMessage
import dev.kord.core.entity.Member
import dev.kord.rest.builder.message.EmbedBuilder
import dev.kord.rest.builder.message.embed
import dev.kordex.core.components.ComponentContainer
import dev.kordex.core.components.components
import dev.kordex.core.components.disabledButton
import dev.kordex.core.components.publicButton
import dev.kordex.core.extensions.Extension
import dev.kordex.core.i18n.toKey
import dev.kordex.core.time.TimestampType
import dev.kordex.core.time.toDiscord
import io.ktor.util.network.address
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import ua.mei.minekord.bot.MinekordBot
import ua.mei.minekord.cache.IPCache
import ua.mei.minekord.config.MinekordConfig.Auth
import ua.mei.minekord.config.MinekordConfig.Messages
import ua.mei.minekord.config.spec.MessagesSpec
import ua.mei.minekord.event.IPCheckEvent
import ua.mei.minekord.utils.AuthUtils
import java.net.SocketAddress

class IPCheckExtension : Extension() {
override val name: String = "minekord.ipcheck"

override suspend fun setup() {
IPCheckEvent.EVENT.register { socketAddress, profile ->
if (!Auth.ipBasedLogin) return@register

MinekordBot.launch {
try {
val member: Member? = AuthUtils.findMember(profile.name)

member?.getDmChannelOrNull()?.createMessage {
embed {
title = Messages.embedTitle
addIpField(socketAddress)
addTimeField()
}
components {
addYesButton(socketAddress, profile)
addNoButton(socketAddress, profile)
}
}
} catch (e: Exception) {
println("Error handling IP check: ${e.message}")
}
}
}
}

private fun EmbedBuilder.addIpField(socketAddress: SocketAddress) {
field {
name = "> IP"
value = "> ${socketAddress.address}"
inline = true
}
}

private fun EmbedBuilder.addTimeField() {
field {
name = "> ${MessagesSpec.timeLabel}"
value = "> ${Clock.System.now().toDiscord(TimestampType.Default)}"
inline = true
}
}

private suspend fun ComponentContainer.addYesButton(socketAddress: SocketAddress, profile: GameProfile) {
publicButton {
label = Messages.yesButton.toKey()
style = ButtonStyle.Success

action {
IPCache.ipCache[profile.name] = socketAddress.address

edit {
components {
disabledButton {
label = Messages.yesButton.toKey()
style = ButtonStyle.Success
}
}
}
}
}
}

private suspend fun ComponentContainer.addNoButton(socketAddress: SocketAddress, profile: GameProfile) {
publicButton {
label = Messages.noButton.toKey()
style = ButtonStyle.Danger

action {
IPCache.blockedIps += socketAddress.address

edit {
embed {
title = Messages.ipBlockedTitle
addIpField(socketAddress)
addTimeField()
}
components {
addUnblockButton(socketAddress, profile)
}
}
}
}
}

private suspend fun ComponentContainer.addUnblockButton(socketAddress: SocketAddress, profile: GameProfile) {
publicButton {
label = Messages.unblockButton.toKey()
style = ButtonStyle.Danger

action {
IPCache.blockedIps.removeAll { it == socketAddress.address }
IPCache.alreadyRequestedIps[profile.name]?.removeAll { it == socketAddress.address }

edit {
embed {
title = Messages.ipUnblockedTitle
addIpField(socketAddress)
addTimeField()
}
components {
disabledButton {
label = Messages.unblockButton.toKey()
style = ButtonStyle.Danger
}
}
}
}
}
}
}
15 changes: 14 additions & 1 deletion src/main/kotlin/ua/mei/minekord/cache/IPCache.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ua.mei.minekord.cache
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import com.mojang.authlib.GameProfile
import io.ktor.util.network.address
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents
import net.fabricmc.loader.api.FabricLoader
Expand All @@ -15,7 +16,7 @@ import java.nio.file.Path

object IPCache : ServerLifecycleEvents.ServerStarting, ServerLifecycleEvents.ServerStopped {
var ipCache: MutableMap<String, String> = mutableMapOf()
val alreadyRequestedIps: MutableMap<String, String> = mutableMapOf()
val alreadyRequestedIps: MutableMap<String, MutableList<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>>() {}
Expand Down Expand Up @@ -43,4 +44,16 @@ object IPCache : ServerLifecycleEvents.ServerStarting, ServerLifecycleEvents.Ser
fun isBlocked(socketAddress: SocketAddress): Boolean {
return blockedIps.contains(socketAddress.address)
}

fun isRequested(address: SocketAddress, profile: GameProfile): Boolean {
if (alreadyRequestedIps[profile.name]?.contains(address.address) == true) {
return true
}
alreadyRequestedIps.getOrPut(profile.name) { mutableListOf() }.add(address.address)
return false
}

fun containsInCache(address: SocketAddress, profile: GameProfile): Boolean {
return ipCache[profile.name] == address.address
}
}
33 changes: 33 additions & 0 deletions src/main/kotlin/ua/mei/minekord/config/MinekordConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ 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.MessagesSpec
import ua.mei.minekord.config.spec.PresenceSpec
import ua.mei.minekord.utils.MinekordActivityType
import ua.mei.minekord.utils.toColor
Expand All @@ -42,6 +43,7 @@ object MinekordConfig {
addSpec(CommandsSpec)
addSpec(ColorsSpec)
addSpec(AuthSpec)
addSpec(MessagesSpec)
}.from.toml.file(FabricLoader.getInstance().configDir.resolve(CONFIG_PATH).toFile())

config.validateRequired()
Expand All @@ -52,6 +54,7 @@ object MinekordConfig {
Commands.load()
Colors.load()
Auth.load()
Messages.load()
}

private fun parseNode(text: String): TextNode {
Expand Down Expand Up @@ -244,6 +247,36 @@ object MinekordConfig {
}
}

object Messages {
lateinit var ipKickMessage: String
private set
lateinit var embedTitle: String
private set
lateinit var timeLabel: String
private set
lateinit var yesButton: String
private set
lateinit var noButton: String
private set
lateinit var unblockButton: String
private set
lateinit var ipBlockedTitle: String
private set
lateinit var ipUnblockedTitle: String
private set

fun load() {
ipKickMessage = config[MessagesSpec.ipKickMessage]
embedTitle = config[MessagesSpec.embedTitle]
timeLabel = config[MessagesSpec.timeLabel]
yesButton = config[MessagesSpec.yesButton]
noButton = config[MessagesSpec.noButton]
unblockButton = config[MessagesSpec.unblockButton]
ipBlockedTitle = config[MessagesSpec.ipBlockedTitle]
ipUnblockedTitle = config[MessagesSpec.ipUnblockedTitle]
}
}

data class DynamicNode(val key: String, val text: Text) : TextNode {
companion object {
fun of(key: String): DynamicNode {
Expand Down
14 changes: 14 additions & 0 deletions src/main/kotlin/ua/mei/minekord/config/spec/MessagesSpec.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ua.mei.minekord.config.spec

import com.uchuhimo.konf.ConfigSpec

object MessagesSpec : ConfigSpec() {
val ipKickMessage by required<String>()
val embedTitle by required<String>()
val timeLabel by required<String>()
val yesButton by required<String>()
val noButton by required<String>()
val unblockButton by required<String>()
val ipBlockedTitle by required<String>()
val ipUnblockedTitle by required<String>()
}
11 changes: 11 additions & 0 deletions src/main/resources/minekord.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,14 @@ link = "#5865F2"
snowflakeBasedUuid = false
requiredRoles = []
ipBasedLogin = false

[Messages]

ipKickMessage = "Joined with new IP! Check your DM."
embedTitle = "This is your IP?"
timeLabel = "Time"
yesButton = "Yes"
noButton = "No"
unblockButton = "Unblock"
ipBlockedTitle = "IP was blocked!"
ipUnblockedTitle = "IP was unblocked!"

0 comments on commit 7ece993

Please sign in to comment.