diff --git a/build.gradle b/build.gradle index 0a5e7b10..8465f580 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ plugins { } setGroup("net.elytrium") -setVersion("1.1.14") +setVersion("1.2.0-SNAPSHOT-4") java { setSourceCompatibility(JavaVersion.VERSION_17) @@ -58,8 +58,8 @@ 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") @@ -84,7 +84,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*/**") @@ -106,7 +105,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 a0d66330..40642c02 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,12 @@ 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( + if (this.playerRepository != null) { + this.playerRepository.close(); + } + this.playerRepository = dataProvider.createRegisteredPlayerRepository( this.dataDirectoryFile.toPath().toAbsolutePath(), dbConfig.HOSTNAME, dbConfig.DATABASE + dbConfig.CONNECTION_PARAMETERS, @@ -324,30 +297,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 +320,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 +370,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 +421,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 +465,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 +473,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 +489,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 +545,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 +562,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 +641,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 +653,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 +812,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 e19fff0b..22dafb4a 100644 --- a/src/main/java/net/elytrium/limboauth/Settings.java +++ b/src/main/java/net/elytrium/limboauth/Settings.java @@ -32,12 +32,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 @@ -512,8 +520,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 20342cff..f4f40590 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 a9993512..dc2ea755 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 d7284b76..7c4a7263 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 e88caea3..72833079 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 a77206fe..9a4adcc0 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 12d6f08c..e8c49abc 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 9ad10735..712269b5 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 20366cad..22afbd32 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 444d69ed..a8ab756f 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..acf18318 --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/data/DataProvider.java @@ -0,0 +1,45 @@ +package net.elytrium.limboauth.data; + +import net.elytrium.limboauth.dependencies.BaseLibrary; +import net.elytrium.limboauth.dependencies.hikary.HikariRegisteredPlayerRepository; +import net.elytrium.limboauth.repository.RegisteredPlayerRepository; + +import java.nio.file.Path; + +public enum DataProvider { + MYSQL(BaseLibrary.MYSQL), + MARIADB(BaseLibrary.MARIADB), + POSTGRESQL(BaseLibrary.POSTGRESQL), + H2_LEGACY(BaseLibrary.H2_V1), + H2(BaseLibrary.H2_V2), + SQLITE(BaseLibrary.SQLITE); + + 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, + path, + host, + database, + user, + password + ); + repo.updateSchema(this); + 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 new file mode 100644 index 00000000..cbb0c58e --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/dependencies/hikary/HikariRegisteredPlayerRepository.java @@ -0,0 +1,419 @@ +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.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.*; +import java.util.*; + +public class HikariRegisteredPlayerRepository implements RegisteredPlayerRepository { + + private final HikariDataSource dataSource; + + public HikariRegisteredPlayerRepository( + DataProvider dataProvider, + Path path, + String hostname, + String database, + String user, + String password + ) 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) + ":"); + 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); + 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 { + try (Connection con = getConnection()) { + PreparedStatement createStatement = con.prepareStatement(""" + 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 != null && 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 != null && 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 != null && 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 != null && 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 = ?, + HASH = ?, + IP = ?, + LOGINIP = ?, + TOTPTOKEN = ?, + REGDATE = ?, + LOGINDATE = ?, + UUID = ?, + PREMIUMUUID = ?, + ISSUEDTIME = ? + 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.prepareStatement("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.prepareStatement("UPDATE AUTH SET LOGINIP = ?, LOGINDATE = ? WHERE LOWERCASENICKNAME = ?"); + st.setString(1, loginIp); + st.setLong(2, 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.prepareStatement("SELECT HASH FROM AUTH WHERE PREMIUMUUID = ?"); + st.setString(1, uuid); + ResultSet resultSet = st.getResultSet(); + return resultSet != null && 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.prepareStatement("SELECT HASH FROM AUTH WHERE LOWERCASENICKNAME = ?"); + st.setString(1, name); + ResultSet resultSet = st.getResultSet(); + return resultSet != null && resultSet.next() && "".equals(resultSet.getString("HASH")); + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + + public int registeredPlayerCount() { + try (Connection con = getConnection()) { + PreparedStatement st = con.prepareStatement("SELECT COUNT(*) FROM AUTH"); + ResultSet set = st.getResultSet(); + if (set != null && set.next()) { + return set.getInt(1); + } else { + return 0; + } + } catch (SQLException e) { + return 0; + } + } + + private RegisteredPlayer parseRegisteredPlayer(ResultSet resultSet) throws SQLException { + return new RegisteredPlayer( + resultSet.getString("NICKNAME"), + resultSet.getString("UUID"), + resultSet.getString("IP") + ) + .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 0f053b70..19416c06 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,569 @@ 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.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; - } - } - - 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); - } + @Override + public void onSpawn(Limbo server, LimboPlayer player) { + this.player = player; - 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 6ea55a4d..a17ba4a9 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 982bdc91..3b67e957 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 18a056be..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 @@ -18,62 +18,36 @@ 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 java.net.InetSocketAddress; import java.util.Locale; import java.util.UUID; import net.elytrium.limboauth.Settings; -@DatabaseTable(tableName = "AUTH") 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 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..47b9cbc3 --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/repository/RegisteredPlayerRepository.java @@ -0,0 +1,36 @@ +package net.elytrium.limboauth.repository; + +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 extends Closeable { + + 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; + + boolean isHashEmptyByPremiumUuid(String uuid) throws DataAccessException; + + 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); + } +}