diff --git a/src/main/java/com/dqu/simplerauth/AuthMod.java b/src/main/java/com/dqu/simplerauth/AuthMod.java index 2756098..ef003b6 100644 --- a/src/main/java/com/dqu/simplerauth/AuthMod.java +++ b/src/main/java/com/dqu/simplerauth/AuthMod.java @@ -1,28 +1,69 @@ package com.dqu.simplerauth; import com.dqu.simplerauth.commands.*; -import com.dqu.simplerauth.managers.ConfigManager; -import com.dqu.simplerauth.managers.DbManager; -import com.dqu.simplerauth.managers.LangManager; -import com.dqu.simplerauth.managers.PlayerManager; +import com.dqu.simplerauth.managers.*; +import com.google.gson.Gson; +import com.google.gson.JsonObject; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import javax.net.ssl.HttpsURLConnection; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; public class AuthMod implements ModInitializer { public static final Logger LOGGER = LogManager.getLogger(); + private static final Gson GSON = new Gson(); public static PlayerManager playerManager = new PlayerManager(); @Override public void onInitialize() { ConfigManager.loadConfig(); // Loads config file DbManager.loadDatabase(); // Loads password database + CacheManager.loadCache(); // Loads cache LangManager.loadTranslations("en"); // Loads translations CommandRegistrationCallback.EVENT.register(((dispatcher, dedicated) -> { LoginCommand.registerCommand(dispatcher); RegisterCommand.registerCommand(dispatcher); ChangePasswordCommand.registerCommand(dispatcher); + OnlineAuthCommand.registerCommand(dispatcher); })); } + + public static boolean doesMinecraftAccountExist(String username) { + String content = ""; + if (CacheManager.getMinecraftAccount(username) != null) { + return true; + } + + try { + HttpsURLConnection connection = (HttpsURLConnection) new URL("https://api.mojang.com/users/profiles/minecraft/" + username).openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + + int response = connection.getResponseCode(); + if (response == HttpURLConnection.HTTP_OK) { + InputStream stream = connection.getInputStream(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream)); + StringBuilder stringBuilder = new StringBuilder(); + String out; + while ((out = bufferedReader.readLine()) != null) { + stringBuilder.append(out); + } + content = stringBuilder.toString(); + } else return false; + } catch (Exception e) { + AuthMod.LOGGER.error(e); + return false; + } + + JsonObject jsonObject = GSON.fromJson(content, JsonObject.class); + CacheManager.addMinecraftAccount(username, jsonObject.get("id").getAsString()); + return true; + } } diff --git a/src/main/java/com/dqu/simplerauth/commands/OnlineAuthCommand.java b/src/main/java/com/dqu/simplerauth/commands/OnlineAuthCommand.java new file mode 100644 index 0000000..8ff7647 --- /dev/null +++ b/src/main/java/com/dqu/simplerauth/commands/OnlineAuthCommand.java @@ -0,0 +1,132 @@ +package com.dqu.simplerauth.commands; + +import com.dqu.simplerauth.AuthMod; +import com.dqu.simplerauth.listeners.OnOnlineAuthChanged; +import com.dqu.simplerauth.managers.ConfigManager; +import com.dqu.simplerauth.managers.DbManager; +import com.dqu.simplerauth.managers.LangManager; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import org.jetbrains.annotations.Nullable; + +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; + +public class OnlineAuthCommand { + public static void registerCommand(CommandDispatcher dispatcher) { + dispatcher.register(literal("onlineauth") + .executes(ctx -> { + ServerCommandSource source = ctx.getSource(); + ServerPlayerEntity player = source.getPlayer(); + executeEnable(player, source, null); + return 1; + }) + .then(literal("enable") + .then(argument("password", StringArgumentType.word()) + .executes(ctx -> { + String password = StringArgumentType.getString(ctx, "password"); + ServerCommandSource source = ctx.getSource(); + ServerPlayerEntity player = source.getPlayer(); + executeEnable(player, source, password); + return 1; + }) + ) + ) + .then(literal("disable") + .executes(ctx -> { + ServerCommandSource source = ctx.getSource(); + ServerPlayerEntity player = source.getPlayer(); + executeDisable(player, source, null); + return 1; + }) + .then(argument("password", StringArgumentType.word()) + .executes(ctx -> { + String password = StringArgumentType.getString(ctx, "password"); + ServerCommandSource source = ctx.getSource(); + ServerPlayerEntity player = source.getPlayer(); + executeDisable(player, source, password); + return 1; + }) + ) + ) + ); + } + + private static void executeEnable(ServerPlayerEntity player, ServerCommandSource source, @Nullable String password) { + String username = player.getEntityName(); + + if (checkFeatureDisabled(source)) { + return; + } + + if (!DbManager.isPlayerRegistered(username)) { + source.sendFeedback(LangManager.getLiteralText("command.onlineauth.notregistered"), false); + return; + } else if (DbManager.getUseOnlineAuth(username)) { + source.sendFeedback(LangManager.getLiteralText("command.onlineauth.alreadyenabled"), false); + return; + } else if (!AuthMod.doesMinecraftAccountExist(username)) { + source.sendFeedback(LangManager.getLiteralText("command.onlineauth.cannotenable"), false); + return; + } + + if (password != null) { + if (DbManager.isPasswordCorrect(username, password)) { + DbManager.setUseOnlineAuth(username, true); + OnOnlineAuthChanged.onEnabled(player); + source.sendFeedback(LangManager.getLiteralText("command.onlineauth.enabled"), false); + } else { + source.sendFeedback(LangManager.getLiteralText("command.general.notmatch"), false); + } + } else { + source.sendFeedback(LangManager.getLiteralText("command.onlineauth.warning"), false); + source.sendFeedback(LangManager.getLiteralText("command.onlineauth.confirmenable"), false); + } + } + + private static void executeDisable(ServerPlayerEntity player, ServerCommandSource source, @Nullable String password) { + String username = player.getEntityName(); + + if (checkFeatureDisabled(source)) { + return; + } + + if (!DbManager.isPlayerRegistered(username)) { + source.sendFeedback(LangManager.getLiteralText("command.onlineauth.notregistered"), false); + return; + } else if (!DbManager.getUseOnlineAuth(username)) { + source.sendFeedback(LangManager.getLiteralText("command.onlineauth.alreadydisabled"), false); + return; + } + + if (password != null) { + if (DbManager.isPasswordCorrect(username, password)) { + DbManager.setUseOnlineAuth(username, false); + OnOnlineAuthChanged.onDisabled(player); + source.sendFeedback(LangManager.getLiteralText("command.onlineauth.disabled"), false); + } else { + source.sendFeedback(LangManager.getLiteralText("command.general.notmatch"), false); + } + } else { + source.sendFeedback(LangManager.getLiteralText("command.onlineauth.warning"), false); + source.sendFeedback(LangManager.getLiteralText("command.onlineauth.confirmdisable"), false); + } + } + + private static boolean checkFeatureDisabled(ServerCommandSource source) { + String authtype = ConfigManager.getAuthType(); + boolean optionalOnlineAuth = ConfigManager.getBoolean("optional-online-auth"); + + if (authtype.equals("global")) { + source.sendFeedback(LangManager.getLiteralText("command.onlineauth.globaltype"), false); + return true; + } else if (!optionalOnlineAuth) { + source.sendFeedback(LangManager.getLiteralText("command.onlineauth.featuredisabled"), false); + return true; + } + + return false; + } +} diff --git a/src/main/java/com/dqu/simplerauth/listeners/OnOnlineAuthChanged.java b/src/main/java/com/dqu/simplerauth/listeners/OnOnlineAuthChanged.java new file mode 100644 index 0000000..509ea10 --- /dev/null +++ b/src/main/java/com/dqu/simplerauth/listeners/OnOnlineAuthChanged.java @@ -0,0 +1,102 @@ +package com.dqu.simplerauth.listeners; + +import com.dqu.simplerauth.AuthMod; +import com.dqu.simplerauth.managers.CacheManager; +import com.google.gson.JsonObject; +import com.mojang.authlib.GameProfile; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.*; +import net.minecraft.server.network.ServerPlayerEntity; + +import java.io.IOException; +import java.util.UUID; + +public class OnOnlineAuthChanged { + public static void onEnabled(ServerPlayerEntity player) { + String username = player.getEntityName(); + MinecraftServer server = player.getServer(); + if (server == null) return; // Shouldn't happen + PlayerManager playerManager = server.getPlayerManager(); + + // Update uuids + JsonObject cachedAccount = CacheManager.getMinecraftAccount(username); + if (cachedAccount == null) { + return; // Shouldn't happen + } + + String onlineUuid = cachedAccount.get("online-uuid").getAsString(); + // UUID.fromString doesn't work on a uuid without dashes, we need to add them back before creating the UUID + onlineUuid = onlineUuid.replaceAll("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"); + + GameProfile onlineProfile = new GameProfile(UUID.fromString(onlineUuid), username); + GameProfile offlineProfile = player.getGameProfile(); // The player is offline when this is executed + + // Update ops uuid + OperatorList ops = playerManager.getOpList(); + OperatorEntry opEntry = ops.get(offlineProfile); + if (opEntry != null) { + ops.remove(offlineProfile); + ops.add(new OperatorEntry(onlineProfile, opEntry.getPermissionLevel(), opEntry.canBypassPlayerLimit())); + try { + ops.save(); + } catch (IOException e) { + AuthMod.LOGGER.error("Failed to save updated operator list for username '{}', who enabled online authentication", username, e); + } + } + + if (playerManager.isWhitelistEnabled()) { + // Update whitelist uuid + Whitelist whitelist = playerManager.getWhitelist(); + + if (whitelist.isAllowed(offlineProfile)) { + whitelist.remove(offlineProfile); + whitelist.add(new WhitelistEntry(onlineProfile)); + try { + whitelist.save(); + } catch (IOException e) { + AuthMod.LOGGER.error("Failed to save updated whitelist for username '{}', who enabled online authentication", username, e); + } + } + } + } + + public static void onDisabled(ServerPlayerEntity player) { + String username = player.getEntityName(); + MinecraftServer server = player.getServer(); + if (server == null) return; // Shouldn't happen + PlayerManager playerManager = server.getPlayerManager(); + + // Update uuids + UUID offlineUuid = PlayerEntity.getOfflinePlayerUuid(username); + GameProfile offlineProfile = new GameProfile(offlineUuid, username); + GameProfile onlineProfile = player.getGameProfile(); // The player is online when this is executed + + // Update ops uuid + OperatorList ops = playerManager.getOpList(); + OperatorEntry opEntry = ops.get(onlineProfile); + if (opEntry != null) { + ops.remove(onlineProfile); + ops.add(new OperatorEntry(offlineProfile, opEntry.getPermissionLevel(), opEntry.canBypassPlayerLimit())); + try { + ops.save(); + } catch (IOException e) { + AuthMod.LOGGER.error("Failed to save updated operator list for username '{}', who disabled online authentication", username, e); + } + } + + if (playerManager.isWhitelistEnabled()) { + // Update whitelist uuid + Whitelist whitelist = playerManager.getWhitelist(); + + if (whitelist.isAllowed(onlineProfile)) { + whitelist.remove(onlineProfile); + whitelist.add(new WhitelistEntry(offlineProfile)); + try { + whitelist.save(); + } catch (IOException e) { + AuthMod.LOGGER.error("Failed to save updated whitelist for username '{}', who disabled online authentication", username, e); + } + } + } + } +} diff --git a/src/main/java/com/dqu/simplerauth/listeners/OnPlayerConnect.java b/src/main/java/com/dqu/simplerauth/listeners/OnPlayerConnect.java index 7345cb2..d4c1a7c 100644 --- a/src/main/java/com/dqu/simplerauth/listeners/OnPlayerConnect.java +++ b/src/main/java/com/dqu/simplerauth/listeners/OnPlayerConnect.java @@ -2,6 +2,7 @@ import com.dqu.simplerauth.AuthMod; import com.dqu.simplerauth.PlayerObject; +import com.dqu.simplerauth.managers.CacheManager; import com.dqu.simplerauth.managers.ConfigManager; import com.dqu.simplerauth.managers.DbManager; import com.dqu.simplerauth.managers.LangManager; @@ -27,10 +28,13 @@ public static void listen(ServerPlayerEntity player) { player.stopRiding(); player.sendMessage(LangManager.getLiteralText("player.connect.authenticate"), false); - boolean skiponline = ConfigManager.getBoolean("skip-online-auth"); - if (skiponline && testPlayerOnline(player) && DbManager.isPlayerRegistered(player.getEntityName())) { + boolean forcedOnlineAuth = ConfigManager.getBoolean("forced-online-auth"); + boolean optionalOnlineAuth = ConfigManager.getBoolean("optional-online-auth"); + // Forced online authentication does not require registration + if ((forcedOnlineAuth || (optionalOnlineAuth && DbManager.isPlayerRegistered(player.getEntityName()))) && testPlayerOnline(player)) { PlayerObject playerObject = AuthMod.playerManager.get(player); playerObject.authenticate(); + if (!player.isCreative()) player.setInvulnerable(false); player.sendMessage(LangManager.getLiteralText("command.general.authenticated"), false); AuthMod.LOGGER.info(player.getEntityName() + " is using an online account, authenticated automatically."); return; @@ -41,6 +45,7 @@ public static void listen(ServerPlayerEntity player) { if (DbManager.sessionVerify(player.getEntityName(), player.getIp())) { PlayerObject playerObject = AuthMod.playerManager.get(player); playerObject.authenticate(); + if (!player.isCreative()) player.setInvulnerable(false); DbManager.sessionCreate(player.getEntityName(), player.getIp()); player.sendMessage(LangManager.getLiteralText("command.general.authenticated"), false); } else { @@ -57,31 +62,40 @@ public static boolean testPlayerOnline(ServerPlayerEntity player) { Matcher matcher = pattern.matcher(uuid); if (!matcher.matches()) return false; - String content = ""; - try { - HttpsURLConnection connection = (HttpsURLConnection) new URL("https://api.mojang.com/users/profiles/minecraft/" + player.getEntityName()).openConnection(); - connection.setRequestMethod("GET"); - connection.setConnectTimeout(5000); - connection.setReadTimeout(5000); + String realUuid; - int response = connection.getResponseCode(); - if (response == HttpURLConnection.HTTP_OK) { - InputStream stream = connection.getInputStream(); - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream)); - StringBuilder stringBuilder = new StringBuilder(); - String out; - while ((out = bufferedReader.readLine()) != null) { - stringBuilder.append(out); - } - content = stringBuilder.toString(); - } else return false; - } catch (Exception e) { - AuthMod.LOGGER.error(e); - return false; - } + JsonObject cachedAccount = CacheManager.getMinecraftAccount(player.getEntityName()); + if (cachedAccount == null) { + String content = ""; + try { + HttpsURLConnection connection = (HttpsURLConnection) new URL("https://api.mojang.com/users/profiles/minecraft/" + player.getEntityName()).openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + + int response = connection.getResponseCode(); + if (response == HttpURLConnection.HTTP_OK) { + InputStream stream = connection.getInputStream(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream)); + StringBuilder stringBuilder = new StringBuilder(); + String out; + while ((out = bufferedReader.readLine()) != null) { + stringBuilder.append(out); + } + content = stringBuilder.toString(); + } else return false; + } catch (Exception e) { + AuthMod.LOGGER.error(e); + return false; + } - JsonObject jsonObject = GSON.fromJson(content, JsonObject.class); - String realUuid = jsonObject.get("id").getAsString().toLowerCase(); + JsonObject jsonObject = GSON.fromJson(content, JsonObject.class); + realUuid = jsonObject.get("id").getAsString().toLowerCase(); + + CacheManager.addMinecraftAccount(player.getEntityName(), realUuid); + } else { + realUuid = cachedAccount.get("online-uuid").getAsString(); + } return uuid.equals(realUuid); } } diff --git a/src/main/java/com/dqu/simplerauth/listeners/OnPlayerLogin.java b/src/main/java/com/dqu/simplerauth/listeners/OnPlayerLogin.java new file mode 100644 index 0000000..c0af69a --- /dev/null +++ b/src/main/java/com/dqu/simplerauth/listeners/OnPlayerLogin.java @@ -0,0 +1,19 @@ +package com.dqu.simplerauth.listeners; + +import com.dqu.simplerauth.AuthMod; +import com.dqu.simplerauth.managers.ConfigManager; +import com.dqu.simplerauth.managers.DbManager; +import net.minecraft.server.MinecraftServer; + +public class OnPlayerLogin { + public static boolean canUseOnlineAuth(MinecraftServer server, String username) { + boolean forcedOnlineAuth = ConfigManager.getBoolean("forced-online-auth"); + boolean optionalOnlineAuth = ConfigManager.getBoolean("optional-online-auth"); + if (!server.isOnlineMode() || (!forcedOnlineAuth && !optionalOnlineAuth)) { + return false; + } + + if (forcedOnlineAuth) return !ConfigManager.forcePlayerOffline(username) && AuthMod.doesMinecraftAccountExist(username); + else return DbManager.isPlayerRegistered(username) && DbManager.getUseOnlineAuth(username); + } +} diff --git a/src/main/java/com/dqu/simplerauth/managers/CacheManager.java b/src/main/java/com/dqu/simplerauth/managers/CacheManager.java new file mode 100644 index 0000000..63e001e --- /dev/null +++ b/src/main/java/com/dqu/simplerauth/managers/CacheManager.java @@ -0,0 +1,106 @@ +package com.dqu.simplerauth.managers; + +import com.dqu.simplerauth.AuthMod; +import com.google.common.collect.Sets; +import com.google.common.io.Files; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import net.fabricmc.loader.api.FabricLoader; +import org.jetbrains.annotations.Nullable; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Collection; + +public class CacheManager { + private static final int VERSION = 1; + private static final Gson GSON = new Gson(); + private static final String PATH = FabricLoader.getInstance().getConfigDir().resolve("simplerauth-cache.json").toString(); + private static final File DBFILE = new File(PATH); + private static JsonObject db = new JsonObject(); + + public static void loadCache() { + if (!DBFILE.exists()) { + db.addProperty("version", VERSION); + + db.add("minecraft-account-cache", new JsonArray()); + + saveCache(); + } + + try { + BufferedReader bufferedReader = Files.newReader(DBFILE, StandardCharsets.UTF_8); + db = GSON.fromJson(bufferedReader, JsonObject.class); + } catch (Exception e) { + AuthMod.LOGGER.error(e); + } + } + + private static void saveCache() { + try { + BufferedWriter bufferedWriter = Files.newWriter(DBFILE, StandardCharsets.UTF_8); + String json = GSON.toJson(db); + bufferedWriter.write(json); + bufferedWriter.close(); + } catch (Exception e) { + AuthMod.LOGGER.error(e); + } + } + + public static JsonArray getMinecraftAccounts() { + return db.get("minecraft-account-cache").getAsJsonArray(); + } + + @Nullable + public static JsonObject getMinecraftAccount(String username) { + removeExpired(); + JsonArray minecraftAccounts = getMinecraftAccounts(); + for (int i = 0; i < minecraftAccounts.size(); ++i) { + JsonObject account = minecraftAccounts.get(i).getAsJsonObject(); + if (account.get("username").getAsString().equals(username)) { + return account; + } + } + + return null; + } + + public static void addMinecraftAccount(String username, String onlineUuid) { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("username", username); + jsonObject.addProperty("online-uuid", onlineUuid); + jsonObject.addProperty("timestamp", LocalDateTime.now().toString()); + + getMinecraftAccounts().add(jsonObject); + saveCache(); + } + + private static void removeExpired() { + JsonArray minecraftAccounts = getMinecraftAccounts(); + Collection invalidAccounts = Sets.newHashSet(); + for (int i = 0; i < minecraftAccounts.size(); ++i) { + JsonObject account = minecraftAccounts.get(i).getAsJsonObject(); + LocalDateTime parsedTimestamp; + try { + parsedTimestamp = LocalDateTime.parse(account.get("timestamp").getAsString()); + } catch (Exception e) { + continue; + } + boolean valid = Duration.between(parsedTimestamp, LocalDateTime.now()).toDays() <= 14; + if (!valid) { + invalidAccounts.add(account); + } + } + + for (JsonObject account : invalidAccounts) { + minecraftAccounts.remove(account); + } + + saveCache(); + } +} diff --git a/src/main/java/com/dqu/simplerauth/managers/ConfigManager.java b/src/main/java/com/dqu/simplerauth/managers/ConfigManager.java index 2842e88..85ab62b 100644 --- a/src/main/java/com/dqu/simplerauth/managers/ConfigManager.java +++ b/src/main/java/com/dqu/simplerauth/managers/ConfigManager.java @@ -4,6 +4,7 @@ import com.google.common.io.Files; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; import com.google.gson.JsonObject; import net.fabricmc.loader.api.FabricLoader; @@ -11,9 +12,10 @@ import java.io.BufferedWriter; import java.io.File; import java.nio.charset.StandardCharsets; +import java.util.Locale; public class ConfigManager { - public static final int VERSION = 1; + public static final int VERSION = 2; private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); private static final String PATH = FabricLoader.getInstance().getConfigDir().resolve("simplerauth-config.json").toString(); private static final File DBFILE = new File(PATH); @@ -25,9 +27,11 @@ public static void loadConfig() { db.addProperty("sessions-enabled", true); db.addProperty("sessions-valid-hours", "6"); - db.addProperty("skip-online-auth", true); db.addProperty("password-type", "local"); db.addProperty("global-password", "123456"); + db.addProperty("forced-online-auth", false); + db.addProperty("optional-online-auth", true); + db.add("forced-offline-users", new JsonArray()); saveDatabase(); } @@ -38,6 +42,8 @@ public static void loadConfig() { } catch (Exception e) { AuthMod.LOGGER.error(e); } + + if (db.get("version").getAsInt() != VERSION) convertDatabase(db.get("version").getAsInt()); } private static void saveDatabase() { @@ -108,4 +114,29 @@ public static String getAuthType() { return authtype.equalsIgnoreCase("global") ? "global" : "none"; } + + public static boolean forcePlayerOffline(String username) { + if (db.get("forced-offline-users").getAsJsonArray().size() == 0) return false; + JsonArray forcedOfflineUsers = db.get("forced-offline-users").getAsJsonArray(); + for (int i = 0; i < forcedOfflineUsers.size(); ++i) { + String user = forcedOfflineUsers.get(i).getAsString().toLowerCase(Locale.ROOT); + if (user.matches(username.toLowerCase(Locale.ROOT))) return true; + } + + return false; + } + + private static void convertDatabase(int version) { + if (version == 1) { + db.addProperty("version", VERSION); + boolean skipOnlineAuth = db.get("skip-online-auth").getAsBoolean(); + db.remove("skip-online-auth"); + db.addProperty("forced-online-auth", false); + db.addProperty("optional-online-auth", skipOnlineAuth); + db.add("forced-offline-users", new JsonArray()); + + AuthMod.LOGGER.info("[SimplerAuth] Updated outdated config."); + saveDatabase(); + } + } } diff --git a/src/main/java/com/dqu/simplerauth/managers/DbManager.java b/src/main/java/com/dqu/simplerauth/managers/DbManager.java index 2d92917..2bc45b5 100644 --- a/src/main/java/com/dqu/simplerauth/managers/DbManager.java +++ b/src/main/java/com/dqu/simplerauth/managers/DbManager.java @@ -127,6 +127,19 @@ public static void setPassword(String username, String password) { saveDatabase(); } + public static void setUseOnlineAuth(String username, boolean onlineAuth) { + JsonObject user = getPlayer(username); + if (user == null) return; + user.addProperty("online-auth", onlineAuth); + saveDatabase(); + } + + public static boolean getUseOnlineAuth(String username) { + JsonObject user = getPlayer(username); + if (user == null || !user.has("online-auth")) return false; + return user.get("online-auth").getAsBoolean(); + } + private static void convertDatabase(int version) { if (version == -1) { // Convert the old database to a new format. diff --git a/src/main/java/com/dqu/simplerauth/managers/LangManager.java b/src/main/java/com/dqu/simplerauth/managers/LangManager.java index 2220a3d..b41217b 100644 --- a/src/main/java/com/dqu/simplerauth/managers/LangManager.java +++ b/src/main/java/com/dqu/simplerauth/managers/LangManager.java @@ -30,6 +30,17 @@ public static void loadTranslations(String language) { lang.put("command.register.globaltype", "§cНа сервере установлен глобальный пароль, регистрация невозможна! Используйте §f/login"); lang.put("command.changepassword.notregistered", "§cВы не зарегестрированы!"); lang.put("command.changepassword.success", "§aВы успешно изменили пароль!"); + lang.put("command.onlineauth.globaltype", "§cGlobal password is set on this server, online auth is not possible! Use §f/login"); + lang.put("command.onlineauth.notregistered", "§cYou are not registered! Use §f/register §cbefore enabling online auth."); + lang.put("command.onlineauth.featuredisabled", "§cOptional online auth is not enabled in this server!"); + lang.put("command.onlineauth.warning", "§cYou will lose all your data linked to your UUID, like your inventory & ender chest, statistics, advancements, villager discounts, etc."); + lang.put("command.onlineauth.cannotenable", "§cAn official account with this username does not exist! You can't enable this feature"); + lang.put("command.onlineauth.confirmenable", "§6To confirm, use §f/onlineauth enable §6. You can disable it with §f/onlineauth disable§6."); + lang.put("command.onlineauth.alreadyenabled", "§cAlready enabled!."); + lang.put("command.onlineauth.enabled", "§aOnline auth has been enabled for your account. Reconnect to the server to apply the changes."); + lang.put("command.onlineauth.confirmdisable", "§6To confirm, use §f/onlineauth disable §6. You can enable it again with §f/onlineauth§6."); + lang.put("command.onlineauth.alreadydisabled", "§cAlready disabled!."); + lang.put("command.onlineauth.disabled", "§aOnline auth has been disabled for your account. Reconnect to the server to apply the changes."); lang.put("player.connect.authenticate", "§6Пожалуйста, войдите! Используйте §f/login §6или §f/register§6."); lang.put("command.general.authenticated", "§aВы успешно вошли!"); lang.put("command.general.notmatch", "§cПароли не совпадают!"); @@ -42,6 +53,17 @@ public static void loadTranslations(String language) { lang.put("command.register.globaltype", "§cGlobal password is set on this server, registration is not possible! Use §f/login"); lang.put("command.changepassword.notregistered", "§cYou are not registered!"); lang.put("command.changepassword.success", "§aChanged password successfully!"); + lang.put("command.onlineauth.globaltype", "§cGlobal password is set on this server, online auth is not possible! Use §f/login"); + lang.put("command.onlineauth.notregistered", "§cYou are not registered! Use §f/register §cbefore enabling online auth."); + lang.put("command.onlineauth.featuredisabled", "§cOptional online auth is not enabled in this server!"); + lang.put("command.onlineauth.warning", "§cYou will lose all your data linked to your UUID, like your inventory & ender chest, statistics, advancements, villager discounts, etc."); + lang.put("command.onlineauth.cannotenable", "§cAn official account with this username does not exist! You can't enable this feature"); + lang.put("command.onlineauth.confirmenable", "§6To confirm, use §f/onlineauth enable §6. You can disable it with §f/onlineauth disable§6."); + lang.put("command.onlineauth.alreadyenabled", "§cAlready enabled!."); + lang.put("command.onlineauth.enabled", "§aOnline auth has been enabled for your account. Reconnect to the server to apply the changes."); + lang.put("command.onlineauth.confirmdisable", "§6To confirm, use §f/onlineauth disable §6. You can enable it again with §f/onlineauth§6."); + lang.put("command.onlineauth.alreadydisabled", "§cAlready disabled!."); + lang.put("command.onlineauth.disabled", "§aOnline auth has been disabled for your account. Reconnect to the server to apply the changes."); lang.put("player.connect.authenticate", "§6Please, authenticate! Use §f/login §6or §f/register§6."); lang.put("command.general.authenticated", "§aAuthenticated successfully!"); lang.put("command.general.notmatch", "§cPasswords do not match!"); diff --git a/src/main/java/com/dqu/simplerauth/mixin/ServerConfigHandlerMixin.java b/src/main/java/com/dqu/simplerauth/mixin/ServerConfigHandlerMixin.java new file mode 100644 index 0000000..6199670 --- /dev/null +++ b/src/main/java/com/dqu/simplerauth/mixin/ServerConfigHandlerMixin.java @@ -0,0 +1,62 @@ +package com.dqu.simplerauth.mixin; + +import com.dqu.simplerauth.AuthMod; +import com.dqu.simplerauth.managers.ConfigManager; +import com.dqu.simplerauth.managers.DbManager; +import com.google.common.collect.Lists; +import com.mojang.authlib.Agent; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.ProfileLookupCallback; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.ServerConfigHandler; +import net.minecraft.util.ChatUtil; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +@Mixin(ServerConfigHandler.class) +public class ServerConfigHandlerMixin { + // This one may not be necessary, it is only used in conversion of old server files + @Inject(method = "lookupProfile", at = @At(value = "HEAD"), cancellable = true) + private static void lookupProfile(MinecraftServer server, Collection bannedPlayers, ProfileLookupCallback callback, CallbackInfo ci) { + boolean forcedOnlineAuth = ConfigManager.getBoolean("forced-online-auth"); + boolean optionalOnlineAuth = ConfigManager.getBoolean("optional-online-auth"); + if (!server.isOnlineMode() || (!forcedOnlineAuth && !optionalOnlineAuth)) { + return; + } + + ci.cancel(); + List onlineBannedPlayers = Lists.newArrayList(); + for (String player : bannedPlayers) { + if (!ChatUtil.isEmpty(player)) { + boolean onlinePlayer = forcedOnlineAuth ? AuthMod.doesMinecraftAccountExist(player) : DbManager.getUseOnlineAuth(player); + if (onlinePlayer) onlineBannedPlayers.add(player); + else { + UUID uuid = PlayerEntity.getOfflinePlayerUuid(player); + GameProfile profile = new GameProfile(uuid, player); + callback.onProfileLookupSucceeded(profile); + } + } + } + + server.getGameProfileRepo().findProfilesByNames(onlineBannedPlayers.toArray(new String[0]), Agent.MINECRAFT, callback); + } + + @Redirect(method = "getPlayerUuidByName", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;isOnlineMode()Z")) + private static boolean useOnlineModeForPlayer(MinecraftServer server, MinecraftServer server1, String name) { + boolean forcedOnlineAuth = ConfigManager.getBoolean("forced-online-auth"); + boolean optionalOnlineAuth = ConfigManager.getBoolean("optional-online-auth"); + if (!server.isOnlineMode() || (!forcedOnlineAuth && !optionalOnlineAuth)) { + return false; + } + + if (forcedOnlineAuth) return AuthMod.doesMinecraftAccountExist(name); + else return DbManager.getUseOnlineAuth(name); + } +} diff --git a/src/main/java/com/dqu/simplerauth/mixin/ServerLoginNetworkHandlerMixin.java b/src/main/java/com/dqu/simplerauth/mixin/ServerLoginNetworkHandlerMixin.java new file mode 100644 index 0000000..4203610 --- /dev/null +++ b/src/main/java/com/dqu/simplerauth/mixin/ServerLoginNetworkHandlerMixin.java @@ -0,0 +1,22 @@ +package com.dqu.simplerauth.mixin; + +import com.dqu.simplerauth.listeners.OnPlayerLogin; +import com.mojang.authlib.GameProfile; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerLoginNetworkHandler; +import org.jetbrains.annotations.Nullable; +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.Redirect; + +@Mixin(ServerLoginNetworkHandler.class) +public class ServerLoginNetworkHandlerMixin { + @Shadow @Nullable GameProfile profile; + + @Redirect(method = "onHello", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;isOnlineMode()Z")) + private boolean useOnlineAuth(MinecraftServer server) { + //noinspection ConstantConditions - this.profile was set just before + return OnPlayerLogin.canUseOnlineAuth(server, this.profile.getName()); + } +} diff --git a/src/main/java/com/dqu/simplerauth/mixin/UserCacheMixin.java b/src/main/java/com/dqu/simplerauth/mixin/UserCacheMixin.java new file mode 100644 index 0000000..12e2d36 --- /dev/null +++ b/src/main/java/com/dqu/simplerauth/mixin/UserCacheMixin.java @@ -0,0 +1,36 @@ +package com.dqu.simplerauth.mixin; + +import com.dqu.simplerauth.AuthMod; +import com.dqu.simplerauth.managers.ConfigManager; +import com.dqu.simplerauth.managers.DbManager; +import com.mojang.authlib.GameProfileRepository; +import net.minecraft.util.UserCache; +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.Redirect; + +@Mixin(UserCache.class) +public abstract class UserCacheMixin { + @Shadow + private static boolean shouldUseRemote() { + return false; + } + + // Without this, there might be issues with offline players + @SuppressWarnings("target") + @Redirect(method = { + "method_14509(Lcom/mojang/authlib/GameProfileRepository;Ljava/lang/String;)Lcom/mojang/authlib/GameProfile;", + "method_14509(Lcom/mojang/authlib/GameProfileRepository;Ljava/lang/String;)Ljava/util/Optional;" + }, at = @At(value = "INVOKE", target = "Lnet/minecraft/util/UserCache;shouldUseRemote()Z")) + private static boolean useOnlineUuid(GameProfileRepository repository, String name) { + boolean forcedOnlineAuth = ConfigManager.getBoolean("forced-online-auth"); + boolean optionalOnlineAuth = ConfigManager.getBoolean("optional-online-auth"); + if (!shouldUseRemote() || (!forcedOnlineAuth && !optionalOnlineAuth)) { + return false; + } + + if (forcedOnlineAuth) return AuthMod.doesMinecraftAccountExist(name); + else return DbManager.getUseOnlineAuth(name); + } +} diff --git a/src/main/resources/simplerauth.mixins.json b/src/main/resources/simplerauth.mixins.json index a875c83..977abdf 100755 --- a/src/main/resources/simplerauth.mixins.json +++ b/src/main/resources/simplerauth.mixins.json @@ -7,7 +7,10 @@ ], "server": [ "PlayerManagerMixin", - "ServerPlayNetworkHandlerMixin" + "ServerConfigHandlerMixin", + "ServerLoginNetworkHandlerMixin", + "ServerPlayNetworkHandlerMixin", + "UserCacheMixin" ], "injectors": { "defaultRequire": 1