From eb0c587080ca93ca4d9b81b140c565374224a6ba Mon Sep 17 00:00:00 2001 From: Dmitri Ponomarev Date: Sun, 8 Dec 2024 16:17:05 +0700 Subject: [PATCH 1/7] Replace com.j256.ormlite with HikariCP --- build.gradle | 10 +- lombok.config | 1 + .../net/elytrium/limboauth/LimboAuth.java | 274 ++--- .../java/net/elytrium/limboauth/Settings.java | 21 +- .../backend/type/LongDatabaseEndpoint.java | 2 +- .../backend/type/StringDatabaseEndpoint.java | 2 +- .../command/ChangePasswordCommand.java | 29 +- .../command/ForceChangePasswordCommand.java | 31 +- .../command/ForceRegisterCommand.java | 25 +- .../command/ForceUnregisterCommand.java | 26 +- .../limboauth/command/PremiumCommand.java | 25 +- .../limboauth/command/TotpCommand.java | 48 +- .../limboauth/command/UnregisterCommand.java | 25 +- .../elytrium/limboauth/data/DataProvider.java | 43 + .../dependencies/DatabaseLibrary.java | 184 ---- .../HikariRegisteredPlayerRepository.java | 381 +++++++ .../limboauth/handler/AuthSessionHandler.java | 989 +++++++++--------- .../limboauth/listener/AuthListener.java | 43 +- ...n.java => DataAccessRuntimeException.java} | 8 +- .../limboauth/model/RegisteredPlayer.java | 280 ++--- .../RegisteredPlayerRepository.java | 52 + .../exception/DataAccessException.java | 12 + 22 files changed, 1297 insertions(+), 1214 deletions(-) create mode 100644 lombok.config create mode 100644 src/main/java/net/elytrium/limboauth/data/DataProvider.java delete mode 100644 src/main/java/net/elytrium/limboauth/dependencies/DatabaseLibrary.java create mode 100644 src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java rename src/main/java/net/elytrium/limboauth/model/{SQLRuntimeException.java => DataAccessRuntimeException.java} (79%) create mode 100644 src/main/java/net/elytrium/limboauth/repository/RegisteredPlayerRepository.java create mode 100644 src/main/java/net/elytrium/limboauth/repository/exception/DataAccessException.java diff --git a/build.gradle b/build.gradle index afbb5ed1..71b100b1 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ plugins { } setGroup("net.elytrium") -setVersion("1.1.14") +setVersion("1.2.0-SNAPSHOT-1") java { setSourceCompatibility(JavaVersion.VERSION_17) @@ -53,6 +53,9 @@ dependencies { annotationProcessor("com.velocitypowered:velocity-api:$velocityVersion") compileOnly("com.velocitypowered:velocity-proxy:$velocityVersion") // From Elytrium Repo. + compileOnly 'org.projectlombok:lombok:1.18.36' + annotationProcessor 'org.projectlombok:lombok:1.18.36' + // Needs for some velocity methods. compileOnly("io.netty:netty-codec:$nettyVersion") compileOnly("io.netty:netty-handler:$nettyVersion") @@ -62,8 +65,9 @@ dependencies { implementation("at.favre.lib:bcrypt:0.9.0") implementation("dev.samstevens.totp:totp:1.7.1") - implementation("com.j256.ormlite:ormlite-jdbc:6.1") implementation("de.mkammerer:argon2-jvm-nolibs:2.11") + implementation("com.zaxxer:HikariCP:6.2.1") + implementation("io.whitfin:siphash:2.0.0") @@ -88,7 +92,6 @@ shadowJar { exclude("META-INF/*.txt") exclude("google/protobuf/**") exclude("com/google/protobuf/**") - exclude("com/j256/ormlite/**/*.txt") exclude("com/mysql/cj/x/**") exclude("com/mysql/cj/xdevapi/**") exclude("com/sun/jna/aix-ppc*/**") @@ -110,7 +113,6 @@ shadowJar { minimize() relocate("at.favre.lib", "net.elytrium.limboauth.thirdparty.at.favre.lib") - relocate("com.j256.ormlite", "net.elytrium.limboauth.thirdparty.com.j256.ormlite") relocate("com.sun.jna", "net.elytrium.limboauth.thirdparty.com.sun.jna") { exclude("com.sun.jna.Native") // For compatibility with native methods. } diff --git a/lombok.config b/lombok.config new file mode 100644 index 00000000..04e1ae91 --- /dev/null +++ b/lombok.config @@ -0,0 +1 @@ +lombok.accessors.chain=true \ No newline at end of file diff --git a/src/main/java/net/elytrium/limboauth/LimboAuth.java b/src/main/java/net/elytrium/limboauth/LimboAuth.java index 04f2d21d..eb5a1de7 100644 --- a/src/main/java/net/elytrium/limboauth/LimboAuth.java +++ b/src/main/java/net/elytrium/limboauth/LimboAuth.java @@ -23,16 +23,6 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.inject.Inject; -import com.j256.ormlite.dao.Dao; -import com.j256.ormlite.dao.DaoManager; -import com.j256.ormlite.dao.GenericRawResults; -import com.j256.ormlite.db.DatabaseType; -import com.j256.ormlite.field.FieldType; -import com.j256.ormlite.stmt.QueryBuilder; -import com.j256.ormlite.stmt.UpdateBuilder; -import com.j256.ormlite.support.ConnectionSource; -import com.j256.ormlite.table.TableInfo; -import com.j256.ormlite.table.TableUtils; import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.event.EventManager; import com.velocitypowered.api.event.Subscribe; @@ -52,35 +42,6 @@ import com.velocitypowered.proxy.util.ratelimit.Ratelimiters; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.whitfin.siphash.SipHasher; -import java.io.File; -import java.io.IOException; -import java.net.InetAddress; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.sql.SQLException; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; import net.elytrium.commons.kyori.serialization.Serializer; import net.elytrium.commons.kyori.serialization.Serializers; import net.elytrium.commons.utils.reflection.ReflectionException; @@ -90,28 +51,17 @@ import net.elytrium.limboapi.api.chunk.VirtualWorld; import net.elytrium.limboapi.api.command.LimboCommandMeta; import net.elytrium.limboapi.api.file.WorldFile; -import net.elytrium.limboauth.command.ChangePasswordCommand; -import net.elytrium.limboauth.command.DestroySessionCommand; -import net.elytrium.limboauth.command.ForceChangePasswordCommand; -import net.elytrium.limboauth.command.ForceLoginCommand; -import net.elytrium.limboauth.command.ForceRegisterCommand; -import net.elytrium.limboauth.command.ForceUnregisterCommand; -import net.elytrium.limboauth.command.LimboAuthCommand; -import net.elytrium.limboauth.command.PremiumCommand; -import net.elytrium.limboauth.command.TotpCommand; -import net.elytrium.limboauth.command.UnregisterCommand; -import net.elytrium.limboauth.dependencies.DatabaseLibrary; -import net.elytrium.limboauth.event.AuthPluginReloadEvent; -import net.elytrium.limboauth.event.PreAuthorizationEvent; -import net.elytrium.limboauth.event.PreEvent; -import net.elytrium.limboauth.event.PreRegisterEvent; -import net.elytrium.limboauth.event.TaskEvent; +import net.elytrium.limboauth.command.*; +import net.elytrium.limboauth.data.DataProvider; +import net.elytrium.limboauth.event.*; import net.elytrium.limboauth.floodgate.FloodgateApiHolder; import net.elytrium.limboauth.handler.AuthSessionHandler; import net.elytrium.limboauth.listener.AuthListener; import net.elytrium.limboauth.listener.BackendEndpointsListener; +import net.elytrium.limboauth.model.DataAccessRuntimeException; import net.elytrium.limboauth.model.RegisteredPlayer; -import net.elytrium.limboauth.model.SQLRuntimeException; +import net.elytrium.limboauth.repository.RegisteredPlayerRepository; +import net.elytrium.limboauth.repository.exception.DataAccessException; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.ComponentSerializer; import net.kyori.adventure.title.Title; @@ -122,6 +72,27 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.Logger; +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + @Plugin( id = "limboauth", name = "LimboAuth", @@ -183,8 +154,7 @@ public class LimboAuth { private ScheduledTask purgePremiumCacheTask; private ScheduledTask purgeBruteforceCacheTask; - private ConnectionSource connectionSource; - private Dao playerDao; + private RegisteredPlayerRepository playerRepository; private Pattern nicknameValidationPattern; private Limbo authServer; @@ -215,7 +185,7 @@ public void onProxyInitialization(ProxyInitializeEvent event) { try { this.reload(); - } catch (SQLRuntimeException exception) { + } catch (DataAccessRuntimeException exception) { LOGGER.error("SQL EXCEPTION CAUGHT.", exception); this.server.shutdown(); } @@ -228,7 +198,7 @@ public void onProxyInitialization(ProxyInitializeEvent event) { metrics.addCustomChart(new SimplePie("totp_enabled", () -> String.valueOf(Settings.IMP.MAIN.ENABLE_TOTP))); metrics.addCustomChart(new SimplePie("dimension", () -> String.valueOf(Settings.IMP.MAIN.DIMENSION))); metrics.addCustomChart(new SimplePie("save_uuid", () -> String.valueOf(Settings.IMP.MAIN.SAVE_UUID))); - metrics.addCustomChart(new SingleLineChart("registered_players", () -> Math.toIntExact(this.playerDao.countOf()))); + metrics.addCustomChart(new SingleLineChart("registered_players", () -> Math.toIntExact(this.playerRepository.registeredPlayerCount()))); this.server.getScheduler().buildTask(this, () -> { if (!UpdatesChecker.checkVersionByURL("https://raw.githubusercontent.com/Elytrium/LimboAuth/master/VERSION", Settings.IMP.VERSION)) { @@ -314,9 +284,9 @@ public void reload() { this.bruteforceCache.clear(); Settings.DATABASE dbConfig = Settings.IMP.DATABASE; - DatabaseLibrary databaseLibrary = dbConfig.STORAGE_TYPE; + DataProvider dataProvider = dbConfig.STORAGE_TYPE; try { - this.connectionSource = databaseLibrary.connectToORM( + this.playerRepository = dataProvider.createRegisteredPlayerRepository( this.dataDirectoryFile.toPath().toAbsolutePath(), dbConfig.HOSTNAME, dbConfig.DATABASE + dbConfig.CONNECTION_PARAMETERS, @@ -324,30 +294,17 @@ public void reload() { dbConfig.PASSWORD ); } catch (ReflectiveOperationException e) { + LOGGER.error("Fail to load database, disable server", e); + this.server.shutdown(); throw new ReflectionException(e); - } catch (SQLException e) { - throw new SQLRuntimeException(e); - } catch (IOException | URISyntaxException e) { + } catch (Exception e) { + LOGGER.error("Fail to load database, disable server", e); + this.server.shutdown(); throw new IllegalArgumentException(e); } this.nicknameValidationPattern = Pattern.compile(Settings.IMP.MAIN.ALLOWED_NICKNAME_REGEX); - try { - try { - TableUtils.createTableIfNotExists(this.connectionSource, RegisteredPlayer.class); - } catch (SQLException e) { - if (!e.getMessage().contains("CREATE INDEX")) { - throw e; - } - } - - this.playerDao = DaoManager.createDao(this.connectionSource, RegisteredPlayer.class); - this.migrateDb(this.playerDao); - } catch (SQLException e) { - throw new SQLRuntimeException(e); - } - CommandManager manager = this.server.getCommandManager(); manager.unregister("unregister"); manager.unregister("forceregister"); @@ -360,16 +317,16 @@ public void reload() { manager.unregister("2fa"); manager.unregister("limboauth"); - manager.register("unregister", new UnregisterCommand(this, this.playerDao), "unreg"); - manager.register("forceregister", new ForceRegisterCommand(this, this.playerDao), "forcereg"); + manager.register("unregister", new UnregisterCommand(this, this.playerRepository), "unreg"); + manager.register("forceregister", new ForceRegisterCommand(this, this.playerRepository), "forcereg"); manager.register("forcelogin", new ForceLoginCommand(this)); - manager.register("premium", new PremiumCommand(this, this.playerDao), "license"); - manager.register("forceunregister", new ForceUnregisterCommand(this, this.server, this.playerDao), "forceunreg"); - manager.register("changepassword", new ChangePasswordCommand(this, this.playerDao), "changepass", "cp"); - manager.register("forcechangepassword", new ForceChangePasswordCommand(this, this.server, this.playerDao), "forcechangepass", "fcp"); + manager.register("premium", new PremiumCommand(this, this.playerRepository), "license"); + manager.register("forceunregister", new ForceUnregisterCommand(this, this.server, this.playerRepository), "forceunreg"); + manager.register("changepassword", new ChangePasswordCommand(this, this.playerRepository), "changepass", "cp"); + manager.register("forcechangepassword", new ForceChangePasswordCommand(this, this.server, this.playerRepository), "forcechangepass", "fcp"); manager.register("destroysession", new DestroySessionCommand(this), "logout"); if (Settings.IMP.MAIN.ENABLE_TOTP) { - manager.register("2fa", new TotpCommand(this.playerDao), "totp"); + manager.register("2fa", new TotpCommand(this.playerRepository), "totp"); } manager.register("limboauth", new LimboAuthCommand(this), "la", "auth", "lauth"); @@ -410,7 +367,7 @@ public void reload() { EventManager eventManager = this.server.getEventManager(); eventManager.unregisterListeners(this); - eventManager.register(this, new AuthListener(this, this.playerDao, this.floodgateApi)); + eventManager.register(this, new AuthListener(this, this.playerRepository, this.floodgateApi)); if (Settings.IMP.MAIN.BACKEND_API.ENABLED) { eventManager.register(this, new BackendEndpointsListener(this)); } else { @@ -461,75 +418,6 @@ private void checkCache(Map userMap, long time) { .forEach(userMap::remove); } - public void migrateDb(Dao dao) { - TableInfo tableInfo = dao.getTableInfo(); - - Set tables = new HashSet<>(); - Collections.addAll(tables, tableInfo.getFieldTypes()); - - String findSql; - String database = Settings.IMP.DATABASE.DATABASE; - String tableName = tableInfo.getTableName(); - DatabaseLibrary databaseLibrary = Settings.IMP.DATABASE.STORAGE_TYPE; - switch (databaseLibrary) { - case SQLITE: { - findSql = "SELECT name FROM PRAGMA_TABLE_INFO('" + tableName + "')"; - break; - } - case H2: { - findSql = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '" + tableName + "';"; - break; - } - case POSTGRESQL: { - findSql = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_CATALOG = '" + database + "' AND TABLE_NAME = '" + tableName + "';"; - break; - } - case MARIADB: - case MYSQL: { - findSql = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '" + database + "' AND TABLE_NAME = '" + tableName + "';"; - break; - } - default: { - LOGGER.error("WRONG DATABASE TYPE."); - this.server.shutdown(); - return; - } - } - - try (GenericRawResults queryResult = dao.queryRaw(findSql)) { - queryResult.forEach(result -> tables.removeIf(table -> table.getColumnName().equalsIgnoreCase(result[0]))); - - tables.forEach(table -> { - try { - StringBuilder builder = new StringBuilder("ALTER TABLE "); - if (databaseLibrary == DatabaseLibrary.POSTGRESQL) { - builder.append('"'); - } - builder.append(tableName); - if (databaseLibrary == DatabaseLibrary.POSTGRESQL) { - builder.append('"'); - } - builder.append(" ADD "); - String columnDefinition = table.getColumnDefinition(); - DatabaseType databaseType = dao.getConnectionSource().getDatabaseType(); - if (columnDefinition == null) { - List dummy = List.of(); - databaseType.appendColumnArg(table.getTableName(), builder, table, dummy, dummy, dummy, dummy); - } else { - databaseType.appendEscapedEntityName(builder, table.getColumnName()); - builder.append(" ").append(columnDefinition).append(" "); - } - - dao.executeRawNoArgs(builder.toString()); - } catch (SQLException e) { - throw new SQLRuntimeException(e); - } - }); - } catch (Exception e) { - throw new SQLRuntimeException(e); - } - } - public void cacheAuthUser(Player player) { String username = player.getUsername(); String lowercaseUsername = username.toLowerCase(Locale.ROOT); @@ -574,7 +462,7 @@ public void authPlayer(Player player) { return; } - RegisteredPlayer registeredPlayer = AuthSessionHandler.fetchInfo(this.playerDao, nickname); + RegisteredPlayer registeredPlayer = AuthSessionHandler.fetchInfo(this.playerRepository, nickname); boolean onlineMode = player.isOnlineMode(); TaskEvent.Result result = TaskEvent.Result.NORMAL; @@ -582,15 +470,15 @@ public void authPlayer(Player player) { if (onlineMode || isFloodgate) { if (registeredPlayer == null || registeredPlayer.getHash().isEmpty()) { RegisteredPlayer nicknameRegisteredPlayer = registeredPlayer; - registeredPlayer = AuthSessionHandler.fetchInfo(this.playerDao, player.getUniqueId()); + registeredPlayer = AuthSessionHandler.fetchInfo(this.playerRepository, player.getUniqueId()); if (nicknameRegisteredPlayer != null && registeredPlayer == null && nicknameRegisteredPlayer.getHash().isEmpty()) { registeredPlayer = nicknameRegisteredPlayer; registeredPlayer.setPremiumUuid(player.getUniqueId().toString()); try { - this.playerDao.update(registeredPlayer); - } catch (SQLException e) { - throw new SQLRuntimeException(e); + this.playerRepository.update(registeredPlayer); + } catch (DataAccessException e) { + throw new DataAccessRuntimeException(e); } } @@ -598,9 +486,9 @@ public void authPlayer(Player player) { registeredPlayer = new RegisteredPlayer(player).setPremiumUuid(player.getUniqueId()); try { - this.playerDao.create(registeredPlayer); - } catch (SQLException e) { - throw new SQLRuntimeException(e); + this.playerRepository.createIfNotExists(registeredPlayer); + } catch (DataAccessException e) { + throw new DataAccessRuntimeException(e); } } @@ -654,8 +542,8 @@ private void sendPlayer(TaskEvent event, RegisteredPlayer registeredPlayer) { this.cacheAuthUser(player); try { this.updateLoginData(player); - } catch (SQLException e) { - throw new SQLRuntimeException(e); + } catch (DataAccessException e) { + throw new DataAccessRuntimeException(e); } } finally { this.factory.passLoginLimbo(player); @@ -671,19 +559,15 @@ private void sendPlayer(TaskEvent event, RegisteredPlayer registeredPlayer) { } case NORMAL: default: { - this.authServer.spawnPlayer(player, new AuthSessionHandler(this.playerDao, player, this, registeredPlayer)); + this.authServer.spawnPlayer(player, new AuthSessionHandler(this.playerRepository, player, this, registeredPlayer)); break; } } } - public void updateLoginData(Player player) throws SQLException { + public void updateLoginData(Player player) throws DataAccessException { String lowercaseNickname = player.getUsername().toLowerCase(Locale.ROOT); - UpdateBuilder updateBuilder = this.playerDao.updateBuilder(); - updateBuilder.where().eq(RegisteredPlayer.LOWERCASE_NICKNAME_FIELD, lowercaseNickname); - updateBuilder.updateColumnValue(RegisteredPlayer.LOGIN_IP_FIELD, player.getRemoteAddress().getAddress().getHostAddress()); - updateBuilder.updateColumnValue(RegisteredPlayer.LOGIN_DATE_FIELD, System.currentTimeMillis()); - updateBuilder.update(); + this.playerRepository.updateLogin(lowercaseNickname, player.getRemoteAddress().getAddress().getHostAddress(), System.currentTimeMillis()); if (Settings.IMP.MAIN.MOD.ENABLED) { byte[] lowercaseNicknameSerialized = lowercaseNickname.getBytes(StandardCharsets.UTF_8); @@ -754,30 +638,11 @@ public PremiumResponse isPremiumExternal(String nickname) { public PremiumResponse isPremiumInternal(String nickname) { try { - QueryBuilder crackedCountQuery = this.playerDao.queryBuilder(); - crackedCountQuery.where() - .eq(RegisteredPlayer.LOWERCASE_NICKNAME_FIELD, nickname) - .and() - .ne(RegisteredPlayer.HASH_FIELD, ""); - crackedCountQuery.setCountOf(true); - - QueryBuilder premiumCountQuery = this.playerDao.queryBuilder(); - premiumCountQuery.where() - .eq(RegisteredPlayer.LOWERCASE_NICKNAME_FIELD, nickname) - .and() - .eq(RegisteredPlayer.HASH_FIELD, ""); - premiumCountQuery.setCountOf(true); - - if (this.playerDao.countOf(crackedCountQuery.prepare()) != 0) { + if (this.playerRepository.isHashEmptyByLowercaseName(nickname)) { return new PremiumResponse(PremiumState.CRACKED); } - - if (this.playerDao.countOf(premiumCountQuery.prepare()) != 0) { - return new PremiumResponse(PremiumState.PREMIUM); - } - return new PremiumResponse(PremiumState.UNKNOWN); - } catch (SQLException e) { + } catch (DataAccessException e) { LOGGER.error("Unable to check if account is premium.", e); return new PremiumResponse(PremiumState.ERROR); } @@ -785,15 +650,8 @@ public PremiumResponse isPremiumInternal(String nickname) { public boolean isPremiumUuid(UUID uuid) { try { - QueryBuilder premiumCountQuery = this.playerDao.queryBuilder(); - premiumCountQuery.where() - .eq(RegisteredPlayer.PREMIUM_UUID_FIELD, uuid.toString()) - .and() - .eq(RegisteredPlayer.HASH_FIELD, ""); - premiumCountQuery.setCountOf(true); - - return this.playerDao.countOf(premiumCountQuery.prepare()) != 0; - } catch (SQLException e) { + return this.playerRepository.isHashEmptyByPremiumUuid(uuid.toString()); + } catch (DataAccessException e) { LOGGER.error("Unable to check if account is premium.", e); return false; } @@ -951,12 +809,8 @@ public ProxyServer getServer() { return this.server; } - public ConnectionSource getConnectionSource() { - return this.connectionSource; - } - - public Dao getPlayerDao() { - return this.playerDao; + public RegisteredPlayerRepository getPlayerRepository() { + return this.playerRepository; } private static void setLogger(Logger logger) { diff --git a/src/main/java/net/elytrium/limboauth/Settings.java b/src/main/java/net/elytrium/limboauth/Settings.java index fc2344e8..67e7f0e4 100644 --- a/src/main/java/net/elytrium/limboauth/Settings.java +++ b/src/main/java/net/elytrium/limboauth/Settings.java @@ -17,13 +17,6 @@ package net.elytrium.limboauth; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.List; -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; import net.elytrium.commons.config.ConfigSerializer; import net.elytrium.commons.config.YamlConfig; import net.elytrium.commons.kyori.serialization.Serializers; @@ -31,12 +24,20 @@ import net.elytrium.limboapi.api.file.BuiltInWorldFileType; import net.elytrium.limboapi.api.player.GameMode; import net.elytrium.limboauth.command.CommandPermissionState; -import net.elytrium.limboauth.dependencies.DatabaseLibrary; +import net.elytrium.limboauth.data.DataProvider; import net.elytrium.limboauth.migration.MigrationHash; import net.kyori.adventure.bossbar.BossBar; import net.kyori.adventure.title.Title; import net.kyori.adventure.util.Ticks; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + public class Settings extends YamlConfig { @Ignore @@ -511,8 +512,8 @@ public static class STRINGS { @Comment("Database settings") public static class DATABASE { - @Comment("Database type: mariadb, mysql, postgresql, sqlite or h2.") - public DatabaseLibrary STORAGE_TYPE = DatabaseLibrary.H2; + @Comment("Database type: mariadb, mysql, postgresql.") + public DataProvider STORAGE_TYPE = DataProvider.MYSQL; @Comment("Settings for Network-based database (like MySQL, PostgreSQL): ") public String HOSTNAME = "127.0.0.1:3306"; diff --git a/src/main/java/net/elytrium/limboauth/backend/type/LongDatabaseEndpoint.java b/src/main/java/net/elytrium/limboauth/backend/type/LongDatabaseEndpoint.java index 7ccf60a2..0bb145d4 100644 --- a/src/main/java/net/elytrium/limboauth/backend/type/LongDatabaseEndpoint.java +++ b/src/main/java/net/elytrium/limboauth/backend/type/LongDatabaseEndpoint.java @@ -30,7 +30,7 @@ public LongDatabaseEndpoint(LimboAuth plugin, String type, String username, long public LongDatabaseEndpoint(LimboAuth plugin, String type, Function function) { super(plugin, type, username -> { - RegisteredPlayer player = AuthSessionHandler.fetchInfo(plugin.getPlayerDao(), username); + RegisteredPlayer player = AuthSessionHandler.fetchInfo(plugin.getPlayerRepository(), username); if (player == null) { return Long.MIN_VALUE; } else { diff --git a/src/main/java/net/elytrium/limboauth/backend/type/StringDatabaseEndpoint.java b/src/main/java/net/elytrium/limboauth/backend/type/StringDatabaseEndpoint.java index 174a8d4b..7566bc6c 100644 --- a/src/main/java/net/elytrium/limboauth/backend/type/StringDatabaseEndpoint.java +++ b/src/main/java/net/elytrium/limboauth/backend/type/StringDatabaseEndpoint.java @@ -30,7 +30,7 @@ public StringDatabaseEndpoint(LimboAuth plugin, String type, String username, St public StringDatabaseEndpoint(LimboAuth plugin, String type, Function function) { super(plugin, type, username -> { - RegisteredPlayer player = AuthSessionHandler.fetchInfo(plugin.getPlayerDao(), username); + RegisteredPlayer player = AuthSessionHandler.fetchInfo(plugin.getPlayerRepository(), username); if (player == null) { return ""; } else { diff --git a/src/main/java/net/elytrium/limboauth/command/ChangePasswordCommand.java b/src/main/java/net/elytrium/limboauth/command/ChangePasswordCommand.java index 28dcd8e5..2de72c7a 100644 --- a/src/main/java/net/elytrium/limboauth/command/ChangePasswordCommand.java +++ b/src/main/java/net/elytrium/limboauth/command/ChangePasswordCommand.java @@ -17,26 +17,26 @@ package net.elytrium.limboauth.command; -import com.j256.ormlite.dao.Dao; -import com.j256.ormlite.stmt.UpdateBuilder; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.proxy.Player; -import java.sql.SQLException; -import java.util.Locale; import net.elytrium.commons.kyori.serialization.Serializer; import net.elytrium.limboauth.LimboAuth; import net.elytrium.limboauth.Settings; import net.elytrium.limboauth.event.ChangePasswordEvent; import net.elytrium.limboauth.handler.AuthSessionHandler; +import net.elytrium.limboauth.model.DataAccessRuntimeException; import net.elytrium.limboauth.model.RegisteredPlayer; -import net.elytrium.limboauth.model.SQLRuntimeException; +import net.elytrium.limboauth.repository.RegisteredPlayerRepository; +import net.elytrium.limboauth.repository.exception.DataAccessException; import net.kyori.adventure.text.Component; +import java.util.Locale; + public class ChangePasswordCommand extends RatelimitedCommand { private final LimboAuth plugin; - private final Dao playerDao; + private final RegisteredPlayerRepository registeredPlayerRepository; private final boolean needOldPass; private final Component notRegistered; @@ -46,9 +46,9 @@ public class ChangePasswordCommand extends RatelimitedCommand { private final Component usage; private final Component notPlayer; - public ChangePasswordCommand(LimboAuth plugin, Dao playerDao) { + public ChangePasswordCommand(LimboAuth plugin, RegisteredPlayerRepository registeredPlayerRepository) { this.plugin = plugin; - this.playerDao = playerDao; + this.registeredPlayerRepository = registeredPlayerRepository; Serializer serializer = LimboAuth.getSerializer(); this.needOldPass = Settings.IMP.MAIN.CHANGE_PASSWORD_NEED_OLD_PASSWORD; @@ -64,7 +64,7 @@ public ChangePasswordCommand(LimboAuth plugin, Dao pla public void execute(CommandSource source, String[] args) { if (source instanceof Player) { String usernameLowercase = ((Player) source).getUsername().toLowerCase(Locale.ROOT); - RegisteredPlayer player = AuthSessionHandler.fetchInfoLowercased(this.playerDao, usernameLowercase); + RegisteredPlayer player = AuthSessionHandler.fetchInfoLowercased(this.registeredPlayerRepository, usernameLowercase); if (player == null) { source.sendMessage(this.notRegistered); @@ -79,7 +79,7 @@ public void execute(CommandSource source, String[] args) { return; } - if (!AuthSessionHandler.checkPassword(args[0], player, this.playerDao)) { + if (!AuthSessionHandler.checkPassword(args[0], player, this.registeredPlayerRepository)) { source.sendMessage(this.wrongPassword); return; } @@ -93,10 +93,7 @@ public void execute(CommandSource source, String[] args) { final String newPassword = needOldPass ? args[1] : args[0]; final String newHash = RegisteredPlayer.genHash(newPassword); - UpdateBuilder updateBuilder = this.playerDao.updateBuilder(); - updateBuilder.where().eq(RegisteredPlayer.LOWERCASE_NICKNAME_FIELD, usernameLowercase); - updateBuilder.updateColumnValue(RegisteredPlayer.HASH_FIELD, newHash); - updateBuilder.update(); + registeredPlayerRepository.updateHash(usernameLowercase, newHash); this.plugin.removePlayerFromCacheLowercased(usernameLowercase); @@ -104,9 +101,9 @@ public void execute(CommandSource source, String[] args) { new ChangePasswordEvent(player, needOldPass ? args[0] : null, oldHash, newPassword, newHash)); source.sendMessage(this.successful); - } catch (SQLException e) { + } catch (DataAccessException e) { source.sendMessage(this.errorOccurred); - throw new SQLRuntimeException(e); + throw new DataAccessRuntimeException(e); } } else { source.sendMessage(this.notPlayer); diff --git a/src/main/java/net/elytrium/limboauth/command/ForceChangePasswordCommand.java b/src/main/java/net/elytrium/limboauth/command/ForceChangePasswordCommand.java index 23a7d8a9..fc20dd1b 100644 --- a/src/main/java/net/elytrium/limboauth/command/ForceChangePasswordCommand.java +++ b/src/main/java/net/elytrium/limboauth/command/ForceChangePasswordCommand.java @@ -17,30 +17,30 @@ package net.elytrium.limboauth.command; -import com.j256.ormlite.dao.Dao; -import com.j256.ormlite.stmt.UpdateBuilder; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.proxy.ProxyServer; -import java.sql.SQLException; -import java.text.MessageFormat; -import java.util.List; -import java.util.Locale; import net.elytrium.commons.kyori.serialization.Serializer; import net.elytrium.commons.velocity.commands.SuggestUtils; import net.elytrium.limboauth.LimboAuth; import net.elytrium.limboauth.Settings; import net.elytrium.limboauth.event.ChangePasswordEvent; import net.elytrium.limboauth.handler.AuthSessionHandler; +import net.elytrium.limboauth.model.DataAccessRuntimeException; import net.elytrium.limboauth.model.RegisteredPlayer; -import net.elytrium.limboauth.model.SQLRuntimeException; +import net.elytrium.limboauth.repository.RegisteredPlayerRepository; +import net.elytrium.limboauth.repository.exception.DataAccessException; import net.kyori.adventure.text.Component; +import java.text.MessageFormat; +import java.util.List; +import java.util.Locale; + public class ForceChangePasswordCommand extends RatelimitedCommand { private final LimboAuth plugin; private final ProxyServer server; - private final Dao playerDao; + private final RegisteredPlayerRepository registeredPlayerRepository; private final String message; private final String successful; @@ -48,10 +48,10 @@ public class ForceChangePasswordCommand extends RatelimitedCommand { private final String notRegistered; private final Component usage; - public ForceChangePasswordCommand(LimboAuth plugin, ProxyServer server, Dao playerDao) { + public ForceChangePasswordCommand(LimboAuth plugin, ProxyServer server, RegisteredPlayerRepository registeredPlayerRepository) { this.plugin = plugin; this.server = server; - this.playerDao = playerDao; + this.registeredPlayerRepository = registeredPlayerRepository; this.message = Settings.IMP.MAIN.STRINGS.FORCE_CHANGE_PASSWORD_MESSAGE; this.successful = Settings.IMP.MAIN.STRINGS.FORCE_CHANGE_PASSWORD_SUCCESSFUL; @@ -74,7 +74,7 @@ public void execute(CommandSource source, String[] args) { Serializer serializer = LimboAuth.getSerializer(); try { - RegisteredPlayer registeredPlayer = AuthSessionHandler.fetchInfoLowercased(this.playerDao, nicknameLowercased); + RegisteredPlayer registeredPlayer = AuthSessionHandler.fetchInfoLowercased(this.registeredPlayerRepository, nicknameLowercased); if (registeredPlayer == null) { source.sendMessage(serializer.deserialize(MessageFormat.format(this.notRegistered, nickname))); @@ -84,10 +84,7 @@ public void execute(CommandSource source, String[] args) { final String oldHash = registeredPlayer.getHash(); final String newHash = RegisteredPlayer.genHash(newPassword); - UpdateBuilder updateBuilder = this.playerDao.updateBuilder(); - updateBuilder.where().eq(RegisteredPlayer.LOWERCASE_NICKNAME_FIELD, nicknameLowercased); - updateBuilder.updateColumnValue(RegisteredPlayer.HASH_FIELD, newHash); - updateBuilder.update(); + this.registeredPlayerRepository.updateHash(nicknameLowercased, newHash); this.plugin.removePlayerFromCacheLowercased(nicknameLowercased); this.server.getPlayer(nickname) @@ -96,9 +93,9 @@ public void execute(CommandSource source, String[] args) { this.plugin.getServer().getEventManager().fireAndForget(new ChangePasswordEvent(registeredPlayer, null, oldHash, newPassword, newHash)); source.sendMessage(serializer.deserialize(MessageFormat.format(this.successful, nickname))); - } catch (SQLException e) { + } catch (DataAccessException e) { source.sendMessage(serializer.deserialize(MessageFormat.format(this.notSuccessful, nickname))); - throw new SQLRuntimeException(e); + throw new DataAccessRuntimeException(e); } } else { source.sendMessage(this.usage); diff --git a/src/main/java/net/elytrium/limboauth/command/ForceRegisterCommand.java b/src/main/java/net/elytrium/limboauth/command/ForceRegisterCommand.java index ad8fa00c..61879000 100644 --- a/src/main/java/net/elytrium/limboauth/command/ForceRegisterCommand.java +++ b/src/main/java/net/elytrium/limboauth/command/ForceRegisterCommand.java @@ -17,23 +17,24 @@ package net.elytrium.limboauth.command; -import com.j256.ormlite.dao.Dao; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; -import java.sql.SQLException; -import java.text.MessageFormat; -import java.util.Locale; import net.elytrium.commons.kyori.serialization.Serializer; import net.elytrium.limboauth.LimboAuth; import net.elytrium.limboauth.Settings; +import net.elytrium.limboauth.model.DataAccessRuntimeException; import net.elytrium.limboauth.model.RegisteredPlayer; -import net.elytrium.limboauth.model.SQLRuntimeException; +import net.elytrium.limboauth.repository.RegisteredPlayerRepository; +import net.elytrium.limboauth.repository.exception.DataAccessException; import net.kyori.adventure.text.Component; +import java.text.MessageFormat; +import java.util.Locale; + public class ForceRegisterCommand extends RatelimitedCommand { private final LimboAuth plugin; - private final Dao playerDao; + private final RegisteredPlayerRepository registeredPlayerRepository; private final String successful; private final String notSuccessful; @@ -41,9 +42,9 @@ public class ForceRegisterCommand extends RatelimitedCommand { private final Component takenNickname; private final Component incorrectNickname; - public ForceRegisterCommand(LimboAuth plugin, Dao playerDao) { + public ForceRegisterCommand(LimboAuth plugin, RegisteredPlayerRepository registeredPlayerRepository) { this.plugin = plugin; - this.playerDao = playerDao; + this.registeredPlayerRepository = registeredPlayerRepository; this.successful = Settings.IMP.MAIN.STRINGS.FORCE_REGISTER_SUCCESSFUL; this.notSuccessful = Settings.IMP.MAIN.STRINGS.FORCE_REGISTER_NOT_SUCCESSFUL; @@ -66,18 +67,18 @@ public void execute(CommandSource source, String[] args) { } String lowercaseNickname = nickname.toLowerCase(Locale.ROOT); - if (this.playerDao.idExists(lowercaseNickname)) { + if (this.registeredPlayerRepository.getByLowercaseName(lowercaseNickname).isPresent()) { source.sendMessage(this.takenNickname); return; } RegisteredPlayer player = new RegisteredPlayer(nickname, "", "").setPassword(password); - this.playerDao.create(player); + this.registeredPlayerRepository.createIfNotExists(player); source.sendMessage(serializer.deserialize(MessageFormat.format(this.successful, nickname))); - } catch (SQLException e) { + } catch (DataAccessException e) { source.sendMessage(serializer.deserialize(MessageFormat.format(this.notSuccessful, nickname))); - throw new SQLRuntimeException(e); + throw new DataAccessRuntimeException(e); } } else { source.sendMessage(this.usage); diff --git a/src/main/java/net/elytrium/limboauth/command/ForceUnregisterCommand.java b/src/main/java/net/elytrium/limboauth/command/ForceUnregisterCommand.java index 20510b83..aa23831b 100644 --- a/src/main/java/net/elytrium/limboauth/command/ForceUnregisterCommand.java +++ b/src/main/java/net/elytrium/limboauth/command/ForceUnregisterCommand.java @@ -17,38 +17,38 @@ package net.elytrium.limboauth.command; -import com.j256.ormlite.dao.Dao; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.proxy.ProxyServer; -import java.sql.SQLException; -import java.text.MessageFormat; -import java.util.List; -import java.util.Locale; import net.elytrium.commons.kyori.serialization.Serializer; import net.elytrium.commons.velocity.commands.SuggestUtils; import net.elytrium.limboauth.LimboAuth; import net.elytrium.limboauth.Settings; import net.elytrium.limboauth.event.AuthUnregisterEvent; -import net.elytrium.limboauth.model.RegisteredPlayer; -import net.elytrium.limboauth.model.SQLRuntimeException; +import net.elytrium.limboauth.model.DataAccessRuntimeException; +import net.elytrium.limboauth.repository.RegisteredPlayerRepository; +import net.elytrium.limboauth.repository.exception.DataAccessException; import net.kyori.adventure.text.Component; +import java.text.MessageFormat; +import java.util.List; +import java.util.Locale; + public class ForceUnregisterCommand extends RatelimitedCommand { private final LimboAuth plugin; private final ProxyServer server; - private final Dao playerDao; + private final RegisteredPlayerRepository registeredPlayerRepository; private final Component kick; private final String successful; private final String notSuccessful; private final Component usage; - public ForceUnregisterCommand(LimboAuth plugin, ProxyServer server, Dao playerDao) { + public ForceUnregisterCommand(LimboAuth plugin, ProxyServer server, RegisteredPlayerRepository registeredPlayerRepository) { this.plugin = plugin; this.server = server; - this.playerDao = playerDao; + this.registeredPlayerRepository = registeredPlayerRepository; Serializer serializer = LimboAuth.getSerializer(); this.kick = serializer.deserialize(Settings.IMP.MAIN.STRINGS.FORCE_UNREGISTER_KICK); @@ -71,13 +71,13 @@ public void execute(CommandSource source, String[] args) { Serializer serializer = LimboAuth.getSerializer(); try { this.plugin.getServer().getEventManager().fireAndForget(new AuthUnregisterEvent(playerNick)); - this.playerDao.deleteById(usernameLowercased); + this.registeredPlayerRepository.deleteByLowercaseName(usernameLowercased); this.plugin.removePlayerFromCacheLowercased(usernameLowercased); this.server.getPlayer(playerNick).ifPresent(player -> player.disconnect(this.kick)); source.sendMessage(serializer.deserialize(MessageFormat.format(this.successful, playerNick))); - } catch (SQLException e) { + } catch (DataAccessException e) { source.sendMessage(serializer.deserialize(MessageFormat.format(this.notSuccessful, playerNick))); - throw new SQLRuntimeException(e); + throw new DataAccessRuntimeException(e); } } else { source.sendMessage(this.usage); diff --git a/src/main/java/net/elytrium/limboauth/command/PremiumCommand.java b/src/main/java/net/elytrium/limboauth/command/PremiumCommand.java index 99c5439f..8fb945ac 100644 --- a/src/main/java/net/elytrium/limboauth/command/PremiumCommand.java +++ b/src/main/java/net/elytrium/limboauth/command/PremiumCommand.java @@ -17,24 +17,25 @@ package net.elytrium.limboauth.command; -import com.j256.ormlite.dao.Dao; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.proxy.Player; -import java.sql.SQLException; -import java.util.Locale; import net.elytrium.commons.kyori.serialization.Serializer; import net.elytrium.limboauth.LimboAuth; import net.elytrium.limboauth.Settings; import net.elytrium.limboauth.handler.AuthSessionHandler; +import net.elytrium.limboauth.model.DataAccessRuntimeException; import net.elytrium.limboauth.model.RegisteredPlayer; -import net.elytrium.limboauth.model.SQLRuntimeException; +import net.elytrium.limboauth.repository.RegisteredPlayerRepository; +import net.elytrium.limboauth.repository.exception.DataAccessException; import net.kyori.adventure.text.Component; +import java.util.Locale; + public class PremiumCommand extends RatelimitedCommand { private final LimboAuth plugin; - private final Dao playerDao; + private final RegisteredPlayerRepository registeredPlayerRepository; private final String confirmKeyword; private final Component notRegistered; @@ -46,9 +47,9 @@ public class PremiumCommand extends RatelimitedCommand { private final Component usage; private final Component notPlayer; - public PremiumCommand(LimboAuth plugin, Dao playerDao) { + public PremiumCommand(LimboAuth plugin, RegisteredPlayerRepository registeredPlayerRepository) { this.plugin = plugin; - this.playerDao = playerDao; + this.registeredPlayerRepository = registeredPlayerRepository; Serializer serializer = LimboAuth.getSerializer(); this.confirmKeyword = Settings.IMP.MAIN.CONFIRM_KEYWORD; @@ -68,21 +69,21 @@ public void execute(CommandSource source, String[] args) { if (args.length == 2) { if (this.confirmKeyword.equalsIgnoreCase(args[1])) { String usernameLowercase = ((Player) source).getUsername().toLowerCase(Locale.ROOT); - RegisteredPlayer player = AuthSessionHandler.fetchInfoLowercased(this.playerDao, usernameLowercase); + RegisteredPlayer player = AuthSessionHandler.fetchInfoLowercased(this.registeredPlayerRepository, usernameLowercase); if (player == null) { source.sendMessage(this.notRegistered); } else if (player.getHash().isEmpty()) { source.sendMessage(this.alreadyPremium); - } else if (AuthSessionHandler.checkPassword(args[0], player, this.playerDao)) { + } else if (AuthSessionHandler.checkPassword(args[0], player, this.registeredPlayerRepository)) { if (this.plugin.isPremiumExternal(usernameLowercase).getState() == LimboAuth.PremiumState.PREMIUM_USERNAME) { try { player.setHash(""); - this.playerDao.update(player); + this.registeredPlayerRepository.update(player); this.plugin.removePlayerFromCacheLowercased(usernameLowercase); ((Player) source).disconnect(this.successful); - } catch (SQLException e) { + } catch (DataAccessException e) { source.sendMessage(this.errorOccurred); - throw new SQLRuntimeException(e); + throw new DataAccessRuntimeException(e); } } else { source.sendMessage(this.notPremium); diff --git a/src/main/java/net/elytrium/limboauth/command/TotpCommand.java b/src/main/java/net/elytrium/limboauth/command/TotpCommand.java index 447b8461..242c8a37 100644 --- a/src/main/java/net/elytrium/limboauth/command/TotpCommand.java +++ b/src/main/java/net/elytrium/limboauth/command/TotpCommand.java @@ -17,8 +17,6 @@ package net.elytrium.limboauth.command; -import com.j256.ormlite.dao.Dao; -import com.j256.ormlite.stmt.UpdateBuilder; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.proxy.Player; @@ -26,25 +24,27 @@ import dev.samstevens.totp.recovery.RecoveryCodeGenerator; import dev.samstevens.totp.secret.DefaultSecretGenerator; import dev.samstevens.totp.secret.SecretGenerator; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.sql.SQLException; -import java.text.MessageFormat; -import java.util.Locale; import net.elytrium.commons.kyori.serialization.Serializer; import net.elytrium.limboauth.LimboAuth; import net.elytrium.limboauth.Settings; import net.elytrium.limboauth.handler.AuthSessionHandler; +import net.elytrium.limboauth.model.DataAccessRuntimeException; import net.elytrium.limboauth.model.RegisteredPlayer; -import net.elytrium.limboauth.model.SQLRuntimeException; +import net.elytrium.limboauth.repository.RegisteredPlayerRepository; +import net.elytrium.limboauth.repository.exception.DataAccessException; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.Locale; + public class TotpCommand extends RatelimitedCommand { private final SecretGenerator secretGenerator = new DefaultSecretGenerator(); private final RecoveryCodeGenerator codesGenerator = new RecoveryCodeGenerator(); - private final Dao playerDao; + private final RegisteredPlayerRepository registeredPlayerRepository; private final Component notPlayer; private final Component usage; @@ -64,8 +64,8 @@ public class TotpCommand extends RatelimitedCommand { private final Component wrong; private final Component crackedCommand; - public TotpCommand(Dao playerDao) { - this.playerDao = playerDao; + public TotpCommand(RegisteredPlayerRepository registeredPlayerRepository) { + this.registeredPlayerRepository = registeredPlayerRepository; Serializer serializer = LimboAuth.getSerializer(); this.notPlayer = serializer.deserialize(Settings.IMP.MAIN.STRINGS.NOT_PLAYER); @@ -98,17 +98,16 @@ public void execute(CommandSource source, String[] args) { String usernameLowercase = username.toLowerCase(Locale.ROOT); RegisteredPlayer playerInfo; - UpdateBuilder updateBuilder; if (args[0].equalsIgnoreCase("enable")) { if (this.needPassword ? args.length == 2 : args.length == 1) { - playerInfo = AuthSessionHandler.fetchInfoLowercased(this.playerDao, usernameLowercase); + playerInfo = AuthSessionHandler.fetchInfoLowercased(this.registeredPlayerRepository, usernameLowercase); if (playerInfo == null) { source.sendMessage(this.notRegistered); return; } else if (playerInfo.getHash().isEmpty()) { source.sendMessage(this.crackedCommand); return; - } else if (this.needPassword && !AuthSessionHandler.checkPassword(args[1], playerInfo, this.playerDao)) { + } else if (this.needPassword && !AuthSessionHandler.checkPassword(args[1], playerInfo, this.registeredPlayerRepository)) { source.sendMessage(this.wrongPassword); return; } @@ -120,13 +119,10 @@ public void execute(CommandSource source, String[] args) { String secret = this.secretGenerator.generate(); try { - updateBuilder = this.playerDao.updateBuilder(); - updateBuilder.where().eq(RegisteredPlayer.LOWERCASE_NICKNAME_FIELD, usernameLowercase); - updateBuilder.updateColumnValue(RegisteredPlayer.TOTP_TOKEN_FIELD, secret); - updateBuilder.update(); - } catch (SQLException e) { + this.registeredPlayerRepository.updateTotpToken(usernameLowercase, secret); + } catch (DataAccessException e) { source.sendMessage(this.errorOccurred); - throw new SQLRuntimeException(e); + throw new DataAccessRuntimeException(e); } source.sendMessage(this.successful); @@ -149,7 +145,7 @@ public void execute(CommandSource source, String[] args) { } } else if (args[0].equalsIgnoreCase("disable")) { if (args.length == 2) { - playerInfo = AuthSessionHandler.fetchInfoLowercased(this.playerDao, usernameLowercase); + playerInfo = AuthSessionHandler.fetchInfoLowercased(this.registeredPlayerRepository, usernameLowercase); if (playerInfo == null) { source.sendMessage(this.notRegistered); @@ -158,15 +154,11 @@ public void execute(CommandSource source, String[] args) { if (AuthSessionHandler.TOTP_CODE_VERIFIER.isValidCode(playerInfo.getTotpToken(), args[1])) { try { - updateBuilder = this.playerDao.updateBuilder(); - updateBuilder.where().eq(RegisteredPlayer.LOWERCASE_NICKNAME_FIELD, usernameLowercase); - updateBuilder.updateColumnValue(RegisteredPlayer.TOTP_TOKEN_FIELD, ""); - updateBuilder.update(); - + this.registeredPlayerRepository.updateTotpToken(usernameLowercase, ""); source.sendMessage(this.disabled); - } catch (SQLException e) { + } catch (DataAccessException e) { source.sendMessage(this.errorOccurred); - throw new SQLRuntimeException(e); + throw new DataAccessRuntimeException(e); } } else { source.sendMessage(this.wrong); diff --git a/src/main/java/net/elytrium/limboauth/command/UnregisterCommand.java b/src/main/java/net/elytrium/limboauth/command/UnregisterCommand.java index 73d9843f..b338d980 100644 --- a/src/main/java/net/elytrium/limboauth/command/UnregisterCommand.java +++ b/src/main/java/net/elytrium/limboauth/command/UnregisterCommand.java @@ -17,25 +17,26 @@ package net.elytrium.limboauth.command; -import com.j256.ormlite.dao.Dao; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.proxy.Player; -import java.sql.SQLException; -import java.util.Locale; import net.elytrium.commons.kyori.serialization.Serializer; import net.elytrium.limboauth.LimboAuth; import net.elytrium.limboauth.Settings; import net.elytrium.limboauth.event.AuthUnregisterEvent; import net.elytrium.limboauth.handler.AuthSessionHandler; +import net.elytrium.limboauth.model.DataAccessRuntimeException; import net.elytrium.limboauth.model.RegisteredPlayer; -import net.elytrium.limboauth.model.SQLRuntimeException; +import net.elytrium.limboauth.repository.RegisteredPlayerRepository; +import net.elytrium.limboauth.repository.exception.DataAccessException; import net.kyori.adventure.text.Component; +import java.util.Locale; + public class UnregisterCommand extends RatelimitedCommand { private final LimboAuth plugin; - private final Dao playerDao; + private final RegisteredPlayerRepository registeredPlayerRepository; private final String confirmKeyword; private final Component notPlayer; @@ -46,9 +47,9 @@ public class UnregisterCommand extends RatelimitedCommand { private final Component usage; private final Component crackedCommand; - public UnregisterCommand(LimboAuth plugin, Dao playerDao) { + public UnregisterCommand(LimboAuth plugin, RegisteredPlayerRepository registeredPlayerRepository) { this.plugin = plugin; - this.playerDao = playerDao; + this.registeredPlayerRepository = registeredPlayerRepository; Serializer serializer = LimboAuth.getSerializer(); this.confirmKeyword = Settings.IMP.MAIN.CONFIRM_KEYWORD; @@ -68,20 +69,20 @@ public void execute(CommandSource source, String[] args) { if (this.confirmKeyword.equalsIgnoreCase(args[1])) { String username = ((Player) source).getUsername(); String usernameLowercase = username.toLowerCase(Locale.ROOT); - RegisteredPlayer player = AuthSessionHandler.fetchInfoLowercased(this.playerDao, usernameLowercase); + RegisteredPlayer player = AuthSessionHandler.fetchInfoLowercased(this.registeredPlayerRepository, usernameLowercase); if (player == null) { source.sendMessage(this.notRegistered); } else if (player.getHash().isEmpty()) { source.sendMessage(this.crackedCommand); - } else if (AuthSessionHandler.checkPassword(args[0], player, this.playerDao)) { + } else if (AuthSessionHandler.checkPassword(args[0], player, this.registeredPlayerRepository)) { try { this.plugin.getServer().getEventManager().fireAndForget(new AuthUnregisterEvent(username)); - this.playerDao.deleteById(usernameLowercase); + this.registeredPlayerRepository.deleteByLowercaseName(usernameLowercase); this.plugin.removePlayerFromCacheLowercased(usernameLowercase); ((Player) source).disconnect(this.successful); - } catch (SQLException e) { + } catch (DataAccessException e) { source.sendMessage(this.errorOccurred); - throw new SQLRuntimeException(e); + throw new DataAccessRuntimeException(e); } } else { source.sendMessage(this.wrongPassword); diff --git a/src/main/java/net/elytrium/limboauth/data/DataProvider.java b/src/main/java/net/elytrium/limboauth/data/DataProvider.java new file mode 100644 index 00000000..0f70c068 --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/data/DataProvider.java @@ -0,0 +1,43 @@ +package net.elytrium.limboauth.data; + +import lombok.Getter; +import net.elytrium.limboauth.dependencies.BaseLibrary; +import net.elytrium.limboauth.dependencies.hikary.HikariRegisteredPlayerRepository; +import net.elytrium.limboauth.repository.RegisteredPlayerRepository; + +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.nio.file.Path; +import java.sql.SQLException; + +@Getter +public enum DataProvider { + MYSQL(BaseLibrary.MYSQL), + MARIADB(BaseLibrary.MARIADB), + POSTGRESQL(BaseLibrary.POSTGRESQL); + + private final BaseLibrary baseLibrary; + + DataProvider(BaseLibrary baseLibrary) { + this.baseLibrary = baseLibrary; + } + + public RegisteredPlayerRepository createRegisteredPlayerRepository( + Path path, + String host, + String database, + String user, + String password + ) throws Exception { + HikariRegisteredPlayerRepository repo = new HikariRegisteredPlayerRepository( + this, + host, + database, + user, + password + ); + repo.updateSchema(this); + return repo; + } + +} diff --git a/src/main/java/net/elytrium/limboauth/dependencies/DatabaseLibrary.java b/src/main/java/net/elytrium/limboauth/dependencies/DatabaseLibrary.java deleted file mode 100644 index 62f68892..00000000 --- a/src/main/java/net/elytrium/limboauth/dependencies/DatabaseLibrary.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2021 - 2024 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboauth.dependencies; - -import com.j256.ormlite.jdbc.JdbcPooledConnectionSource; -import com.j256.ormlite.jdbc.db.DatabaseTypeUtils; -import com.j256.ormlite.support.ConnectionSource; -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.sql.Connection; -import java.sql.Driver; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.Locale; -import java.util.Properties; - -public enum DatabaseLibrary { - H2_LEGACY_V1( - BaseLibrary.H2_V1, - (classLoader, dir, jdbc, user, password) -> fromDriver(classLoader.loadClass("org.h2.Driver"), jdbc, null, null, false), - (dir, hostname, database) -> "jdbc:h2:" + dir + "/limboauth" - ), - H2( - BaseLibrary.H2_V2, - (classLoader, dir, jdbc, user, password) -> { - Connection modernConnection = fromDriver(classLoader.loadClass("org.h2.Driver"), jdbc, null, null, true); - - Path legacyDatabase = dir.resolve("limboauth.mv.db"); - if (Files.exists(legacyDatabase)) { - Path dumpFile = dir.resolve("limboauth.dump.sql"); - try (Connection legacyConnection = H2_LEGACY_V1.connect(dir, null, null, user, password)) { - try (PreparedStatement migrateStatement = legacyConnection.prepareStatement("SCRIPT TO '?'")) { - migrateStatement.setString(1, dumpFile.toString()); - migrateStatement.execute(); - } - } - - try (PreparedStatement migrateStatement = modernConnection.prepareStatement("RUNSCRIPT FROM '?'")) { - migrateStatement.setString(1, dumpFile.toString()); - migrateStatement.execute(); - } - - Files.delete(dumpFile); - Files.move(legacyDatabase, dir.resolve("limboauth-v1-backup.mv.db")); - } - - return modernConnection; - }, - (dir, hostname, database) -> "jdbc:h2:" + dir + "/limboauth-v2" - ), - MYSQL( - BaseLibrary.MYSQL, - (classLoader, dir, jdbc, user, password) - -> fromDriver(classLoader.loadClass("com.mysql.cj.jdbc.NonRegisteringDriver"), jdbc, user, password, true), - (dir, hostname, database) -> - "jdbc:mysql://" + hostname + "/" + database - ), - MARIADB( - BaseLibrary.MARIADB, - (classLoader, dir, jdbc, user, password) - -> fromDriver(classLoader.loadClass("org.mariadb.jdbc.Driver"), jdbc, user, password, true), - (dir, hostname, database) -> - "jdbc:mariadb://" + hostname + "/" + database - ), - POSTGRESQL( - BaseLibrary.POSTGRESQL, - (classLoader, dir, jdbc, user, password) -> fromDriver(classLoader.loadClass("org.postgresql.Driver"), jdbc, user, password, true), - (dir, hostname, database) -> "jdbc:postgresql://" + hostname + "/" + database - ), - SQLITE( - BaseLibrary.SQLITE, - (classLoader, dir, jdbc, user, password) -> fromDriver(classLoader.loadClass("org.sqlite.JDBC"), jdbc, user, password, true), - (dir, hostname, database) -> "jdbc:sqlite:" + dir + "/limboauth.db" - ); - - private final BaseLibrary baseLibrary; - private final DatabaseConnector connector; - private final DatabaseStringGetter stringGetter; - private final IsolatedDriver driver = new IsolatedDriver("jdbc:limboauth_" + this.name().toLowerCase(Locale.ROOT) + ":"); - - DatabaseLibrary(BaseLibrary baseLibrary, DatabaseConnector connector, DatabaseStringGetter stringGetter) { - this.baseLibrary = baseLibrary; - this.connector = connector; - this.stringGetter = stringGetter; - } - - public Connection connect(ClassLoader classLoader, Path dir, String hostname, String database, String user, String password) - throws ReflectiveOperationException, SQLException, IOException { - return this.connect(classLoader, dir, this.stringGetter.getJdbcString(dir, hostname, database), user, password); - } - - public Connection connect(Path dir, String hostname, String database, String user, String password) - throws ReflectiveOperationException, SQLException, IOException { - return this.connect(dir, this.stringGetter.getJdbcString(dir, hostname, database), user, password); - } - - public Connection connect(ClassLoader classLoader, Path dir, String jdbc, String user, String password) - throws ReflectiveOperationException, SQLException, IOException { - return this.connector.connect(classLoader, dir, jdbc, user, password); - } - - public Connection connect(Path dir, String jdbc, String user, String password) throws IOException, ReflectiveOperationException, SQLException { - return this.connector.connect(new IsolatedClassLoader(new URL[]{this.baseLibrary.getClassLoaderURL()}), dir, jdbc, user, password); - } - - public ConnectionSource connectToORM(Path dir, String hostname, String database, String user, String password) - throws ReflectiveOperationException, IOException, SQLException, URISyntaxException { - if (this.driver.getOriginal() == null) { - IsolatedClassLoader classLoader = new IsolatedClassLoader(new URL[] {this.baseLibrary.getClassLoaderURL()}); - Class driverClass = classLoader.loadClass( - switch (this) { - case H2_LEGACY_V1, H2 -> "org.h2.Driver"; - case MYSQL -> "com.mysql.cj.jdbc.NonRegisteringDriver"; - case MARIADB -> "org.mariadb.jdbc.Driver"; - case POSTGRESQL -> "org.postgresql.Driver"; - case SQLITE -> "org.sqlite.JDBC"; - } - ); - - this.driver.setOriginal((Driver) driverClass.getConstructor().newInstance()); - DriverManager.registerDriver(this.driver); - } - - String jdbc = this.stringGetter.getJdbcString(dir, hostname, database); - boolean h2 = this.baseLibrary == BaseLibrary.H2_V1 || this.baseLibrary == BaseLibrary.H2_V2; - return new JdbcPooledConnectionSource(this.driver.getInitializer() + jdbc, - h2 ? null : user, h2 ? null : password, DatabaseTypeUtils.createDatabaseType(jdbc)); - } - - private static Connection fromDriver(Class connectionClass, String jdbc, String user, String password, boolean register) - throws ReflectiveOperationException, SQLException { - Constructor legacyConstructor = connectionClass.getConstructor(); - - Properties info = new Properties(); - if (user != null) { - info.put("user", user); - } - - if (password != null) { - info.put("password", password); - } - - Object driver = legacyConstructor.newInstance(); - - DriverManager.deregisterDriver((Driver) driver); - if (register) { - DriverManager.registerDriver((Driver) driver); - } - - Method connect = connectionClass.getDeclaredMethod("connect", String.class, Properties.class); - connect.setAccessible(true); - return (Connection) connect.invoke(driver, jdbc, info); - } - - public interface DatabaseConnector { - Connection connect(ClassLoader classLoader, Path dir, String jdbc, String user, String password) - throws ReflectiveOperationException, SQLException, IOException; - } - - public interface DatabaseStringGetter { - String getJdbcString(Path dir, String hostname, String database); - } -} diff --git a/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java b/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java new file mode 100644 index 00000000..84ccd6cc --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java @@ -0,0 +1,381 @@ +package net.elytrium.limboauth.dependencies.hikary; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import net.elytrium.limboauth.data.DataProvider; +import net.elytrium.limboauth.dependencies.IsolatedClassLoader; +import net.elytrium.limboauth.dependencies.IsolatedDriver; +import net.elytrium.limboauth.model.RegisteredPlayer; +import net.elytrium.limboauth.repository.RegisteredPlayerRepository; +import net.elytrium.limboauth.repository.exception.DataAccessException; + +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URL; +import java.sql.*; +import java.util.*; + +public class HikariRegisteredPlayerRepository implements RegisteredPlayerRepository { + + private final HikariDataSource dataSource; + + public HikariRegisteredPlayerRepository( + DataProvider dataProvider, + String hostname, + String database, + String user, + String password + ) throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, + InstantiationException, IllegalAccessException, SQLException { + String driverClassName = switch (dataProvider) { + case MYSQL -> "com.mysql.cj.jdbc.NonRegisteringDriver"; + case MARIADB -> "org.mariadb.jdbc.Driver"; + case POSTGRESQL -> "org.postgresql.Driver"; + }; + String databaseUrl = switch (dataProvider) { + case MYSQL -> "jdbc:mysql://%s/%s".formatted(hostname, database); + case POSTGRESQL -> "jdbc:postgresql://%s/%s".formatted(hostname, database); + case MARIADB -> "jdbc:mariadb://%s/%s".formatted(hostname, database); + }; + + IsolatedDriver driver = new IsolatedDriver("jdbc:limboauth_" + dataProvider.name().toLowerCase(Locale.ROOT) + ":"); + IsolatedClassLoader classLoader = new IsolatedClassLoader(new URL[] {dataProvider.getBaseLibrary().getClassLoaderURL()}); + Class driverClass = classLoader.loadClass(driverClassName); + + driver.setOriginal((Driver) driverClass.getConstructor().newInstance()); + DriverManager.registerDriver(driver); + HikariConfig config = new HikariConfig(); + config.setJdbcUrl(driver.getInitializer() + databaseUrl); + config.setUsername(user); + config.setPassword(password); + config.setMaximumPoolSize(2); + dataSource = new HikariDataSource(config); + } + + public void updateSchema(DataProvider dataProvider) throws SQLException { + try (Connection con = getConnection()) { + CallableStatement createStatement = con.prepareCall(""" + CREATE TABLE IF NOT EXISTS AUTH ( + NICKNAME varchar(255), + LOWERCASENICKNAME varchar(255) PRIMARY KEY, + HASH varchar(255), + IP varchar(255), + LOGINIP varchar(255), + TOTPTOKEN varchar(255), + REGDATE bigint, + LOGINDATE bigint, + UUID varchar(36), + PREMIUMUUID varchar(36), + ISSUEDTIME bigint + ); + """); + createStatement.execute(); + } + } + + @Override + public void deleteByLowercaseName(String name) throws DataAccessException { + try (Connection con = getConnection()) { + PreparedStatement st = con.prepareStatement("DELETE FROM AUTH where LOWERCASENICKNAME = ?"); + st.setString(1, name); + st.executeUpdate(); + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + + @Override + public Optional getByLowercaseName(String name) throws DataAccessException { + try (Connection con = getConnection()) { + PreparedStatement st = con.prepareStatement( + """ + SELECT + NICKNAME, + LOWERCASENICKNAME, + HASH, + IP, + LOGINIP, + TOTPTOKEN, + REGDATE, + LOGINDATE, + UUID, + PREMIUMUUID, + ISSUEDTIME + FROM AUTH + WHERE LOWERCASENICKNAME = ? + """ + ); + st.setString(1, name); + st.execute(); + ResultSet resultSet = st.getResultSet(); + if (resultSet.next()) { + return Optional.of(parseRegisteredPlayer(resultSet)); + } else { + return Optional.empty(); + } + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + + @Override + public List getByIp(String ip) throws DataAccessException { + try (Connection con = getConnection()) { + PreparedStatement st = con.prepareStatement( + """ + SELECT + NICKNAME, + LOWERCASENICKNAME, + HASH, + IP, + LOGINIP, + TOTPTOKEN, + REGDATE, + LOGINDATE, + UUID, + PREMIUMUUID, + ISSUEDTIME + FROM AUTH + WHERE IP = ? + """ + ); + st.setString(1, ip); + st.execute(); + ResultSet resultSet = st.getResultSet(); + List players = new ArrayList<>(); + while (resultSet.next()) { + players.add(parseRegisteredPlayer(resultSet)); + } + return Collections.unmodifiableList(players); + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + + @Override + public List getByPremiumUUID(String uuid) throws DataAccessException { + try (Connection con = getConnection()) { + PreparedStatement st = con.prepareStatement( + """ + SELECT + NICKNAME, + LOWERCASENICKNAME, + HASH, + IP, + LOGINIP, + TOTPTOKEN, + REGDATE, + LOGINDATE, + UUID, + PREMIUMUUID, + ISSUEDTIME + FROM AUTH + WHERE PREMIUMUUID = ? + """ + ); + st.setString(1, uuid); + st.execute(); + ResultSet resultSet = st.getResultSet(); + List players = new ArrayList<>(); + while (resultSet.next()) { + players.add(parseRegisteredPlayer(resultSet)); + } + return Collections.unmodifiableList(players); + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + + @Override + public void createIfNotExists(RegisteredPlayer player) throws DataAccessException { + try (Connection con = getConnection()) { + con.setAutoCommit(false); + PreparedStatement selectStatement = con.prepareStatement( + """ + SELECT + NICKNAME, + LOWERCASENICKNAME, + HASH, + IP, + LOGINIP, + TOTPTOKEN, + REGDATE, + LOGINDATE, + UUID, + PREMIUMUUID, + ISSUEDTIME + FROM AUTH + WHERE nickname = ? + """ + ); + selectStatement.setString(1, player.getLowercaseNickname()); + ResultSet resultSet = selectStatement.getResultSet(); + if (resultSet.next()) return; + PreparedStatement insertStatement = con.prepareStatement( + """ + INSERT INTO AUTH + ( + NICKNAME, + LOWERCASENICKNAME, + HASH, + IP, + LOGINIP, + TOTPTOKEN, + REGDATE, + LOGINDATE, + UUID, + PREMIUMUUID, + ISSUEDTIME + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """ + ); + insertStatement.setString(1, player.getNickname()); + insertStatement.setString(2, player.getLowercaseNickname()); + insertStatement.setString(3, player.getHash()); + insertStatement.setString(4, player.getIP()); + insertStatement.setString(5, player.getLoginIp()); + insertStatement.setString(6, player.getTotpToken()); + insertStatement.setLong(7, player.getRegDate()); + insertStatement.setLong(8, player.getLoginDate()); + insertStatement.setString(9, player.getUuid()); + insertStatement.setString(10, player.getPremiumUuid()); + insertStatement.setLong(11, player.getTokenIssuedAt()); + insertStatement.executeUpdate(); + con.commit(); + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + + @Override + public void update(RegisteredPlayer player) throws DataAccessException { + try (Connection con = getConnection()) { + PreparedStatement updateStatement = con.prepareStatement( + """ + UPDATE AUTH + SET NICKNAME = ? and + HASH = ? and + IP = ? and + LOGINIP = ? and + TOTPTOKEN = ? and + REGDATE = ? and + LOGINDATE = ? and + UUID = ? and + PREMIUMUUID = ? and + ISSUEDTIME = ? and + WHERE LOWERCASENICKNAME = ? + """ + ); + updateStatement.setString(1, player.getNickname()); + updateStatement.setString(2, player.getHash()); + updateStatement.setString(3, player.getIP()); + updateStatement.setString(4, player.getLoginIp()); + updateStatement.setString(5, player.getTotpToken()); + updateStatement.setLong(6, player.getRegDate()); + updateStatement.setLong(7, player.getLoginDate()); + updateStatement.setString(8, player.getUuid()); + updateStatement.setString(9, player.getPremiumUuid()); + updateStatement.setLong(10, player.getTokenIssuedAt()); + updateStatement.setString(11, player.getLowercaseNickname()); + updateStatement.executeUpdate(); + con.commit(); + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + + @Override + public void updateHash(String lowercaseName, String hash) throws DataAccessException { + try (Connection con = getConnection()) { + PreparedStatement st = con.prepareCall("UPDATE AUTH SET HASH = ? WHERE LOWERCASENICKNAME = ?"); + st.setString(1, hash); + st.setString(2, lowercaseName); + st.executeUpdate(); + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + + @Override + public void updateTotpToken(String lowercaseName, String totpToken) throws DataAccessException { + try (Connection con = getConnection()) { + PreparedStatement st = con.prepareCall("UPDATE AUTH SET TOTPTOKEN = ? WHERE LOWERCASENICKNAME = ?"); + st.setString(1, totpToken); + st.setString(2, lowercaseName); + st.executeUpdate(); + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + + @Override + public void updateLogin(String lowercase, String loginIp, Long loginDate) throws DataAccessException { + try (Connection con = getConnection()) { + PreparedStatement st = con.prepareCall("UPDATE AUTH SET LOGINIP = ? AND LOGINDATE = ? WHERE LOWERCASENICKNAME = ?"); + st.setString(1, loginIp); + st.setLong(1, loginDate); + st.setString(3, lowercase); + st.executeUpdate(); + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + + @Override + public boolean isHashEmptyByPremiumUuid(String uuid) throws DataAccessException { + try (Connection con = getConnection()) { + PreparedStatement st = con.prepareCall("SELECT HASH FROM AUTH WHERE PREMIUMUUID = ?"); + st.setString(1, uuid); + ResultSet resultSet = st.getResultSet(); + return resultSet.next() && "".equals(resultSet.getString("HASH")); + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + + @Override + public boolean isHashEmptyByLowercaseName(String name) throws DataAccessException { + try (Connection con = getConnection()) { + PreparedStatement st = con.prepareCall("SELECT HASH FROM AUTH WHERE LOWERCASENICKNAME = ?"); + st.setString(1, name); + ResultSet resultSet = st.getResultSet(); + return resultSet.next() && "".equals(resultSet.getString("HASH")); + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + + public int registeredPlayerCount() { + try (Connection con = getConnection()) { + CallableStatement st = con.prepareCall("SELECT COUNT(*) FROM AUTH"); + ResultSet set = st.getResultSet(); + if (set.next()) { + return set.getInt(1); + } else { + return 0; + } + } catch (SQLException e) { + return 0; + } + } + + private RegisteredPlayer parseRegisteredPlayer(ResultSet resultSet) throws SQLException { + return RegisteredPlayer.builder() + .nickname(resultSet.getString("NICKNAME")) + .lowercaseNickname(resultSet.getString("LOWERCASENICKNAME")) + .hash(resultSet.getString("HASH")) + .ip(resultSet.getString("IP")) + .totpToken(resultSet.getString("TOTPTOKEN")) + .regDate(resultSet.getLong("REGDATE")) + .uuid(resultSet.getString("UUID")) + .premiumUuid(resultSet.getString("PREMIUMUUID")) + .loginIp(resultSet.getString("LOGINIP")) + .loginDate(resultSet.getLong("LOGINDATE")) + .tokenIssuedAt(resultSet.getLong("ISSUEDTIME")) + .build(); + } + + protected Connection getConnection() throws SQLException { + return dataSource.getConnection(); + } +} diff --git a/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java b/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java index fd32fdd6..b577f37c 100644 --- a/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java +++ b/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java @@ -19,7 +19,6 @@ import at.favre.lib.crypto.bcrypt.BCrypt; import com.google.common.primitives.Longs; -import com.j256.ormlite.dao.Dao; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; import dev.samstevens.totp.code.CodeVerifier; @@ -28,15 +27,6 @@ import dev.samstevens.totp.time.SystemTimeProvider; import io.netty.buffer.ByteBuf; import io.whitfin.siphash.SipHasher; -import java.nio.charset.StandardCharsets; -import java.sql.SQLException; -import java.text.MessageFormat; -import java.util.List; -import java.util.Locale; -import java.util.UUID; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import net.elytrium.commons.kyori.serialization.Serializer; import net.elytrium.limboapi.api.Limbo; import net.elytrium.limboapi.api.LimboSessionHandler; @@ -47,555 +37,570 @@ import net.elytrium.limboauth.event.PostRegisterEvent; import net.elytrium.limboauth.event.TaskEvent; import net.elytrium.limboauth.migration.MigrationHash; +import net.elytrium.limboauth.model.DataAccessRuntimeException; import net.elytrium.limboauth.model.RegisteredPlayer; -import net.elytrium.limboauth.model.SQLRuntimeException; +import net.elytrium.limboauth.repository.RegisteredPlayerRepository; +import net.elytrium.limboauth.repository.exception.DataAccessException; import net.kyori.adventure.bossbar.BossBar; import net.kyori.adventure.text.Component; import net.kyori.adventure.title.Title; import org.checkerframework.checker.nullness.qual.Nullable; +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.List; +import java.util.Locale; +import java.util.UUID; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + public class AuthSessionHandler implements LimboSessionHandler { - public static final CodeVerifier TOTP_CODE_VERIFIER = new DefaultCodeVerifier(new DefaultCodeGenerator(), new SystemTimeProvider()); - private static final BCrypt.Verifyer HASH_VERIFIER = BCrypt.verifyer(); - private static final BCrypt.Hasher HASHER = BCrypt.withDefaults(); - - private static Component ratelimited; - private static BossBar.Color bossbarColor; - private static BossBar.Overlay bossbarOverlay; - private static Component ipLimitKick; - private static Component databaseErrorKick; - private static String wrongNicknameCaseKick; - private static Component timesUp; - private static Component registerSuccessful; - @Nullable - private static Title registerSuccessfulTitle; - private static Component[] loginWrongPassword; - private static Component loginWrongPasswordKick; - private static Component totp; - @Nullable - private static Title totpTitle; - private static Component register; - @Nullable - private static Title registerTitle; - private static Component[] login; - @Nullable - private static Title loginTitle; - private static Component registerDifferentPasswords; - private static Component registerPasswordTooLong; - private static Component registerPasswordTooShort; - private static Component registerPasswordUnsafe; - private static Component loginSuccessful; - private static Component sessionExpired; - @Nullable - private static Title loginSuccessfulTitle; - @Nullable - private static MigrationHash migrationHash; - - private final Dao playerDao; - private final Player proxyPlayer; - private final LimboAuth plugin; - - private final long joinTime = System.currentTimeMillis(); - private final BossBar bossBar = BossBar.bossBar( - Component.empty(), - 1.0F, - bossbarColor, - bossbarOverlay - ); - private final boolean loginOnlyByMod = Settings.IMP.MAIN.MOD.ENABLED && Settings.IMP.MAIN.MOD.LOGIN_ONLY_BY_MOD; - - @Nullable - private RegisteredPlayer playerInfo; - - private ScheduledFuture authMainTask; - - private LimboPlayer player; - private int attempts = Settings.IMP.MAIN.LOGIN_ATTEMPTS; - private boolean totpState; - private String tempPassword; - private boolean tokenReceived; - - public AuthSessionHandler(Dao playerDao, Player proxyPlayer, LimboAuth plugin, @Nullable RegisteredPlayer playerInfo) { - this.playerDao = playerDao; - this.proxyPlayer = proxyPlayer; - this.plugin = plugin; - this.playerInfo = playerInfo; - } - - @Override - public void onSpawn(Limbo server, LimboPlayer player) { - this.player = player; - - if (Settings.IMP.MAIN.DISABLE_FALLING) { - this.player.disableFalling(); - } else { - this.player.enableFalling(); + public static final CodeVerifier TOTP_CODE_VERIFIER = new DefaultCodeVerifier(new DefaultCodeGenerator(), new SystemTimeProvider()); + private static final BCrypt.Verifyer HASH_VERIFIER = BCrypt.verifyer(); + private static final BCrypt.Hasher HASHER = BCrypt.withDefaults(); + + private static Component ratelimited; + private static BossBar.Color bossbarColor; + private static BossBar.Overlay bossbarOverlay; + private static Component ipLimitKick; + private static Component databaseErrorKick; + private static String wrongNicknameCaseKick; + private static Component timesUp; + private static Component registerSuccessful; + @Nullable + private static Title registerSuccessfulTitle; + private static Component[] loginWrongPassword; + private static Component loginWrongPasswordKick; + private static Component totp; + @Nullable + private static Title totpTitle; + private static Component register; + @Nullable + private static Title registerTitle; + private static Component[] login; + @Nullable + private static Title loginTitle; + private static Component registerDifferentPasswords; + private static Component registerPasswordTooLong; + private static Component registerPasswordTooShort; + private static Component registerPasswordUnsafe; + private static Component loginSuccessful; + private static Component sessionExpired; + @Nullable + private static Title loginSuccessfulTitle; + @Nullable + private static MigrationHash migrationHash; + + private final RegisteredPlayerRepository registeredPlayerRepository; + private final Player proxyPlayer; + private final LimboAuth plugin; + + private final long joinTime = System.currentTimeMillis(); + private final BossBar bossBar = BossBar.bossBar( + Component.empty(), + 1.0F, + bossbarColor, + bossbarOverlay + ); + private final boolean loginOnlyByMod = Settings.IMP.MAIN.MOD.ENABLED && Settings.IMP.MAIN.MOD.LOGIN_ONLY_BY_MOD; + + @Nullable + private RegisteredPlayer playerInfo; + + private ScheduledFuture authMainTask; + + private LimboPlayer player; + private int attempts = Settings.IMP.MAIN.LOGIN_ATTEMPTS; + private boolean totpState; + private String tempPassword; + private boolean tokenReceived; + + public AuthSessionHandler(RegisteredPlayerRepository registeredPlayerRepository, Player proxyPlayer, LimboAuth plugin, @Nullable RegisteredPlayer playerInfo) { + this.registeredPlayerRepository = registeredPlayerRepository; + this.proxyPlayer = proxyPlayer; + this.plugin = plugin; + this.playerInfo = playerInfo; } - Serializer serializer = LimboAuth.getSerializer(); - - if (this.playerInfo == null) { - try { - String ip = this.proxyPlayer.getRemoteAddress().getAddress().getHostAddress(); - List alreadyRegistered = this.playerDao.queryForEq(RegisteredPlayer.IP_FIELD, ip); - if (alreadyRegistered != null) { - int sizeOfValidRegistrations = alreadyRegistered.size(); - if (Settings.IMP.MAIN.IP_LIMIT_VALID_TIME > 0) { - for (RegisteredPlayer registeredPlayer : alreadyRegistered.stream() - .filter(registeredPlayer -> registeredPlayer.getRegDate() < System.currentTimeMillis() - Settings.IMP.MAIN.IP_LIMIT_VALID_TIME) - .collect(Collectors.toList())) { - registeredPlayer.setIP(""); - this.playerDao.update(registeredPlayer); - --sizeOfValidRegistrations; - } - } + @Override + public void onSpawn(Limbo server, LimboPlayer player) { + this.player = player; - if (sizeOfValidRegistrations >= Settings.IMP.MAIN.IP_LIMIT_REGISTRATIONS) { - this.proxyPlayer.disconnect(ipLimitKick); - return; - } - } - } catch (SQLException e) { - this.proxyPlayer.disconnect(databaseErrorKick); - throw new SQLRuntimeException(e); - } - } else { - if (!this.proxyPlayer.getUsername().equals(this.playerInfo.getNickname())) { - this.proxyPlayer.disconnect(serializer.deserialize( - MessageFormat.format(wrongNicknameCaseKick, this.playerInfo.getNickname(), this.proxyPlayer.getUsername())) - ); - return; - } - - this.plugin.addAuthenticatingPlayer(player.getProxyPlayer().getUsername(), this); - } - - boolean bossBarEnabled = !this.loginOnlyByMod && Settings.IMP.MAIN.ENABLE_BOSSBAR; - int authTime = Settings.IMP.MAIN.AUTH_TIME; - float multiplier = 1000.0F / authTime; - this.authMainTask = this.player.getScheduledExecutor().scheduleWithFixedDelay(() -> { - if (System.currentTimeMillis() - this.joinTime > authTime) { - this.proxyPlayer.disconnect(timesUp); - } else { - if (bossBarEnabled) { - float secondsLeft = (authTime - (System.currentTimeMillis() - this.joinTime)) / 1000.0F; - this.bossBar.name(serializer.deserialize(MessageFormat.format(Settings.IMP.MAIN.STRINGS.BOSSBAR, (int) secondsLeft))); - // It's possible, that the progress value can overcome 1, e.g. 1.0000001. - this.bossBar.progress(Math.min(1.0F, secondsLeft * multiplier)); + if (Settings.IMP.MAIN.DISABLE_FALLING) { + this.player.disableFalling(); + } else { + this.player.enableFalling(); } - } - }, 0, 1, TimeUnit.SECONDS); - if (bossBarEnabled) { - this.proxyPlayer.showBossBar(this.bossBar); - } + Serializer serializer = LimboAuth.getSerializer(); - if (!this.loginOnlyByMod) { - this.sendMessage(true); - } - } + if (this.playerInfo == null) { + try { + String ip = this.proxyPlayer.getRemoteAddress().getAddress().getHostAddress(); + List alreadyRegistered = this.registeredPlayerRepository.getByIp(ip); + if (alreadyRegistered != null) { + int sizeOfValidRegistrations = alreadyRegistered.size(); + if (Settings.IMP.MAIN.IP_LIMIT_VALID_TIME > 0) { + for (RegisteredPlayer registeredPlayer : alreadyRegistered.stream() + .filter(registeredPlayer -> registeredPlayer.getRegDate() < System.currentTimeMillis() - Settings.IMP.MAIN.IP_LIMIT_VALID_TIME) + .collect(Collectors.toList())) { + registeredPlayer.setIp(""); + this.registeredPlayerRepository.update(registeredPlayer); + --sizeOfValidRegistrations; + } + } + + if (sizeOfValidRegistrations >= Settings.IMP.MAIN.IP_LIMIT_REGISTRATIONS) { + this.proxyPlayer.disconnect(ipLimitKick); + return; + } + } + } catch (DataAccessException e) { + this.proxyPlayer.disconnect(databaseErrorKick); + throw new DataAccessRuntimeException(e); + } + } else { + if (!this.proxyPlayer.getUsername().equals(this.playerInfo.getNickname())) { + this.proxyPlayer.disconnect(serializer.deserialize( + MessageFormat.format(wrongNicknameCaseKick, this.playerInfo.getNickname(), this.proxyPlayer.getUsername())) + ); + return; + } - @Override - public void onChat(String message) { - if (this.loginOnlyByMod) { - return; - } + this.plugin.addAuthenticatingPlayer(player.getProxyPlayer().getUsername(), this); + } - if (!LimboAuth.RATELIMITER.attempt(this.proxyPlayer.getRemoteAddress().getAddress())) { - this.proxyPlayer.sendMessage(AuthSessionHandler.ratelimited); - return; - } + boolean bossBarEnabled = !this.loginOnlyByMod && Settings.IMP.MAIN.ENABLE_BOSSBAR; + int authTime = Settings.IMP.MAIN.AUTH_TIME; + float multiplier = 1000.0F / authTime; + this.authMainTask = this.player.getScheduledExecutor().scheduleWithFixedDelay(() -> { + if (System.currentTimeMillis() - this.joinTime > authTime) { + this.proxyPlayer.disconnect(timesUp); + } else { + if (bossBarEnabled) { + float secondsLeft = (authTime - (System.currentTimeMillis() - this.joinTime)) / 1000.0F; + this.bossBar.name(serializer.deserialize(MessageFormat.format(Settings.IMP.MAIN.STRINGS.BOSSBAR, (int) secondsLeft))); + // It's possible, that the progress value can overcome 1, e.g. 1.0000001. + this.bossBar.progress(Math.min(1.0F, secondsLeft * multiplier)); + } + } + }, 0, 1, TimeUnit.SECONDS); - String[] args = message.split(" "); - if (args.length != 0 && this.checkArgsLength(args.length)) { - Command command = Command.parse(args[0]); - if (command == Command.REGISTER && !this.totpState && this.playerInfo == null) { - String password = args[1]; - if (this.checkPasswordsRepeat(args) && this.checkPasswordLength(password) && this.checkPasswordStrength(password)) { - this.saveTempPassword(password); - RegisteredPlayer registeredPlayer = new RegisteredPlayer(this.proxyPlayer).setPassword(password); - - try { - this.playerDao.create(registeredPlayer); - this.playerInfo = registeredPlayer; - } catch (SQLException e) { - this.proxyPlayer.disconnect(databaseErrorKick); - throw new SQLRuntimeException(e); - } - - this.proxyPlayer.sendMessage(registerSuccessful); - if (registerSuccessfulTitle != null) { - this.proxyPlayer.showTitle(registerSuccessfulTitle); - } - - this.plugin.getServer().getEventManager() - .fire(new PostRegisterEvent(this::finishAuth, this.player, this.playerInfo, this.tempPassword)) - .thenAcceptAsync(this::finishAuth); + if (bossBarEnabled) { + this.proxyPlayer.showBossBar(this.bossBar); } - // {@code return} placed here (not above), because - // AuthSessionHandler#checkPasswordsRepeat, AuthSessionHandler#checkPasswordLength, and AuthSessionHandler#checkPasswordStrength methods are - // invoking Player#sendMessage that sends its own message in case if the return value is false. - // If we don't place {@code return} here, an another message (AuthSessionHandler#sendMessage) will be sent. - return; - } else if (command == Command.LOGIN && !this.totpState && this.playerInfo != null) { - String password = args[1]; - this.saveTempPassword(password); - - if (password.length() > 0 && checkPassword(password, this.playerInfo, this.playerDao)) { - if (this.playerInfo.getTotpToken().isEmpty()) { - this.finishLogin(); - } else { - this.totpState = true; + if (!this.loginOnlyByMod) { this.sendMessage(true); - } - } else if (--this.attempts != 0) { - this.proxyPlayer.sendMessage(loginWrongPassword[this.attempts - 1]); - this.checkBruteforceAttempts(); - } else { - this.proxyPlayer.disconnect(loginWrongPasswordKick); } - - return; - } else if (command == Command.TOTP && this.totpState && this.playerInfo != null) { - if (TOTP_CODE_VERIFIER.isValidCode(this.playerInfo.getTotpToken(), args[1])) { - this.finishLogin(); - return; - } else { - this.checkBruteforceAttempts(); - } - } } - this.sendMessage(false); - } - - @Override - public void onGeneric(Object packet) { - if (Settings.IMP.MAIN.MOD.ENABLED && packet instanceof PluginMessagePacket) { - PluginMessagePacket pluginMessage = (PluginMessagePacket) packet; - String channel = pluginMessage.getChannel(); - - if (channel.equals("MC|Brand") || channel.equals("minecraft:brand")) { - // Minecraft can't handle the plugin message immediately after going to the PLAY - // state, so we have to postpone sending it - if (Settings.IMP.MAIN.MOD.ENABLED) { - this.proxyPlayer.sendPluginMessage(this.plugin.getChannelIdentifier(this.proxyPlayer), new byte[0]); - } - } else if (channel.equals(this.plugin.getChannelIdentifier(this.proxyPlayer).getId())) { - if (this.tokenReceived) { - this.checkBruteforceAttempts(); - this.proxyPlayer.disconnect(Component.empty()); - return; + @Override + public void onChat(String message) { + if (this.loginOnlyByMod) { + return; } - this.tokenReceived = true; - - if (this.playerInfo == null) { - return; + if (!LimboAuth.RATELIMITER.attempt(this.proxyPlayer.getRemoteAddress().getAddress())) { + this.proxyPlayer.sendMessage(AuthSessionHandler.ratelimited); + return; } - ByteBuf data = pluginMessage.content(); - - if (data.readableBytes() < 16) { - this.checkBruteforceAttempts(); - this.proxyPlayer.sendMessage(sessionExpired); - return; + String[] args = message.split(" "); + if (args.length != 0 && this.checkArgsLength(args.length)) { + Command command = Command.parse(args[0]); + if (command == Command.REGISTER && !this.totpState && this.playerInfo == null) { + String password = args[1]; + if (this.checkPasswordsRepeat(args) && this.checkPasswordLength(password) && this.checkPasswordStrength(password)) { + this.saveTempPassword(password); + RegisteredPlayer registeredPlayer = new RegisteredPlayer(this.proxyPlayer).setPassword(password); + + try { + this.registeredPlayerRepository.createIfNotExists(registeredPlayer); + this.playerInfo = registeredPlayer; + } catch (DataAccessException e) { + this.proxyPlayer.disconnect(databaseErrorKick); + throw new DataAccessRuntimeException(e); + } + + this.proxyPlayer.sendMessage(registerSuccessful); + if (registerSuccessfulTitle != null) { + this.proxyPlayer.showTitle(registerSuccessfulTitle); + } + + this.plugin.getServer().getEventManager() + .fire(new PostRegisterEvent(this::finishAuth, this.player, this.playerInfo, this.tempPassword)) + .thenAcceptAsync(this::finishAuth); + } + + // {@code return} placed here (not above), because + // AuthSessionHandler#checkPasswordsRepeat, AuthSessionHandler#checkPasswordLength, and AuthSessionHandler#checkPasswordStrength methods are + // invoking Player#sendMessage that sends its own message in case if the return value is false. + // If we don't place {@code return} here, an another message (AuthSessionHandler#sendMessage) will be sent. + return; + } else if (command == Command.LOGIN && !this.totpState && this.playerInfo != null) { + String password = args[1]; + this.saveTempPassword(password); + + if (password.length() > 0 && checkPassword(password, this.playerInfo, this.registeredPlayerRepository)) { + if (this.playerInfo.getTotpToken().isEmpty()) { + this.finishLogin(); + } else { + this.totpState = true; + this.sendMessage(true); + } + } else if (--this.attempts != 0) { + this.proxyPlayer.sendMessage(loginWrongPassword[this.attempts - 1]); + this.checkBruteforceAttempts(); + } else { + this.proxyPlayer.disconnect(loginWrongPasswordKick); + } + + return; + } else if (command == Command.TOTP && this.totpState && this.playerInfo != null) { + if (TOTP_CODE_VERIFIER.isValidCode(this.playerInfo.getTotpToken(), args[1])) { + this.finishLogin(); + return; + } else { + this.checkBruteforceAttempts(); + } + } } - long issueTime = data.readLong(); - long hash = data.readLong(); + this.sendMessage(false); + } - if (this.playerInfo.getTokenIssuedAt() > issueTime) { - this.proxyPlayer.sendMessage(sessionExpired); - return; + @Override + public void onGeneric(Object packet) { + if (Settings.IMP.MAIN.MOD.ENABLED && packet instanceof PluginMessagePacket) { + PluginMessagePacket pluginMessage = (PluginMessagePacket) packet; + String channel = pluginMessage.getChannel(); + + if (channel.equals("MC|Brand") || channel.equals("minecraft:brand")) { + // Minecraft can't handle the plugin message immediately after going to the PLAY + // state, so we have to postpone sending it + if (Settings.IMP.MAIN.MOD.ENABLED) { + this.proxyPlayer.sendPluginMessage(this.plugin.getChannelIdentifier(this.proxyPlayer), new byte[0]); + } + } else if (channel.equals(this.plugin.getChannelIdentifier(this.proxyPlayer).getId())) { + if (this.tokenReceived) { + this.checkBruteforceAttempts(); + this.proxyPlayer.disconnect(Component.empty()); + return; + } + + this.tokenReceived = true; + + if (this.playerInfo == null) { + return; + } + + ByteBuf data = pluginMessage.content(); + + if (data.readableBytes() < 16) { + this.checkBruteforceAttempts(); + this.proxyPlayer.sendMessage(sessionExpired); + return; + } + + long issueTime = data.readLong(); + long hash = data.readLong(); + + if (this.playerInfo.getTokenIssuedAt() > issueTime) { + this.proxyPlayer.sendMessage(sessionExpired); + return; + } + + byte[] lowercaseNicknameSerialized = this.playerInfo.getLowercaseNickname().getBytes(StandardCharsets.UTF_8); + long correctHash = SipHasher.init(Settings.IMP.MAIN.MOD.VERIFY_KEY) + .update(lowercaseNicknameSerialized) + .update(Longs.toByteArray(issueTime)) + .digest(); + + if (hash != correctHash) { + this.checkBruteforceAttempts(); + this.proxyPlayer.sendMessage(sessionExpired); + return; + } + + this.finishAuth(); + } } + } - byte[] lowercaseNicknameSerialized = this.playerInfo.getLowercaseNickname().getBytes(StandardCharsets.UTF_8); - long correctHash = SipHasher.init(Settings.IMP.MAIN.MOD.VERIFY_KEY) - .update(lowercaseNicknameSerialized) - .update(Longs.toByteArray(issueTime)) - .digest(); - - if (hash != correctHash) { - this.checkBruteforceAttempts(); - this.proxyPlayer.sendMessage(sessionExpired); - return; + private void checkBruteforceAttempts() { + this.plugin.incrementBruteforceAttempts(this.proxyPlayer.getRemoteAddress().getAddress()); + if (this.plugin.getBruteforceAttempts(this.proxyPlayer.getRemoteAddress().getAddress()) >= Settings.IMP.MAIN.BRUTEFORCE_MAX_ATTEMPTS) { + this.proxyPlayer.disconnect(loginWrongPasswordKick); } - - this.finishAuth(); - } } - } - private void checkBruteforceAttempts() { - this.plugin.incrementBruteforceAttempts(this.proxyPlayer.getRemoteAddress().getAddress()); - if (this.plugin.getBruteforceAttempts(this.proxyPlayer.getRemoteAddress().getAddress()) >= Settings.IMP.MAIN.BRUTEFORCE_MAX_ATTEMPTS) { - this.proxyPlayer.disconnect(loginWrongPasswordKick); + private void saveTempPassword(String password) { + this.tempPassword = password; } - } - private void saveTempPassword(String password) { - this.tempPassword = password; - } + @Override + public void onDisconnect() { + if (this.authMainTask != null) { + this.authMainTask.cancel(true); + } - @Override - public void onDisconnect() { - if (this.authMainTask != null) { - this.authMainTask.cancel(true); + this.proxyPlayer.hideBossBar(this.bossBar); + this.plugin.removeAuthenticatingPlayer(this.player.getProxyPlayer().getUsername()); } - this.proxyPlayer.hideBossBar(this.bossBar); - this.plugin.removeAuthenticatingPlayer(this.player.getProxyPlayer().getUsername()); - } - - private void sendMessage(boolean sendTitle) { - if (this.totpState) { - this.proxyPlayer.sendMessage(totp); - if (sendTitle && totpTitle != null) { - this.proxyPlayer.showTitle(totpTitle); - } - } else if (this.playerInfo == null) { - this.proxyPlayer.sendMessage(register); - if (sendTitle && registerTitle != null) { - this.proxyPlayer.showTitle(registerTitle); - } - } else { - this.proxyPlayer.sendMessage(login[this.attempts - 1]); - if (sendTitle && loginTitle != null) { - this.proxyPlayer.showTitle(loginTitle); - } + private void sendMessage(boolean sendTitle) { + if (this.totpState) { + this.proxyPlayer.sendMessage(totp); + if (sendTitle && totpTitle != null) { + this.proxyPlayer.showTitle(totpTitle); + } + } else if (this.playerInfo == null) { + this.proxyPlayer.sendMessage(register); + if (sendTitle && registerTitle != null) { + this.proxyPlayer.showTitle(registerTitle); + } + } else { + this.proxyPlayer.sendMessage(login[this.attempts - 1]); + if (sendTitle && loginTitle != null) { + this.proxyPlayer.showTitle(loginTitle); + } + } } - } - private boolean checkArgsLength(int argsLength) { - if (this.playerInfo == null && Settings.IMP.MAIN.REGISTER_NEED_REPEAT_PASSWORD) { - return argsLength == 3; - } else { - return argsLength == 2; - } - } - - private boolean checkPasswordsRepeat(String[] args) { - if (!Settings.IMP.MAIN.REGISTER_NEED_REPEAT_PASSWORD || args[1].equals(args[2])) { - return true; - } else { - this.proxyPlayer.sendMessage(registerDifferentPasswords); - return false; + private boolean checkArgsLength(int argsLength) { + if (this.playerInfo == null && Settings.IMP.MAIN.REGISTER_NEED_REPEAT_PASSWORD) { + return argsLength == 3; + } else { + return argsLength == 2; + } } - } - - private boolean checkPasswordLength(String password) { - int length = password.length(); - if (length > Settings.IMP.MAIN.MAX_PASSWORD_LENGTH) { - this.proxyPlayer.sendMessage(registerPasswordTooLong); - return false; - } else if (length < Settings.IMP.MAIN.MIN_PASSWORD_LENGTH) { - this.proxyPlayer.sendMessage(registerPasswordTooShort); - return false; - } else { - return true; + + private boolean checkPasswordsRepeat(String[] args) { + if (!Settings.IMP.MAIN.REGISTER_NEED_REPEAT_PASSWORD || args[1].equals(args[2])) { + return true; + } else { + this.proxyPlayer.sendMessage(registerDifferentPasswords); + return false; + } } - } - - private boolean checkPasswordStrength(String password) { - if (Settings.IMP.MAIN.CHECK_PASSWORD_STRENGTH && this.plugin.getUnsafePasswords().contains(password)) { - this.proxyPlayer.sendMessage(registerPasswordUnsafe); - return false; - } else { - return true; + + private boolean checkPasswordLength(String password) { + int length = password.length(); + if (length > Settings.IMP.MAIN.MAX_PASSWORD_LENGTH) { + this.proxyPlayer.sendMessage(registerPasswordTooLong); + return false; + } else if (length < Settings.IMP.MAIN.MIN_PASSWORD_LENGTH) { + this.proxyPlayer.sendMessage(registerPasswordTooShort); + return false; + } else { + return true; + } } - } - public void finishLogin() { - this.proxyPlayer.sendMessage(loginSuccessful); - if (loginSuccessfulTitle != null) { - this.proxyPlayer.showTitle(loginSuccessfulTitle); + private boolean checkPasswordStrength(String password) { + if (Settings.IMP.MAIN.CHECK_PASSWORD_STRENGTH && this.plugin.getUnsafePasswords().contains(password)) { + this.proxyPlayer.sendMessage(registerPasswordUnsafe); + return false; + } else { + return true; + } } - this.plugin.clearBruteforceAttempts(this.proxyPlayer.getRemoteAddress().getAddress()); + public void finishLogin() { + this.proxyPlayer.sendMessage(loginSuccessful); + if (loginSuccessfulTitle != null) { + this.proxyPlayer.showTitle(loginSuccessfulTitle); + } - this.plugin.getServer().getEventManager() - .fire(new PostAuthorizationEvent(this::finishAuth, this.player, this.playerInfo, this.tempPassword)) - .thenAcceptAsync(this::finishAuth); - } + this.plugin.clearBruteforceAttempts(this.proxyPlayer.getRemoteAddress().getAddress()); - private void finishAuth(TaskEvent event) { - if (event.getResult() == TaskEvent.Result.CANCEL) { - this.proxyPlayer.disconnect(event.getReason()); - return; - } else if (event.getResult() == TaskEvent.Result.WAIT) { - return; + this.plugin.getServer().getEventManager() + .fire(new PostAuthorizationEvent(this::finishAuth, this.player, this.playerInfo, this.tempPassword)) + .thenAcceptAsync(this::finishAuth); } - this.finishAuth(); - } + private void finishAuth(TaskEvent event) { + if (event.getResult() == TaskEvent.Result.CANCEL) { + this.proxyPlayer.disconnect(event.getReason()); + return; + } else if (event.getResult() == TaskEvent.Result.WAIT) { + return; + } - private void finishAuth() { - if (Settings.IMP.MAIN.CRACKED_TITLE_SETTINGS.CLEAR_AFTER_LOGIN) { - this.proxyPlayer.clearTitle(); + this.finishAuth(); } - try { - this.plugin.updateLoginData(this.proxyPlayer); - } catch (SQLException e) { - throw new SQLRuntimeException(e); - } catch (Throwable e) { - e.printStackTrace(); - } + private void finishAuth() { + if (Settings.IMP.MAIN.CRACKED_TITLE_SETTINGS.CLEAR_AFTER_LOGIN) { + this.proxyPlayer.clearTitle(); + } - this.plugin.cacheAuthUser(this.proxyPlayer); - this.player.disconnect(); - } - - public static void reload() { - Serializer serializer = LimboAuth.getSerializer(); - AuthSessionHandler.ratelimited = serializer.deserialize(Settings.IMP.MAIN.STRINGS.RATELIMITED); - bossbarColor = Settings.IMP.MAIN.BOSSBAR_COLOR; - bossbarOverlay = Settings.IMP.MAIN.BOSSBAR_OVERLAY; - ipLimitKick = serializer.deserialize(Settings.IMP.MAIN.STRINGS.IP_LIMIT_KICK); - databaseErrorKick = serializer.deserialize(Settings.IMP.MAIN.STRINGS.DATABASE_ERROR_KICK); - wrongNicknameCaseKick = Settings.IMP.MAIN.STRINGS.WRONG_NICKNAME_CASE_KICK; - timesUp = serializer.deserialize(Settings.IMP.MAIN.STRINGS.TIMES_UP); - registerSuccessful = serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_SUCCESSFUL); - if (Settings.IMP.MAIN.STRINGS.REGISTER_SUCCESSFUL_TITLE.isEmpty() && Settings.IMP.MAIN.STRINGS.REGISTER_SUCCESSFUL_SUBTITLE.isEmpty()) { - registerSuccessfulTitle = null; - } else { - registerSuccessfulTitle = Title.title( - serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_SUCCESSFUL_TITLE), - serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_SUCCESSFUL_SUBTITLE), - Settings.IMP.MAIN.CRACKED_TITLE_SETTINGS.toTimes() - ); - } - int loginAttempts = Settings.IMP.MAIN.LOGIN_ATTEMPTS; - loginWrongPassword = new Component[loginAttempts]; - for (int i = 0; i < loginAttempts; ++i) { - loginWrongPassword[i] = serializer.deserialize(MessageFormat.format(Settings.IMP.MAIN.STRINGS.LOGIN_WRONG_PASSWORD, i + 1)); - } - loginWrongPasswordKick = serializer.deserialize(Settings.IMP.MAIN.STRINGS.LOGIN_WRONG_PASSWORD_KICK); - totp = serializer.deserialize(Settings.IMP.MAIN.STRINGS.TOTP); - if (Settings.IMP.MAIN.STRINGS.TOTP_TITLE.isEmpty() && Settings.IMP.MAIN.STRINGS.TOTP_SUBTITLE.isEmpty()) { - totpTitle = null; - } else { - totpTitle = Title.title( - serializer.deserialize(Settings.IMP.MAIN.STRINGS.TOTP_TITLE), - serializer.deserialize(Settings.IMP.MAIN.STRINGS.TOTP_SUBTITLE), - Settings.IMP.MAIN.CRACKED_TITLE_SETTINGS.toTimes() - ); - } - register = serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER); - if (Settings.IMP.MAIN.STRINGS.REGISTER_TITLE.isEmpty() && Settings.IMP.MAIN.STRINGS.REGISTER_SUBTITLE.isEmpty()) { - registerTitle = null; - } else { - registerTitle = Title.title( - serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_TITLE), - serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_SUBTITLE), - Settings.IMP.MAIN.CRACKED_TITLE_SETTINGS.toTimes() - ); - } - login = new Component[loginAttempts]; - for (int i = 0; i < loginAttempts; ++i) { - login[i] = serializer.deserialize(MessageFormat.format(Settings.IMP.MAIN.STRINGS.LOGIN, i + 1)); - } - if (Settings.IMP.MAIN.STRINGS.LOGIN_TITLE.isEmpty() && Settings.IMP.MAIN.STRINGS.LOGIN_SUBTITLE.isEmpty()) { - loginTitle = null; - } else { - loginTitle = Title.title( - serializer.deserialize(MessageFormat.format(Settings.IMP.MAIN.STRINGS.LOGIN_TITLE, loginAttempts)), - serializer.deserialize(MessageFormat.format(Settings.IMP.MAIN.STRINGS.LOGIN_SUBTITLE, loginAttempts)), - Settings.IMP.MAIN.CRACKED_TITLE_SETTINGS.toTimes() - ); + try { + this.plugin.updateLoginData(this.proxyPlayer); + } catch (DataAccessException e) { + throw new DataAccessRuntimeException(e); + } catch (Throwable e) { + e.printStackTrace(); + } + + this.plugin.cacheAuthUser(this.proxyPlayer); + this.player.disconnect(); } - registerDifferentPasswords = serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_DIFFERENT_PASSWORDS); - registerPasswordTooLong = serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_PASSWORD_TOO_LONG); - registerPasswordTooShort = serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_PASSWORD_TOO_SHORT); - registerPasswordUnsafe = serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_PASSWORD_UNSAFE); - loginSuccessful = serializer.deserialize(Settings.IMP.MAIN.STRINGS.LOGIN_SUCCESSFUL); - sessionExpired = serializer.deserialize(Settings.IMP.MAIN.STRINGS.MOD_SESSION_EXPIRED); - if (Settings.IMP.MAIN.STRINGS.LOGIN_SUCCESSFUL_TITLE.isEmpty() && Settings.IMP.MAIN.STRINGS.LOGIN_SUCCESSFUL_SUBTITLE.isEmpty()) { - loginSuccessfulTitle = null; - } else { - loginSuccessfulTitle = Title.title( - serializer.deserialize(Settings.IMP.MAIN.STRINGS.LOGIN_SUCCESSFUL_TITLE), - serializer.deserialize(Settings.IMP.MAIN.STRINGS.LOGIN_SUCCESSFUL_SUBTITLE), - Settings.IMP.MAIN.CRACKED_TITLE_SETTINGS.toTimes() - ); + + public static void reload() { + Serializer serializer = LimboAuth.getSerializer(); + AuthSessionHandler.ratelimited = serializer.deserialize(Settings.IMP.MAIN.STRINGS.RATELIMITED); + bossbarColor = Settings.IMP.MAIN.BOSSBAR_COLOR; + bossbarOverlay = Settings.IMP.MAIN.BOSSBAR_OVERLAY; + ipLimitKick = serializer.deserialize(Settings.IMP.MAIN.STRINGS.IP_LIMIT_KICK); + databaseErrorKick = serializer.deserialize(Settings.IMP.MAIN.STRINGS.DATABASE_ERROR_KICK); + wrongNicknameCaseKick = Settings.IMP.MAIN.STRINGS.WRONG_NICKNAME_CASE_KICK; + timesUp = serializer.deserialize(Settings.IMP.MAIN.STRINGS.TIMES_UP); + registerSuccessful = serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_SUCCESSFUL); + if (Settings.IMP.MAIN.STRINGS.REGISTER_SUCCESSFUL_TITLE.isEmpty() && Settings.IMP.MAIN.STRINGS.REGISTER_SUCCESSFUL_SUBTITLE.isEmpty()) { + registerSuccessfulTitle = null; + } else { + registerSuccessfulTitle = Title.title( + serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_SUCCESSFUL_TITLE), + serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_SUCCESSFUL_SUBTITLE), + Settings.IMP.MAIN.CRACKED_TITLE_SETTINGS.toTimes() + ); + } + int loginAttempts = Settings.IMP.MAIN.LOGIN_ATTEMPTS; + loginWrongPassword = new Component[loginAttempts]; + for (int i = 0; i < loginAttempts; ++i) { + loginWrongPassword[i] = serializer.deserialize(MessageFormat.format(Settings.IMP.MAIN.STRINGS.LOGIN_WRONG_PASSWORD, i + 1)); + } + loginWrongPasswordKick = serializer.deserialize(Settings.IMP.MAIN.STRINGS.LOGIN_WRONG_PASSWORD_KICK); + totp = serializer.deserialize(Settings.IMP.MAIN.STRINGS.TOTP); + if (Settings.IMP.MAIN.STRINGS.TOTP_TITLE.isEmpty() && Settings.IMP.MAIN.STRINGS.TOTP_SUBTITLE.isEmpty()) { + totpTitle = null; + } else { + totpTitle = Title.title( + serializer.deserialize(Settings.IMP.MAIN.STRINGS.TOTP_TITLE), + serializer.deserialize(Settings.IMP.MAIN.STRINGS.TOTP_SUBTITLE), + Settings.IMP.MAIN.CRACKED_TITLE_SETTINGS.toTimes() + ); + } + register = serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER); + if (Settings.IMP.MAIN.STRINGS.REGISTER_TITLE.isEmpty() && Settings.IMP.MAIN.STRINGS.REGISTER_SUBTITLE.isEmpty()) { + registerTitle = null; + } else { + registerTitle = Title.title( + serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_TITLE), + serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_SUBTITLE), + Settings.IMP.MAIN.CRACKED_TITLE_SETTINGS.toTimes() + ); + } + login = new Component[loginAttempts]; + for (int i = 0; i < loginAttempts; ++i) { + login[i] = serializer.deserialize(MessageFormat.format(Settings.IMP.MAIN.STRINGS.LOGIN, i + 1)); + } + if (Settings.IMP.MAIN.STRINGS.LOGIN_TITLE.isEmpty() && Settings.IMP.MAIN.STRINGS.LOGIN_SUBTITLE.isEmpty()) { + loginTitle = null; + } else { + loginTitle = Title.title( + serializer.deserialize(MessageFormat.format(Settings.IMP.MAIN.STRINGS.LOGIN_TITLE, loginAttempts)), + serializer.deserialize(MessageFormat.format(Settings.IMP.MAIN.STRINGS.LOGIN_SUBTITLE, loginAttempts)), + Settings.IMP.MAIN.CRACKED_TITLE_SETTINGS.toTimes() + ); + } + registerDifferentPasswords = serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_DIFFERENT_PASSWORDS); + registerPasswordTooLong = serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_PASSWORD_TOO_LONG); + registerPasswordTooShort = serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_PASSWORD_TOO_SHORT); + registerPasswordUnsafe = serializer.deserialize(Settings.IMP.MAIN.STRINGS.REGISTER_PASSWORD_UNSAFE); + loginSuccessful = serializer.deserialize(Settings.IMP.MAIN.STRINGS.LOGIN_SUCCESSFUL); + sessionExpired = serializer.deserialize(Settings.IMP.MAIN.STRINGS.MOD_SESSION_EXPIRED); + if (Settings.IMP.MAIN.STRINGS.LOGIN_SUCCESSFUL_TITLE.isEmpty() && Settings.IMP.MAIN.STRINGS.LOGIN_SUCCESSFUL_SUBTITLE.isEmpty()) { + loginSuccessfulTitle = null; + } else { + loginSuccessfulTitle = Title.title( + serializer.deserialize(Settings.IMP.MAIN.STRINGS.LOGIN_SUCCESSFUL_TITLE), + serializer.deserialize(Settings.IMP.MAIN.STRINGS.LOGIN_SUCCESSFUL_SUBTITLE), + Settings.IMP.MAIN.CRACKED_TITLE_SETTINGS.toTimes() + ); + } + + migrationHash = Settings.IMP.MAIN.MIGRATION_HASH; } - migrationHash = Settings.IMP.MAIN.MIGRATION_HASH; - } + public static boolean checkPassword(String password, RegisteredPlayer player, RegisteredPlayerRepository repository) { + String hash = player.getHash(); + boolean isCorrect = HASH_VERIFIER.verify( + password.getBytes(StandardCharsets.UTF_8), + hash.replace("BCRYPT$", "$2a$").getBytes(StandardCharsets.UTF_8) + ).verified; + + if (!isCorrect && migrationHash != null) { + isCorrect = migrationHash.checkPassword(hash, password); + if (isCorrect) { + player.setPassword(password); + try { + repository.update(player); + } catch (DataAccessException e) { + throw new DataAccessRuntimeException(e); + } + } + } - public static boolean checkPassword(String password, RegisteredPlayer player, Dao playerDao) { - String hash = player.getHash(); - boolean isCorrect = HASH_VERIFIER.verify( - password.getBytes(StandardCharsets.UTF_8), - hash.replace("BCRYPT$", "$2a$").getBytes(StandardCharsets.UTF_8) - ).verified; + return isCorrect; + } - if (!isCorrect && migrationHash != null) { - isCorrect = migrationHash.checkPassword(hash, password); - if (isCorrect) { - player.setPassword(password); + public static RegisteredPlayer fetchInfo(RegisteredPlayerRepository playerDao, UUID uuid) { try { - playerDao.update(player); - } catch (SQLException e) { - throw new SQLRuntimeException(e); + List playerList = playerDao.getByPremiumUUID(uuid.toString()); + return (playerList != null ? playerList.size() : 0) == 0 ? null : playerList.get(0); + } catch (DataAccessException e) { + throw new DataAccessRuntimeException(e); } - } } - return isCorrect; - } + public static RegisteredPlayer fetchInfo(RegisteredPlayerRepository repository, String nickname) { + try { + return repository.getByLowercaseName(nickname.toLowerCase(Locale.ROOT)).orElse(null); + } catch (DataAccessException e) { + throw new DataAccessRuntimeException(e); + } + } - public static RegisteredPlayer fetchInfo(Dao playerDao, UUID uuid) { - try { - List playerList = playerDao.queryForEq(RegisteredPlayer.PREMIUM_UUID_FIELD, uuid.toString()); - return (playerList != null ? playerList.size() : 0) == 0 ? null : playerList.get(0); - } catch (SQLException e) { - throw new SQLRuntimeException(e); + public static RegisteredPlayer fetchInfoLowercased(RegisteredPlayerRepository repository, String nickname) { + try { + return repository.getByLowercaseName(nickname).orElse(null); + } catch (DataAccessException e) { + throw new DataAccessRuntimeException(e); + } } - } - - public static RegisteredPlayer fetchInfo(Dao playerDao, String nickname) { - return AuthSessionHandler.fetchInfoLowercased(playerDao, nickname.toLowerCase(Locale.ROOT)); - } - - public static RegisteredPlayer fetchInfoLowercased(Dao playerDao, String nickname) { - try { - List playerList = playerDao.queryForEq(RegisteredPlayer.LOWERCASE_NICKNAME_FIELD, nickname); - return (playerList != null ? playerList.size() : 0) == 0 ? null : playerList.get(0); - } catch (SQLException e) { - throw new SQLRuntimeException(e); + + /** + * Use {@link RegisteredPlayer#genHash(String)} or {@link RegisteredPlayer#setPassword} + */ + @Deprecated() + public static String genHash(String password) { + return HASHER.hashToString(Settings.IMP.MAIN.BCRYPT_COST, password.toCharArray()); } - } - - /** - * Use {@link RegisteredPlayer#genHash(String)} or {@link RegisteredPlayer#setPassword} - */ - @Deprecated() - public static String genHash(String password) { - return HASHER.hashToString(Settings.IMP.MAIN.BCRYPT_COST, password.toCharArray()); - } - - - private enum Command { - - INVALID, - REGISTER, - LOGIN, - TOTP; - - static Command parse(String command) { - if (Settings.IMP.MAIN.REGISTER_COMMAND.contains(command)) { - return Command.REGISTER; - } else if (Settings.IMP.MAIN.LOGIN_COMMAND.contains(command)) { - return Command.LOGIN; - } else if (Settings.IMP.MAIN.TOTP_COMMAND.contains(command)) { - return Command.TOTP; - } else { - return Command.INVALID; - } + + + private enum Command { + + INVALID, + REGISTER, + LOGIN, + TOTP; + + static Command parse(String command) { + if (Settings.IMP.MAIN.REGISTER_COMMAND.contains(command)) { + return Command.REGISTER; + } else if (Settings.IMP.MAIN.LOGIN_COMMAND.contains(command)) { + return Command.LOGIN; + } else if (Settings.IMP.MAIN.TOTP_COMMAND.contains(command)) { + return Command.TOTP; + } else { + return Command.INVALID; + } + } } - } } diff --git a/src/main/java/net/elytrium/limboauth/listener/AuthListener.java b/src/main/java/net/elytrium/limboauth/listener/AuthListener.java index d31cdb89..918d5cac 100644 --- a/src/main/java/net/elytrium/limboauth/listener/AuthListener.java +++ b/src/main/java/net/elytrium/limboauth/listener/AuthListener.java @@ -17,8 +17,6 @@ package net.elytrium.limboauth.listener; -import com.j256.ormlite.dao.Dao; -import com.j256.ormlite.stmt.UpdateBuilder; import com.velocitypowered.api.event.PostOrder; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.connection.PostLoginEvent; @@ -30,12 +28,6 @@ import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.client.InitialInboundConnection; import com.velocitypowered.proxy.connection.client.LoginInboundConnection; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.sql.SQLException; -import java.util.Locale; -import java.util.UUID; -import java.util.concurrent.TimeUnit; import net.elytrium.commons.utils.reflection.ReflectionException; import net.elytrium.limboapi.api.event.LoginLimboRegisterEvent; import net.elytrium.limboauth.LimboAuth; @@ -44,10 +36,18 @@ import net.elytrium.limboauth.Settings; import net.elytrium.limboauth.floodgate.FloodgateApiHolder; import net.elytrium.limboauth.handler.AuthSessionHandler; +import net.elytrium.limboauth.model.DataAccessRuntimeException; import net.elytrium.limboauth.model.RegisteredPlayer; -import net.elytrium.limboauth.model.SQLRuntimeException; +import net.elytrium.limboauth.repository.RegisteredPlayerRepository; +import net.elytrium.limboauth.repository.exception.DataAccessException; import net.kyori.adventure.text.Component; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.util.Locale; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + // TODO: Customizable events priority public class AuthListener { @@ -55,13 +55,13 @@ public class AuthListener { //private static final MethodHandle LOGIN_FIELD; private final LimboAuth plugin; - private final Dao playerDao; + private final RegisteredPlayerRepository playerRepository; private final FloodgateApiHolder floodgateApi; private final Component errorOccurred; - public AuthListener(LimboAuth plugin, Dao playerDao, FloodgateApiHolder floodgateApi) { + public AuthListener(LimboAuth plugin, RegisteredPlayerRepository playerRepository, FloodgateApiHolder floodgateApi) { this.plugin = plugin; - this.playerDao = playerDao; + this.playerRepository = playerRepository; this.floodgateApi = floodgateApi; this.errorOccurred = LimboAuth.getSerializer().deserialize(Settings.IMP.MAIN.STRINGS.ERROR_OCCURRED); @@ -185,13 +185,13 @@ public void onLoginLimboRegister(LoginLimboRegisterEvent event) { @Subscribe(order = PostOrder.FIRST) public void onGameProfileRequest(GameProfileRequestEvent event) { if (Settings.IMP.MAIN.SAVE_UUID && (this.floodgateApi == null || !this.floodgateApi.isFloodgatePlayer(event.getOriginalProfile().getId()))) { - RegisteredPlayer registeredPlayer = AuthSessionHandler.fetchInfo(this.playerDao, event.getOriginalProfile().getId()); + RegisteredPlayer registeredPlayer = AuthSessionHandler.fetchInfo(this.playerRepository, event.getOriginalProfile().getId()); if (registeredPlayer != null && !registeredPlayer.getUuid().isEmpty()) { event.setGameProfile(event.getOriginalProfile().withId(UUID.fromString(registeredPlayer.getUuid()))); return; } - registeredPlayer = AuthSessionHandler.fetchInfo(this.playerDao, event.getUsername()); + registeredPlayer = AuthSessionHandler.fetchInfo(this.playerRepository, event.getUsername()); if (registeredPlayer != null) { String currentUuid = registeredPlayer.getUuid(); @@ -199,9 +199,9 @@ public void onGameProfileRequest(GameProfileRequestEvent event) { if (currentUuid.isEmpty()) { try { registeredPlayer.setUuid(event.getGameProfile().getId().toString()); - this.playerDao.update(registeredPlayer); - } catch (SQLException e) { - throw new SQLRuntimeException(e); + this.playerRepository.update(registeredPlayer); + } catch (DataAccessException e) { + throw new DataAccessRuntimeException(e); } } else { event.setGameProfile(event.getOriginalProfile().withId(UUID.fromString(currentUuid))); @@ -209,12 +209,9 @@ public void onGameProfileRequest(GameProfileRequestEvent event) { } } else if (event.isOnlineMode()) { try { - UpdateBuilder updateBuilder = this.playerDao.updateBuilder(); - updateBuilder.where().eq(RegisteredPlayer.LOWERCASE_NICKNAME_FIELD, event.getUsername().toLowerCase(Locale.ROOT)); - updateBuilder.updateColumnValue(RegisteredPlayer.HASH_FIELD, ""); - updateBuilder.update(); - } catch (SQLException e) { - throw new SQLRuntimeException(e); + this.playerRepository.updateHash(event.getUsername().toLowerCase(Locale.ROOT), ""); + } catch (DataAccessException e) { + throw new DataAccessRuntimeException(e); } } diff --git a/src/main/java/net/elytrium/limboauth/model/SQLRuntimeException.java b/src/main/java/net/elytrium/limboauth/model/DataAccessRuntimeException.java similarity index 79% rename from src/main/java/net/elytrium/limboauth/model/SQLRuntimeException.java rename to src/main/java/net/elytrium/limboauth/model/DataAccessRuntimeException.java index 5f2b4f6a..a90126b4 100644 --- a/src/main/java/net/elytrium/limboauth/model/SQLRuntimeException.java +++ b/src/main/java/net/elytrium/limboauth/model/DataAccessRuntimeException.java @@ -17,13 +17,13 @@ package net.elytrium.limboauth.model; -public class SQLRuntimeException extends RuntimeException { +public class DataAccessRuntimeException extends RuntimeException { - public SQLRuntimeException(Throwable cause) { - this("An unexpected internal error was caught during the database SQL operations.", cause); + public DataAccessRuntimeException(Throwable cause) { + this("An unexpected internal error was caught during the data access operation.", cause); } - public SQLRuntimeException(String message, Throwable cause) { + public DataAccessRuntimeException(String message, Throwable cause) { super(message, cause); } } diff --git a/src/main/java/net/elytrium/limboauth/model/RegisteredPlayer.java b/src/main/java/net/elytrium/limboauth/model/RegisteredPlayer.java index 7bfea59e..e9a44a01 100644 --- a/src/main/java/net/elytrium/limboauth/model/RegisteredPlayer.java +++ b/src/main/java/net/elytrium/limboauth/model/RegisteredPlayer.java @@ -18,219 +18,149 @@ package net.elytrium.limboauth.model; import at.favre.lib.crypto.bcrypt.BCrypt; -import com.j256.ormlite.field.DatabaseField; -import com.j256.ormlite.table.DatabaseTable; import com.velocitypowered.api.proxy.Player; +import lombok.*; +import net.elytrium.limboauth.Settings; + import java.net.InetSocketAddress; import java.util.Locale; import java.util.UUID; -import net.elytrium.limboauth.Settings; -@DatabaseTable(tableName = "AUTH") +@Getter +@Setter +@Builder +@AllArgsConstructor(access = AccessLevel.PROTECTED) public class RegisteredPlayer { - public static final String NICKNAME_FIELD = "NICKNAME"; - public static final String LOWERCASE_NICKNAME_FIELD = "LOWERCASENICKNAME"; - public static final String HASH_FIELD = "HASH"; - public static final String IP_FIELD = "IP"; - public static final String LOGIN_IP_FIELD = "LOGINIP"; - public static final String TOTP_TOKEN_FIELD = "TOTPTOKEN"; - public static final String REG_DATE_FIELD = "REGDATE"; - public static final String LOGIN_DATE_FIELD = "LOGINDATE"; - public static final String UUID_FIELD = "UUID"; - public static final String PREMIUM_UUID_FIELD = "PREMIUMUUID"; - public static final String TOKEN_ISSUED_AT_FIELD = "ISSUEDTIME"; - - private static final BCrypt.Hasher HASHER = BCrypt.withDefaults(); - - @DatabaseField(canBeNull = false, columnName = NICKNAME_FIELD) - private String nickname; - - @DatabaseField(id = true, columnName = LOWERCASE_NICKNAME_FIELD) - private String lowercaseNickname; - - @DatabaseField(canBeNull = false, columnName = HASH_FIELD) - private String hash = ""; - - @DatabaseField(columnName = IP_FIELD, index = true) - private String ip; - - @DatabaseField(columnName = TOTP_TOKEN_FIELD) - private String totpToken = ""; - - @DatabaseField(columnName = REG_DATE_FIELD) - private Long regDate = System.currentTimeMillis(); - - @DatabaseField(columnName = UUID_FIELD) - private String uuid = ""; - - @DatabaseField(columnName = RegisteredPlayer.PREMIUM_UUID_FIELD, index = true) - private String premiumUuid = ""; - - @DatabaseField(columnName = LOGIN_IP_FIELD) - private String loginIp; - - @DatabaseField(columnName = LOGIN_DATE_FIELD) - private Long loginDate = System.currentTimeMillis(); - - @DatabaseField(columnName = TOKEN_ISSUED_AT_FIELD) - private Long tokenIssuedAt = System.currentTimeMillis(); - - @Deprecated - public RegisteredPlayer(String nickname, String lowercaseNickname, - String hash, String ip, String totpToken, Long regDate, String uuid, String premiumUuid, String loginIp, Long loginDate) { - this.nickname = nickname; - this.lowercaseNickname = lowercaseNickname; - this.hash = hash; - this.ip = ip; - this.totpToken = totpToken; - this.regDate = regDate; - this.uuid = uuid; - this.premiumUuid = premiumUuid; - this.loginIp = loginIp; - this.loginDate = loginDate; - } - - public RegisteredPlayer(Player player) { - this(player.getUsername(), player.getUniqueId(), player.getRemoteAddress()); - } - - public RegisteredPlayer(String nickname, UUID uuid, InetSocketAddress ip) { - this(nickname, uuid.toString(), ip.getAddress().getHostAddress()); - } - - public RegisteredPlayer(String nickname, String uuid, String ip) { - this.nickname = nickname; - this.lowercaseNickname = nickname.toLowerCase(Locale.ROOT); - this.uuid = uuid; - this.ip = ip; - this.loginIp = ip; - } - - public RegisteredPlayer() { - - } - - public static String genHash(String password) { - return HASHER.hashToString(Settings.IMP.MAIN.BCRYPT_COST, password.toCharArray()); - } - - public RegisteredPlayer setNickname(String nickname) { - this.nickname = nickname; - this.lowercaseNickname = nickname.toLowerCase(Locale.ROOT); - - return this; - } + private static final BCrypt.Hasher HASHER = BCrypt.withDefaults(); - public String getNickname() { - return this.nickname == null ? this.lowercaseNickname : this.nickname; - } + private String nickname; - public String getLowercaseNickname() { - return this.lowercaseNickname; - } + private String lowercaseNickname; - public RegisteredPlayer setPassword(String password) { - this.hash = genHash(password); - this.tokenIssuedAt = System.currentTimeMillis(); + private String hash = ""; - return this; - } + private String ip; - public RegisteredPlayer setHash(String hash) { - this.hash = hash; - this.tokenIssuedAt = System.currentTimeMillis(); + private String totpToken = ""; - return this; - } + private Long regDate = System.currentTimeMillis(); - public String getHash() { - return this.hash == null ? "" : this.hash; - } + private String uuid = ""; - public RegisteredPlayer setIP(String ip) { - this.ip = ip; + private String premiumUuid = ""; - return this; - } + private String loginIp; - public String getIP() { - return this.ip == null ? "" : this.ip; - } + private Long loginDate = System.currentTimeMillis(); - public RegisteredPlayer setTotpToken(String totpToken) { - this.totpToken = totpToken; + private Long tokenIssuedAt = System.currentTimeMillis(); - return this; - } + @Deprecated + public RegisteredPlayer(String nickname, String lowercaseNickname, + String hash, String ip, String totpToken, Long regDate, String uuid, String premiumUuid, String loginIp, Long loginDate) { + this.nickname = nickname; + this.lowercaseNickname = lowercaseNickname; + this.hash = hash; + this.ip = ip; + this.totpToken = totpToken; + this.regDate = regDate; + this.uuid = uuid; + this.premiumUuid = premiumUuid; + this.loginIp = loginIp; + this.loginDate = loginDate; + } - public String getTotpToken() { - return this.totpToken == null ? "" : this.totpToken; - } + public RegisteredPlayer(Player player) { + this(player.getUsername(), player.getUniqueId(), player.getRemoteAddress()); + } - public RegisteredPlayer setRegDate(Long regDate) { - this.regDate = regDate; + public RegisteredPlayer(String nickname, UUID uuid, InetSocketAddress ip) { + this(nickname, uuid.toString(), ip.getAddress().getHostAddress()); + } - return this; - } + public RegisteredPlayer(String nickname, String uuid, String ip) { + this.nickname = nickname; + this.lowercaseNickname = nickname.toLowerCase(Locale.ROOT); + this.uuid = uuid; + this.ip = ip; + this.loginIp = ip; + } - public long getRegDate() { - return this.regDate == null ? Long.MIN_VALUE : this.regDate; - } - public RegisteredPlayer setUuid(String uuid) { - this.uuid = uuid; + public static String genHash(String password) { + return HASHER.hashToString(Settings.IMP.MAIN.BCRYPT_COST, password.toCharArray()); + } - return this; - } + public RegisteredPlayer setNickname(String nickname) { + this.nickname = nickname; + this.lowercaseNickname = nickname.toLowerCase(Locale.ROOT); - public String getUuid() { - return this.uuid == null ? "" : this.uuid; - } + return this; + } - public RegisteredPlayer setPremiumUuid(String premiumUuid) { - this.premiumUuid = premiumUuid; + public RegisteredPlayer setPremiumUuid(UUID premiumUuid) { + this.premiumUuid = premiumUuid.toString(); + return this; + } - return this; - } + public RegisteredPlayer setPremiumUuid(String premiumUuid) { + this.premiumUuid = premiumUuid; + return this; + } - public RegisteredPlayer setPremiumUuid(UUID premiumUuid) { - this.premiumUuid = premiumUuid.toString(); + public String getNickname() { + return this.nickname == null ? this.lowercaseNickname : this.nickname; + } - return this; - } + public RegisteredPlayer setPassword(String password) { + this.hash = genHash(password); + this.tokenIssuedAt = System.currentTimeMillis(); - public String getPremiumUuid() { - return this.premiumUuid == null ? "" : this.premiumUuid; - } + return this; + } - public String getLoginIp() { - return this.loginIp == null ? "" : this.loginIp; - } + public RegisteredPlayer setHash(String hash) { + this.hash = hash; + this.tokenIssuedAt = System.currentTimeMillis(); - public RegisteredPlayer setLoginIp(String loginIp) { - this.loginIp = loginIp; + return this; + } - return this; - } + public String getHash() { + return this.hash == null ? "" : this.hash; + } - public long getLoginDate() { - return this.loginDate == null ? Long.MIN_VALUE : this.loginDate; - } + public String getIP() { + return this.ip == null ? "" : this.ip; + } - public RegisteredPlayer setLoginDate(Long loginDate) { - this.loginDate = loginDate; + public String getTotpToken() { + return this.totpToken == null ? "" : this.totpToken; + } - return this; - } + public long getRegDate() { + return this.regDate == null ? Long.MIN_VALUE : this.regDate; + } - public long getTokenIssuedAt() { - return this.tokenIssuedAt == null ? Long.MIN_VALUE : this.tokenIssuedAt; - } + public String getUuid() { + return this.uuid == null ? "" : this.uuid; + } - public RegisteredPlayer setTokenIssuedAt(Long tokenIssuedAt) { - this.tokenIssuedAt = tokenIssuedAt; + public String getPremiumUuid() { + return this.premiumUuid == null ? "" : this.premiumUuid; + } + + public String getLoginIp() { + return this.loginIp == null ? "" : this.loginIp; + } + + public long getLoginDate() { + return this.loginDate == null ? Long.MIN_VALUE : this.loginDate; + } + + public long getTokenIssuedAt() { + return this.tokenIssuedAt == null ? Long.MIN_VALUE : this.tokenIssuedAt; + } - return this; - } } diff --git a/src/main/java/net/elytrium/limboauth/repository/RegisteredPlayerRepository.java b/src/main/java/net/elytrium/limboauth/repository/RegisteredPlayerRepository.java new file mode 100644 index 00000000..85e971ee --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/repository/RegisteredPlayerRepository.java @@ -0,0 +1,52 @@ +package net.elytrium.limboauth.repository; + +import net.elytrium.limboauth.model.RegisteredPlayer; +import net.elytrium.limboauth.repository.exception.DataAccessException; + +import java.util.List; +import java.util.Optional; + +public interface RegisteredPlayerRepository { + + void deleteByLowercaseName(String name) throws DataAccessException; + + Optional getByLowercaseName(String name) throws DataAccessException; + + List getByIp(String ip) throws DataAccessException; + + List getByPremiumUUID(String uuid) throws DataAccessException; + + void createIfNotExists(RegisteredPlayer player) throws DataAccessException; + + void update(RegisteredPlayer player) throws DataAccessException; + + void updateHash(String lowercaseName, String hash) throws DataAccessException; + + void updateTotpToken(String lowercaseName, String token) throws DataAccessException; + + void updateLogin(String lowercase, String loginIp, Long loginDate) throws DataAccessException; + + /** + * + QueryBuilder premiumCountQuery = this.playerRepository.queryBuilder(); + premiumCountQuery.where() + .eq(RegisteredPlayer.PREMIUM_UUID_FIELD, uuid.toString()) + .and() + .eq(RegisteredPlayer.HASH_FIELD, ""); + premiumCountQuery.setCountOf(true); + * */ + boolean isHashEmptyByPremiumUuid(String uuid) throws DataAccessException; + + /** + * QueryBuilder crackedCountQuery = this.playerRepository.queryBuilder(); + * crackedCountQuery.where() + * .eq(RegisteredPlayer.LOWERCASE_NICKNAME_FIELD, nickname) + * .and() + * .ne(RegisteredPlayer.HASH_FIELD, ""); + * crackedCountQuery.setCountOf(true); + * */ + boolean isHashEmptyByLowercaseName(String name) throws DataAccessException; + + int registeredPlayerCount(); + +} diff --git a/src/main/java/net/elytrium/limboauth/repository/exception/DataAccessException.java b/src/main/java/net/elytrium/limboauth/repository/exception/DataAccessException.java new file mode 100644 index 00000000..b8179b7b --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/repository/exception/DataAccessException.java @@ -0,0 +1,12 @@ +package net.elytrium.limboauth.repository.exception; + +public class DataAccessException extends Exception { + + public DataAccessException(Throwable cause) { + this("Error was caught during the data access operation.", cause); + } + + public DataAccessException(String message, Throwable cause) { + super(message, cause); + } +} From c4fa7b6229a2dc403cf1eb1edbc0628baff880cc Mon Sep 17 00:00:00 2001 From: Dmitri Ponomarev Date: Sun, 8 Dec 2024 17:32:37 +0700 Subject: [PATCH 2/7] Fix statements --- .../elytrium/limboauth/data/DataProvider.java | 3 --- .../HikariRegisteredPlayerRepository.java | 26 +++++++++---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/main/java/net/elytrium/limboauth/data/DataProvider.java b/src/main/java/net/elytrium/limboauth/data/DataProvider.java index 0f70c068..8cc9ea7c 100644 --- a/src/main/java/net/elytrium/limboauth/data/DataProvider.java +++ b/src/main/java/net/elytrium/limboauth/data/DataProvider.java @@ -5,10 +5,7 @@ import net.elytrium.limboauth.dependencies.hikary.HikariRegisteredPlayerRepository; import net.elytrium.limboauth.repository.RegisteredPlayerRepository; -import java.lang.reflect.InvocationTargetException; -import java.net.MalformedURLException; import java.nio.file.Path; -import java.sql.SQLException; @Getter public enum DataProvider { diff --git a/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java b/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java index 84ccd6cc..7247d9ec 100644 --- a/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java +++ b/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java @@ -54,7 +54,7 @@ public HikariRegisteredPlayerRepository( public void updateSchema(DataProvider dataProvider) throws SQLException { try (Connection con = getConnection()) { - CallableStatement createStatement = con.prepareCall(""" + PreparedStatement createStatement = con.prepareStatement(""" CREATE TABLE IF NOT EXISTS AUTH ( NICKNAME varchar(255), LOWERCASENICKNAME varchar(255) PRIMARY KEY, @@ -108,7 +108,7 @@ public Optional getByLowercaseName(String name) throws DataAcc st.setString(1, name); st.execute(); ResultSet resultSet = st.getResultSet(); - if (resultSet.next()) { + if (resultSet != null && resultSet.next()) { return Optional.of(parseRegisteredPlayer(resultSet)); } else { return Optional.empty(); @@ -143,7 +143,7 @@ public List getByIp(String ip) throws DataAccessException { st.execute(); ResultSet resultSet = st.getResultSet(); List players = new ArrayList<>(); - while (resultSet.next()) { + while (resultSet != null && resultSet.next()) { players.add(parseRegisteredPlayer(resultSet)); } return Collections.unmodifiableList(players); @@ -177,7 +177,7 @@ public List getByPremiumUUID(String uuid) throws DataAccessExc st.execute(); ResultSet resultSet = st.getResultSet(); List players = new ArrayList<>(); - while (resultSet.next()) { + while (resultSet != null && resultSet.next()) { players.add(parseRegisteredPlayer(resultSet)); } return Collections.unmodifiableList(players); @@ -210,7 +210,7 @@ public void createIfNotExists(RegisteredPlayer player) throws DataAccessExceptio ); selectStatement.setString(1, player.getLowercaseNickname()); ResultSet resultSet = selectStatement.getResultSet(); - if (resultSet.next()) return; + if (resultSet != null && resultSet.next()) return; PreparedStatement insertStatement = con.prepareStatement( """ INSERT INTO AUTH @@ -287,7 +287,7 @@ public void update(RegisteredPlayer player) throws DataAccessException { @Override public void updateHash(String lowercaseName, String hash) throws DataAccessException { try (Connection con = getConnection()) { - PreparedStatement st = con.prepareCall("UPDATE AUTH SET HASH = ? WHERE LOWERCASENICKNAME = ?"); + PreparedStatement st = con.prepareStatement("UPDATE AUTH SET HASH = ? WHERE LOWERCASENICKNAME = ?"); st.setString(1, hash); st.setString(2, lowercaseName); st.executeUpdate(); @@ -311,7 +311,7 @@ public void updateTotpToken(String lowercaseName, String totpToken) throws DataA @Override public void updateLogin(String lowercase, String loginIp, Long loginDate) throws DataAccessException { try (Connection con = getConnection()) { - PreparedStatement st = con.prepareCall("UPDATE AUTH SET LOGINIP = ? AND LOGINDATE = ? WHERE LOWERCASENICKNAME = ?"); + PreparedStatement st = con.prepareStatement("UPDATE AUTH SET LOGINIP = ? AND LOGINDATE = ? WHERE LOWERCASENICKNAME = ?"); st.setString(1, loginIp); st.setLong(1, loginDate); st.setString(3, lowercase); @@ -324,10 +324,10 @@ public void updateLogin(String lowercase, String loginIp, Long loginDate) throws @Override public boolean isHashEmptyByPremiumUuid(String uuid) throws DataAccessException { try (Connection con = getConnection()) { - PreparedStatement st = con.prepareCall("SELECT HASH FROM AUTH WHERE PREMIUMUUID = ?"); + PreparedStatement st = con.prepareStatement("SELECT HASH FROM AUTH WHERE PREMIUMUUID = ?"); st.setString(1, uuid); ResultSet resultSet = st.getResultSet(); - return resultSet.next() && "".equals(resultSet.getString("HASH")); + return resultSet != null && resultSet.next() && "".equals(resultSet.getString("HASH")); } catch (SQLException e) { throw new DataAccessException(e); } @@ -336,10 +336,10 @@ public boolean isHashEmptyByPremiumUuid(String uuid) throws DataAccessException @Override public boolean isHashEmptyByLowercaseName(String name) throws DataAccessException { try (Connection con = getConnection()) { - PreparedStatement st = con.prepareCall("SELECT HASH FROM AUTH WHERE LOWERCASENICKNAME = ?"); + PreparedStatement st = con.prepareStatement("SELECT HASH FROM AUTH WHERE LOWERCASENICKNAME = ?"); st.setString(1, name); ResultSet resultSet = st.getResultSet(); - return resultSet.next() && "".equals(resultSet.getString("HASH")); + return resultSet != null && resultSet.next() && "".equals(resultSet.getString("HASH")); } catch (SQLException e) { throw new DataAccessException(e); } @@ -347,9 +347,9 @@ public boolean isHashEmptyByLowercaseName(String name) throws DataAccessExceptio public int registeredPlayerCount() { try (Connection con = getConnection()) { - CallableStatement st = con.prepareCall("SELECT COUNT(*) FROM AUTH"); + PreparedStatement st = con.prepareStatement("SELECT COUNT(*) FROM AUTH"); ResultSet set = st.getResultSet(); - if (set.next()) { + if (set != null && set.next()) { return set.getInt(1); } else { return 0; From 3e528fb4ff352e5800cfb98ea0a1657e3b689251 Mon Sep 17 00:00:00 2001 From: Dmitri Ponomarev Date: Sun, 8 Dec 2024 19:44:27 +0700 Subject: [PATCH 3/7] Critical fix --- build.gradle | 2 +- .../dependencies/hikary/HikariRegisteredPlayerRepository.java | 4 ++-- .../net/elytrium/limboauth/handler/AuthSessionHandler.java | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 71b100b1..e0b6fe23 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ plugins { } setGroup("net.elytrium") -setVersion("1.2.0-SNAPSHOT-1") +setVersion("1.2.0-SNAPSHOT-2") java { setSourceCompatibility(JavaVersion.VERSION_17) diff --git a/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java b/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java index 7247d9ec..f9311bcb 100644 --- a/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java +++ b/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java @@ -311,9 +311,9 @@ public void updateTotpToken(String lowercaseName, String totpToken) throws DataA @Override public void updateLogin(String lowercase, String loginIp, Long loginDate) throws DataAccessException { try (Connection con = getConnection()) { - PreparedStatement st = con.prepareStatement("UPDATE AUTH SET LOGINIP = ? AND LOGINDATE = ? WHERE LOWERCASENICKNAME = ?"); + PreparedStatement st = con.prepareStatement("UPDATE AUTH SET LOGINIP = ?, LOGINDATE = ? WHERE LOWERCASENICKNAME = ?"); st.setString(1, loginIp); - st.setLong(1, loginDate); + st.setLong(2, loginDate); st.setString(3, lowercase); st.executeUpdate(); } catch (SQLException e) { diff --git a/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java b/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java index b577f37c..68787648 100644 --- a/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java +++ b/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java @@ -47,7 +47,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import java.nio.charset.StandardCharsets; -import java.sql.SQLException; import java.text.MessageFormat; import java.util.List; import java.util.Locale; From f78d8925168dbb59b3bf2e933b4f5ab053c192fd Mon Sep 17 00:00:00 2001 From: Dmitri Ponomarev Date: Thu, 12 Dec 2024 23:25:49 +0700 Subject: [PATCH 4/7] Critical update fix --- build.gradle | 2 +- .../HikariRegisteredPlayerRepository.java | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index e0b6fe23..cc7a5b86 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ plugins { } setGroup("net.elytrium") -setVersion("1.2.0-SNAPSHOT-2") +setVersion("1.2.0-SNAPSHOT-3") java { setSourceCompatibility(JavaVersion.VERSION_17) diff --git a/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java b/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java index f9311bcb..920c2d2f 100644 --- a/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java +++ b/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java @@ -253,16 +253,16 @@ public void update(RegisteredPlayer player) throws DataAccessException { PreparedStatement updateStatement = con.prepareStatement( """ UPDATE AUTH - SET NICKNAME = ? and - HASH = ? and - IP = ? and - LOGINIP = ? and - TOTPTOKEN = ? and - REGDATE = ? and - LOGINDATE = ? and - UUID = ? and - PREMIUMUUID = ? and - ISSUEDTIME = ? and + SET NICKNAME = ?, + HASH = ?, + IP = ?, + LOGINIP = ?, + TOTPTOKEN = ?, + REGDATE = ?, + LOGINDATE = ?, + UUID = ?, + PREMIUMUUID = ?, + ISSUEDTIME = ? WHERE LOWERCASENICKNAME = ? """ ); From f529445aa2b403a9de6ae403963604fa29783ca2 Mon Sep 17 00:00:00 2001 From: Dmitri Ponomarev Date: Thu, 12 Dec 2024 23:35:48 +0700 Subject: [PATCH 5/7] Update version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cc7a5b86..d41b367b 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ plugins { } setGroup("net.elytrium") -setVersion("1.2.0-SNAPSHOT-3") +setVersion("1.2.0-SNAPSHOT-4") java { setSourceCompatibility(JavaVersion.VERSION_17) From 2936cef6d5afc78618d4aca5f50b4c0cd9adeb90 Mon Sep 17 00:00:00 2001 From: Dmitri Ponomarev Date: Mon, 13 Apr 2026 22:15:38 +0300 Subject: [PATCH 6/7] Add H2 and SQLITE database implementations --- build.gradle | 4 - .../net/elytrium/limboauth/LimboAuth.java | 3 + .../elytrium/limboauth/data/DataProvider.java | 11 +- .../dependencies/DatabaseLibrary.java | 184 ------------- .../HikariRegisteredPlayerRepository.java | 71 +++-- .../limboauth/handler/AuthSessionHandler.java | 2 +- .../limboauth/model/RegisteredPlayer.java | 256 ++++++++++-------- .../RegisteredPlayerRepository.java | 20 +- 8 files changed, 219 insertions(+), 332 deletions(-) delete mode 100644 src/main/java/net/elytrium/limboauth/dependencies/DatabaseLibrary.java diff --git a/build.gradle b/build.gradle index 450c3883..8465f580 100644 --- a/build.gradle +++ b/build.gradle @@ -49,9 +49,6 @@ dependencies { annotationProcessor("com.velocitypowered:velocity-api:$velocityVersion") compileOnly("com.velocitypowered:velocity-proxy:$velocityVersion") // From Elytrium Repo. - compileOnly 'org.projectlombok:lombok:1.18.36' - annotationProcessor 'org.projectlombok:lombok:1.18.36' - // Needs for some velocity methods. compileOnly("io.netty:netty-codec:$nettyVersion") compileOnly("io.netty:netty-handler:$nettyVersion") @@ -64,7 +61,6 @@ dependencies { implementation("de.mkammerer:argon2-jvm-nolibs:2.11") implementation("com.zaxxer:HikariCP:6.2.1") - implementation("io.whitfin:siphash:2.0.0") implementation("org.bstats:bstats-velocity:$bstatsVersion") diff --git a/src/main/java/net/elytrium/limboauth/LimboAuth.java b/src/main/java/net/elytrium/limboauth/LimboAuth.java index 3e57ce7a..40642c02 100644 --- a/src/main/java/net/elytrium/limboauth/LimboAuth.java +++ b/src/main/java/net/elytrium/limboauth/LimboAuth.java @@ -286,6 +286,9 @@ public void reload() { Settings.DATABASE dbConfig = Settings.IMP.DATABASE; DataProvider dataProvider = dbConfig.STORAGE_TYPE; try { + if (this.playerRepository != null) { + this.playerRepository.close(); + } this.playerRepository = dataProvider.createRegisteredPlayerRepository( this.dataDirectoryFile.toPath().toAbsolutePath(), dbConfig.HOSTNAME, diff --git a/src/main/java/net/elytrium/limboauth/data/DataProvider.java b/src/main/java/net/elytrium/limboauth/data/DataProvider.java index 8cc9ea7c..acf18318 100644 --- a/src/main/java/net/elytrium/limboauth/data/DataProvider.java +++ b/src/main/java/net/elytrium/limboauth/data/DataProvider.java @@ -1,17 +1,18 @@ package net.elytrium.limboauth.data; -import lombok.Getter; import net.elytrium.limboauth.dependencies.BaseLibrary; import net.elytrium.limboauth.dependencies.hikary.HikariRegisteredPlayerRepository; import net.elytrium.limboauth.repository.RegisteredPlayerRepository; import java.nio.file.Path; -@Getter public enum DataProvider { MYSQL(BaseLibrary.MYSQL), MARIADB(BaseLibrary.MARIADB), - POSTGRESQL(BaseLibrary.POSTGRESQL); + POSTGRESQL(BaseLibrary.POSTGRESQL), + H2_LEGACY(BaseLibrary.H2_V1), + H2(BaseLibrary.H2_V2), + SQLITE(BaseLibrary.SQLITE); private final BaseLibrary baseLibrary; @@ -28,6 +29,7 @@ public RegisteredPlayerRepository createRegisteredPlayerRepository( ) throws Exception { HikariRegisteredPlayerRepository repo = new HikariRegisteredPlayerRepository( this, + path, host, database, user, @@ -37,4 +39,7 @@ public RegisteredPlayerRepository createRegisteredPlayerRepository( return repo; } + public BaseLibrary getBaseLibrary() { + return baseLibrary; + } } diff --git a/src/main/java/net/elytrium/limboauth/dependencies/DatabaseLibrary.java b/src/main/java/net/elytrium/limboauth/dependencies/DatabaseLibrary.java deleted file mode 100644 index c80eb117..00000000 --- a/src/main/java/net/elytrium/limboauth/dependencies/DatabaseLibrary.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboauth.dependencies; - -import com.j256.ormlite.jdbc.JdbcPooledConnectionSource; -import com.j256.ormlite.jdbc.db.DatabaseTypeUtils; -import com.j256.ormlite.support.ConnectionSource; -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.sql.Connection; -import java.sql.Driver; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.Locale; -import java.util.Properties; - -public enum DatabaseLibrary { - H2_LEGACY_V1( - BaseLibrary.H2_V1, - (classLoader, dir, jdbc, user, password) -> fromDriver(classLoader.loadClass("org.h2.Driver"), jdbc, null, null, false), - (dir, hostname, database) -> "jdbc:h2:" + dir + "/limboauth" - ), - H2( - BaseLibrary.H2_V2, - (classLoader, dir, jdbc, user, password) -> { - Connection modernConnection = fromDriver(classLoader.loadClass("org.h2.Driver"), jdbc, null, null, true); - - Path legacyDatabase = dir.resolve("limboauth.mv.db"); - if (Files.exists(legacyDatabase)) { - Path dumpFile = dir.resolve("limboauth.dump.sql"); - try (Connection legacyConnection = H2_LEGACY_V1.connect(dir, null, null, user, password)) { - try (PreparedStatement migrateStatement = legacyConnection.prepareStatement("SCRIPT TO '?'")) { - migrateStatement.setString(1, dumpFile.toString()); - migrateStatement.execute(); - } - } - - try (PreparedStatement migrateStatement = modernConnection.prepareStatement("RUNSCRIPT FROM '?'")) { - migrateStatement.setString(1, dumpFile.toString()); - migrateStatement.execute(); - } - - Files.delete(dumpFile); - Files.move(legacyDatabase, dir.resolve("limboauth-v1-backup.mv.db")); - } - - return modernConnection; - }, - (dir, hostname, database) -> "jdbc:h2:" + dir + "/limboauth-v2" - ), - MYSQL( - BaseLibrary.MYSQL, - (classLoader, dir, jdbc, user, password) - -> fromDriver(classLoader.loadClass("com.mysql.cj.jdbc.NonRegisteringDriver"), jdbc, user, password, true), - (dir, hostname, database) -> - "jdbc:mysql://" + hostname + "/" + database - ), - MARIADB( - BaseLibrary.MARIADB, - (classLoader, dir, jdbc, user, password) - -> fromDriver(classLoader.loadClass("org.mariadb.jdbc.Driver"), jdbc, user, password, true), - (dir, hostname, database) -> - "jdbc:mariadb://" + hostname + "/" + database - ), - POSTGRESQL( - BaseLibrary.POSTGRESQL, - (classLoader, dir, jdbc, user, password) -> fromDriver(classLoader.loadClass("org.postgresql.Driver"), jdbc, user, password, true), - (dir, hostname, database) -> "jdbc:postgresql://" + hostname + "/" + database - ), - SQLITE( - BaseLibrary.SQLITE, - (classLoader, dir, jdbc, user, password) -> fromDriver(classLoader.loadClass("org.sqlite.JDBC"), jdbc, user, password, true), - (dir, hostname, database) -> "jdbc:sqlite:" + dir + "/limboauth.db" - ); - - private final BaseLibrary baseLibrary; - private final DatabaseConnector connector; - private final DatabaseStringGetter stringGetter; - private final IsolatedDriver driver = new IsolatedDriver("jdbc:limboauth_" + this.name().toLowerCase(Locale.ROOT) + ":"); - - DatabaseLibrary(BaseLibrary baseLibrary, DatabaseConnector connector, DatabaseStringGetter stringGetter) { - this.baseLibrary = baseLibrary; - this.connector = connector; - this.stringGetter = stringGetter; - } - - public Connection connect(ClassLoader classLoader, Path dir, String hostname, String database, String user, String password) - throws ReflectiveOperationException, SQLException, IOException { - return this.connect(classLoader, dir, this.stringGetter.getJdbcString(dir, hostname, database), user, password); - } - - public Connection connect(Path dir, String hostname, String database, String user, String password) - throws ReflectiveOperationException, SQLException, IOException { - return this.connect(dir, this.stringGetter.getJdbcString(dir, hostname, database), user, password); - } - - public Connection connect(ClassLoader classLoader, Path dir, String jdbc, String user, String password) - throws ReflectiveOperationException, SQLException, IOException { - return this.connector.connect(classLoader, dir, jdbc, user, password); - } - - public Connection connect(Path dir, String jdbc, String user, String password) throws IOException, ReflectiveOperationException, SQLException { - return this.connector.connect(new IsolatedClassLoader(new URL[]{this.baseLibrary.getClassLoaderURL()}), dir, jdbc, user, password); - } - - public ConnectionSource connectToORM(Path dir, String hostname, String database, String user, String password) - throws ReflectiveOperationException, IOException, SQLException, URISyntaxException { - if (this.driver.getOriginal() == null) { - IsolatedClassLoader classLoader = new IsolatedClassLoader(new URL[] {this.baseLibrary.getClassLoaderURL()}); - Class driverClass = classLoader.loadClass( - switch (this) { - case H2_LEGACY_V1, H2 -> "org.h2.Driver"; - case MYSQL -> "com.mysql.cj.jdbc.NonRegisteringDriver"; - case MARIADB -> "org.mariadb.jdbc.Driver"; - case POSTGRESQL -> "org.postgresql.Driver"; - case SQLITE -> "org.sqlite.JDBC"; - } - ); - - this.driver.setOriginal((Driver) driverClass.getConstructor().newInstance()); - DriverManager.registerDriver(this.driver); - } - - String jdbc = this.stringGetter.getJdbcString(dir, hostname, database); - boolean h2 = this.baseLibrary == BaseLibrary.H2_V1 || this.baseLibrary == BaseLibrary.H2_V2; - return new JdbcPooledConnectionSource(this.driver.getInitializer() + jdbc, - h2 ? null : user, h2 ? null : password, DatabaseTypeUtils.createDatabaseType(jdbc)); - } - - private static Connection fromDriver(Class connectionClass, String jdbc, String user, String password, boolean register) - throws ReflectiveOperationException, SQLException { - Constructor legacyConstructor = connectionClass.getConstructor(); - - Properties info = new Properties(); - if (user != null) { - info.put("user", user); - } - - if (password != null) { - info.put("password", password); - } - - Object driver = legacyConstructor.newInstance(); - - DriverManager.deregisterDriver((Driver) driver); - if (register) { - DriverManager.registerDriver((Driver) driver); - } - - Method connect = connectionClass.getDeclaredMethod("connect", String.class, Properties.class); - connect.setAccessible(true); - return (Connection) connect.invoke(driver, jdbc, info); - } - - public interface DatabaseConnector { - Connection connect(ClassLoader classLoader, Path dir, String jdbc, String user, String password) - throws ReflectiveOperationException, SQLException, IOException; - } - - public interface DatabaseStringGetter { - String getJdbcString(Path dir, String hostname, String database); - } -} diff --git a/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java b/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java index 920c2d2f..e3e0973b 100644 --- a/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java +++ b/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java @@ -9,9 +9,11 @@ import net.elytrium.limboauth.repository.RegisteredPlayerRepository; import net.elytrium.limboauth.repository.exception.DataAccessException; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; -import java.net.MalformedURLException; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; import java.sql.*; import java.util.*; @@ -21,21 +23,27 @@ public class HikariRegisteredPlayerRepository implements RegisteredPlayerReposit public HikariRegisteredPlayerRepository( DataProvider dataProvider, + Path path, String hostname, String database, String user, String password - ) throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, - InstantiationException, IllegalAccessException, SQLException { + ) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, + InstantiationException, IllegalAccessException, SQLException { String driverClassName = switch (dataProvider) { case MYSQL -> "com.mysql.cj.jdbc.NonRegisteringDriver"; case MARIADB -> "org.mariadb.jdbc.Driver"; case POSTGRESQL -> "org.postgresql.Driver"; + case H2, H2_LEGACY -> "org.h2.Driver"; + case SQLITE -> "org.sqlite.JDBC"; }; String databaseUrl = switch (dataProvider) { case MYSQL -> "jdbc:mysql://%s/%s".formatted(hostname, database); case POSTGRESQL -> "jdbc:postgresql://%s/%s".formatted(hostname, database); case MARIADB -> "jdbc:mariadb://%s/%s".formatted(hostname, database); + case H2_LEGACY -> "jdbc:h2:" + path + "/limboauth"; + case H2 -> "jdbc:h2:" + path + "/limboauth-v2"; + case SQLITE -> "jdbc:sqlite:" + path + "/limboauth.db"; }; IsolatedDriver driver = new IsolatedDriver("jdbc:limboauth_" + dataProvider.name().toLowerCase(Locale.ROOT) + ":"); @@ -50,6 +58,32 @@ public HikariRegisteredPlayerRepository( config.setPassword(password); config.setMaximumPoolSize(2); dataSource = new HikariDataSource(config); + if (dataProvider == DataProvider.H2) { + Path legacyDatabase = path.resolve("limboauth.mv.db"); + if (Files.exists(legacyDatabase)) { + Path dumpFile = path.resolve("limboauth.dump.sql"); + try (HikariDataSource legacySource = new HikariDataSource(config)) { + migrateH2(legacySource, dataSource, dumpFile); + Files.delete(dumpFile); + } + Files.move(legacyDatabase, path.resolve("limboauth-v1-backup.mv.db")); + } + } + } + + private void migrateH2(HikariDataSource source, HikariDataSource target, Path dumpFile) throws SQLException { + try (Connection legacyConnection = source.getConnection()) { + try (PreparedStatement migrateStatement = legacyConnection.prepareStatement("SCRIPT TO ?")) { + migrateStatement.setString(1, dumpFile.toString()); + migrateStatement.execute(); + } + } + try (Connection modernConnection = target.getConnection()) { + try (PreparedStatement migrateStatement = modernConnection.prepareStatement("RUNSCRIPT FROM ?")) { + migrateStatement.setString(1, dumpFile.toString()); + migrateStatement.execute(); + } + } } public void updateSchema(DataProvider dataProvider) throws SQLException { @@ -360,22 +394,27 @@ public int registeredPlayerCount() { } private RegisteredPlayer parseRegisteredPlayer(ResultSet resultSet) throws SQLException { - return RegisteredPlayer.builder() - .nickname(resultSet.getString("NICKNAME")) - .lowercaseNickname(resultSet.getString("LOWERCASENICKNAME")) - .hash(resultSet.getString("HASH")) - .ip(resultSet.getString("IP")) - .totpToken(resultSet.getString("TOTPTOKEN")) - .regDate(resultSet.getLong("REGDATE")) - .uuid(resultSet.getString("UUID")) - .premiumUuid(resultSet.getString("PREMIUMUUID")) - .loginIp(resultSet.getString("LOGINIP")) - .loginDate(resultSet.getLong("LOGINDATE")) - .tokenIssuedAt(resultSet.getLong("ISSUEDTIME")) - .build(); + return new RegisteredPlayer( + resultSet.getString("NICKNAME"), + resultSet.getString("UUID"), + resultSet.getString("IP") + ) + .setNickname(resultSet.getString("LOWERCASENICKNAME")) + .setHash(resultSet.getString("HASH")) + .setTotpToken(resultSet.getString("TOTPTOKEN")) + .setRegDate(resultSet.getLong("REGDATE")) + .setPremiumUuid(resultSet.getString("PREMIUMUUID")) + .setLoginIp(resultSet.getString("LOGINIP")) + .setLoginDate(resultSet.getLong("LOGINDATE")) + .setTokenIssuedAt(resultSet.getLong("ISSUEDTIME")); } protected Connection getConnection() throws SQLException { return dataSource.getConnection(); } + + @Override + public void close() throws IOException { + dataSource.close(); + } } diff --git a/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java b/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java index 8a9d3f69..19416c06 100644 --- a/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java +++ b/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java @@ -146,7 +146,7 @@ public void onSpawn(Limbo server, LimboPlayer player) { for (RegisteredPlayer registeredPlayer : alreadyRegistered.stream() .filter(registeredPlayer -> registeredPlayer.getRegDate() < System.currentTimeMillis() - Settings.IMP.MAIN.IP_LIMIT_VALID_TIME) .collect(Collectors.toList())) { - registeredPlayer.setIp(""); + registeredPlayer.setIP(""); this.registeredPlayerRepository.update(registeredPlayer); --sizeOfValidRegistrations; } diff --git a/src/main/java/net/elytrium/limboauth/model/RegisteredPlayer.java b/src/main/java/net/elytrium/limboauth/model/RegisteredPlayer.java index 0273fcb2..6f30904e 100644 --- a/src/main/java/net/elytrium/limboauth/model/RegisteredPlayer.java +++ b/src/main/java/net/elytrium/limboauth/model/RegisteredPlayer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 - 2025 Elytrium + * Copyright (C) 2021 - 2024 Elytrium * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -19,148 +19,192 @@ import at.favre.lib.crypto.bcrypt.BCrypt; import com.velocitypowered.api.proxy.Player; -import lombok.*; -import net.elytrium.limboauth.Settings; - import java.net.InetSocketAddress; import java.util.Locale; import java.util.UUID; +import net.elytrium.limboauth.Settings; -@Getter -@Setter -@Builder -@AllArgsConstructor(access = AccessLevel.PROTECTED) public class RegisteredPlayer { - private static final BCrypt.Hasher HASHER = BCrypt.withDefaults(); + private static final BCrypt.Hasher HASHER = BCrypt.withDefaults(); + + private String nickname; + + private String lowercaseNickname; + + private String hash = ""; + + private String ip; + + private String totpToken = ""; + + private Long regDate = System.currentTimeMillis(); + + private String uuid = ""; + + private String premiumUuid = ""; + + private String loginIp; + + private Long loginDate = System.currentTimeMillis(); + + private Long tokenIssuedAt = System.currentTimeMillis(); + + @Deprecated + public RegisteredPlayer(String nickname, String lowercaseNickname, + String hash, String ip, String totpToken, Long regDate, String uuid, String premiumUuid, String loginIp, Long loginDate) { + this.nickname = nickname; + this.lowercaseNickname = lowercaseNickname; + this.hash = hash; + this.ip = ip; + this.totpToken = totpToken; + this.regDate = regDate; + this.uuid = uuid; + this.premiumUuid = premiumUuid; + this.loginIp = loginIp; + this.loginDate = loginDate; + } + + public RegisteredPlayer(Player player) { + this(player.getUsername(), player.getUniqueId(), player.getRemoteAddress()); + } + + public RegisteredPlayer(String nickname, UUID uuid, InetSocketAddress ip) { + this(nickname, uuid.toString(), ip.getAddress().getHostAddress()); + } + + public RegisteredPlayer(String nickname, String uuid, String ip) { + this.nickname = nickname; + this.lowercaseNickname = nickname.toLowerCase(Locale.ROOT); + this.uuid = uuid; + this.ip = ip; + this.loginIp = ip; + } + + public RegisteredPlayer() { + + } + + public static String genHash(String password) { + return HASHER.hashToString(Settings.IMP.MAIN.BCRYPT_COST, password.toCharArray()); + } + + public RegisteredPlayer setNickname(String nickname) { + this.nickname = nickname; + this.lowercaseNickname = nickname.toLowerCase(Locale.ROOT); + + return this; + } - private String nickname; + public String getNickname() { + return this.nickname == null ? this.lowercaseNickname : this.nickname; + } - private String lowercaseNickname; + public String getLowercaseNickname() { + return this.lowercaseNickname; + } - private String hash = ""; + public RegisteredPlayer setPassword(String password) { + this.hash = genHash(password); + this.tokenIssuedAt = System.currentTimeMillis(); - private String ip; + return this; + } - private String totpToken = ""; + public RegisteredPlayer setHash(String hash) { + this.hash = hash; + this.tokenIssuedAt = System.currentTimeMillis(); - private Long regDate = System.currentTimeMillis(); + return this; + } - private String uuid = ""; + public String getHash() { + return this.hash == null ? "" : this.hash; + } - private String premiumUuid = ""; + public RegisteredPlayer setIP(String ip) { + this.ip = ip; - private String loginIp; + return this; + } - private Long loginDate = System.currentTimeMillis(); + public String getIP() { + return this.ip == null ? "" : this.ip; + } - private Long tokenIssuedAt = System.currentTimeMillis(); + public RegisteredPlayer setTotpToken(String totpToken) { + this.totpToken = totpToken; - @Deprecated - public RegisteredPlayer(String nickname, String lowercaseNickname, - String hash, String ip, String totpToken, Long regDate, String uuid, String premiumUuid, String loginIp, Long loginDate) { - this.nickname = nickname; - this.lowercaseNickname = lowercaseNickname; - this.hash = hash; - this.ip = ip; - this.totpToken = totpToken; - this.regDate = regDate; - this.uuid = uuid; - this.premiumUuid = premiumUuid; - this.loginIp = loginIp; - this.loginDate = loginDate; - } + return this; + } - public RegisteredPlayer(Player player) { - this(player.getUsername(), player.getUniqueId(), player.getRemoteAddress()); - } + public String getTotpToken() { + return this.totpToken == null ? "" : this.totpToken; + } - public RegisteredPlayer(String nickname, UUID uuid, InetSocketAddress ip) { - this(nickname, uuid.toString(), ip.getAddress().getHostAddress()); - } + public RegisteredPlayer setRegDate(Long regDate) { + this.regDate = regDate; - public RegisteredPlayer(String nickname, String uuid, String ip) { - this.nickname = nickname; - this.lowercaseNickname = nickname.toLowerCase(Locale.ROOT); - this.uuid = uuid; - this.ip = ip; - this.loginIp = ip; - } + return this; + } + public long getRegDate() { + return this.regDate == null ? Long.MIN_VALUE : this.regDate; + } - public static String genHash(String password) { - return HASHER.hashToString(Settings.IMP.MAIN.BCRYPT_COST, password.toCharArray()); - } + public RegisteredPlayer setUuid(String uuid) { + this.uuid = uuid; - public RegisteredPlayer setNickname(String nickname) { - this.nickname = nickname; - this.lowercaseNickname = nickname.toLowerCase(Locale.ROOT); + return this; + } - return this; - } + public String getUuid() { + return this.uuid == null ? "" : this.uuid; + } - public RegisteredPlayer setPremiumUuid(UUID premiumUuid) { - this.premiumUuid = premiumUuid.toString(); - return this; - } + public RegisteredPlayer setPremiumUuid(String premiumUuid) { + this.premiumUuid = premiumUuid; - public RegisteredPlayer setPremiumUuid(String premiumUuid) { - this.premiumUuid = premiumUuid; - return this; - } + return this; + } - public String getNickname() { - return this.nickname == null ? this.lowercaseNickname : this.nickname; - } + public RegisteredPlayer setPremiumUuid(UUID premiumUuid) { + this.premiumUuid = premiumUuid.toString(); - public RegisteredPlayer setPassword(String password) { - this.hash = genHash(password); - this.tokenIssuedAt = System.currentTimeMillis(); + return this; + } - return this; - } + public String getPremiumUuid() { + return this.premiumUuid == null ? "" : this.premiumUuid; + } - public RegisteredPlayer setHash(String hash) { - this.hash = hash; - this.tokenIssuedAt = System.currentTimeMillis(); + public String getLoginIp() { + return this.loginIp == null ? "" : this.loginIp; + } - return this; - } + public RegisteredPlayer setLoginIp(String loginIp) { + this.loginIp = loginIp; - public String getHash() { - return this.hash == null ? "" : this.hash; - } + return this; + } - public String getIP() { - return this.ip == null ? "" : this.ip; - } + public long getLoginDate() { + return this.loginDate == null ? Long.MIN_VALUE : this.loginDate; + } - public String getTotpToken() { - return this.totpToken == null ? "" : this.totpToken; - } + public RegisteredPlayer setLoginDate(Long loginDate) { + this.loginDate = loginDate; - public long getRegDate() { - return this.regDate == null ? Long.MIN_VALUE : this.regDate; - } + return this; + } - public String getUuid() { - return this.uuid == null ? "" : this.uuid; - } + public long getTokenIssuedAt() { + return this.tokenIssuedAt == null ? Long.MIN_VALUE : this.tokenIssuedAt; + } - public String getPremiumUuid() { - return this.premiumUuid == null ? "" : this.premiumUuid; - } - - public String getLoginIp() { - return this.loginIp == null ? "" : this.loginIp; - } - - public long getLoginDate() { - return this.loginDate == null ? Long.MIN_VALUE : this.loginDate; - } - - public long getTokenIssuedAt() { - return this.tokenIssuedAt == null ? Long.MIN_VALUE : this.tokenIssuedAt; - } + public RegisteredPlayer setTokenIssuedAt(Long tokenIssuedAt) { + this.tokenIssuedAt = tokenIssuedAt; + return this; + } } diff --git a/src/main/java/net/elytrium/limboauth/repository/RegisteredPlayerRepository.java b/src/main/java/net/elytrium/limboauth/repository/RegisteredPlayerRepository.java index 85e971ee..47b9cbc3 100644 --- a/src/main/java/net/elytrium/limboauth/repository/RegisteredPlayerRepository.java +++ b/src/main/java/net/elytrium/limboauth/repository/RegisteredPlayerRepository.java @@ -3,10 +3,11 @@ import net.elytrium.limboauth.model.RegisteredPlayer; import net.elytrium.limboauth.repository.exception.DataAccessException; +import java.io.Closeable; import java.util.List; import java.util.Optional; -public interface RegisteredPlayerRepository { +public interface RegisteredPlayerRepository extends Closeable { void deleteByLowercaseName(String name) throws DataAccessException; @@ -26,25 +27,8 @@ public interface RegisteredPlayerRepository { void updateLogin(String lowercase, String loginIp, Long loginDate) throws DataAccessException; - /** - * - QueryBuilder premiumCountQuery = this.playerRepository.queryBuilder(); - premiumCountQuery.where() - .eq(RegisteredPlayer.PREMIUM_UUID_FIELD, uuid.toString()) - .and() - .eq(RegisteredPlayer.HASH_FIELD, ""); - premiumCountQuery.setCountOf(true); - * */ boolean isHashEmptyByPremiumUuid(String uuid) throws DataAccessException; - /** - * QueryBuilder crackedCountQuery = this.playerRepository.queryBuilder(); - * crackedCountQuery.where() - * .eq(RegisteredPlayer.LOWERCASE_NICKNAME_FIELD, nickname) - * .and() - * .ne(RegisteredPlayer.HASH_FIELD, ""); - * crackedCountQuery.setCountOf(true); - * */ boolean isHashEmptyByLowercaseName(String name) throws DataAccessException; int registeredPlayerCount(); From 701494d98375452641350f15dc5d5e7f7c844bc1 Mon Sep 17 00:00:00 2001 From: Dmitri Ponomarev Date: Wed, 22 Apr 2026 04:44:10 +0300 Subject: [PATCH 7/7] HikariRegisteredPlayerRepository#parseRegisteredPlayer fix --- .../dependencies/hikary/HikariRegisteredPlayerRepository.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java b/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java index e3e0973b..cbb0c58e 100644 --- a/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java +++ b/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java @@ -399,7 +399,6 @@ private RegisteredPlayer parseRegisteredPlayer(ResultSet resultSet) throws SQLEx resultSet.getString("UUID"), resultSet.getString("IP") ) - .setNickname(resultSet.getString("LOWERCASENICKNAME")) .setHash(resultSet.getString("HASH")) .setTotpToken(resultSet.getString("TOTPTOKEN")) .setRegDate(resultSet.getLong("REGDATE"))