diff --git a/checkstyle.xml b/checkstyle.xml
index 9bae4d4c4..cca3b330f 100644
--- a/checkstyle.xml
+++ b/checkstyle.xml
@@ -220,6 +220,14 @@
authPlugin = core.getAuthPluginHook(); try { @@ -119,13 +153,17 @@ public void run() { } } + // defer auto registration, if it's not enabled in the config if (!isRegistered && !isAutoAuthAllowed(autoRegisterFloodgate)) { return; } - //logging in from bedrock for a second time threw an error with UUID - if (profile == null) { - profile = new StoredProfile(getUUID(player), username, true, getAddress(player).toString()); + // stop the auto login procedure, if the current connection's parameters don't match the one stored in our + // database + // ex. we stored a LINKED account, but the current connection is not linked + if ((profile.getFloodgate() == FloodgateState.LINKED && !isLinked) + || (profile.getFloodgate() == FloodgateState.TRUE && isLinked)) { + return; } //start Bukkit/Bungee specific tasks diff --git a/core/src/main/java/com/github/games647/fastlogin/core/shared/FloodgateState.java b/core/src/main/java/com/github/games647/fastlogin/core/shared/FloodgateState.java new file mode 100644 index 000000000..9e5df985a --- /dev/null +++ b/core/src/main/java/com/github/games647/fastlogin/core/shared/FloodgateState.java @@ -0,0 +1,86 @@ +/* + * SPDX-License-Identifier: MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2023 games647 and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.games647.fastlogin.core.shared; + +public enum FloodgateState { + + /** + * Purely Java profile + */ + FALSE(0), + + /** + * Purely Bedrock profile + */ + TRUE(1), + + /** + * Bedrock profile is bidirectional associated with the Java Mojang profile. + */ + LINKED(2), + + /** + * Data before floodgate database migration. Floodgate state is unknown. + */ + NOT_MIGRATED(3); + + private int value; + + FloodgateState(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + /** + * Convert a number to FloodgateState + *
core, AuthPlugin
authHook, Bedro
public void onLogin(String username, S source) {
core.getPlugin().getLog().info("Handling player {}", username);
+
+ //check if the player is connecting through Bedrock Edition
+ if (bedrockService != null && bedrockService.isBedrockConnection(username)) {
+ //perform Bedrock specific checks and skip Java checks if no longer needed
+ if (bedrockService.performChecks(username, source)) {
+ return;
+ }
+ }
+
StoredProfile profile = core.getStorage().loadProfile(username);
+
+ //can't be a premium Java player, if it's not saved in the database
if (profile == null) {
return;
}
- //check if the player is connecting through Bedrock Edition
- if (bedrockService != null && bedrockService.isBedrockConnection(username)) {
- //perform Bedrock specific checks and skip Java checks, if they are not needed
- if (bedrockService.performChecks(username, source)) {
+ if (profile.isFloodgateMigrated()) {
+ if (profile.getFloodgate() == FloodgateState.TRUE) {
+ // migrated and enabled floodgate player, however the above bedrocks fails, so the current connection
+ // isn't premium
return;
}
+ } else {
+ profile.setFloodgate(FloodgateState.FALSE);
+ core.getPlugin().getLog().info(
+ "Player {} will be migrated to the v2 database schema as a JAVA user", username);
}
callFastLoginPreLoginEvent(username, source, profile);
@@ -139,6 +154,12 @@ private boolean checkNameChange(S source, String username, Profile profile) {
if (core.getConfig().get("nameChangeCheck", false)) {
StoredProfile storedProfile = core.getStorage().loadProfile(profile.getId());
if (storedProfile != null) {
+ if (storedProfile.getFloodgate() == FloodgateState.TRUE) {
+ core.getPlugin().getLog()
+ .info("Player {} is already stored by FastLogin as a Bedrock Edition player.", username);
+ return false;
+ }
+
//uuid exists in the database
core.getPlugin().getLog().info("GameProfile {} changed it's username", profile);
diff --git a/core/src/main/java/com/github/games647/fastlogin/core/storage/SQLStorage.java b/core/src/main/java/com/github/games647/fastlogin/core/storage/SQLStorage.java
index 178a2fe89..ade10a409 100644
--- a/core/src/main/java/com/github/games647/fastlogin/core/storage/SQLStorage.java
+++ b/core/src/main/java/com/github/games647/fastlogin/core/storage/SQLStorage.java
@@ -26,11 +26,13 @@
package com.github.games647.fastlogin.core.storage;
import com.github.games647.craftapi.UUIDAdapter;
+import com.github.games647.fastlogin.core.shared.FloodgateState;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.slf4j.Logger;
import java.sql.Connection;
+import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -56,13 +58,19 @@ public abstract class SQLStorage implements AuthStorage {
+ "UNIQUE (`Name`) "
+ ')';
- protected static final String LOAD_BY_NAME = "SELECT * FROM `" + PREMIUM_TABLE + "` WHERE `Name`=? LIMIT 1";
- protected static final String LOAD_BY_UUID = "SELECT * FROM `" + PREMIUM_TABLE + "` WHERE `UUID`=? LIMIT 1";
+ protected static final String ADD_FLOODGATE_COLUMN_STMT = "ALTER TABLE `" + PREMIUM_TABLE
+ + "` ADD COLUMN `Floodgate` INTEGER(3)";
+
+ protected static final String LOAD_BY_NAME = "SELECT * FROM `" + PREMIUM_TABLE
+ + "` WHERE `Name`=? LIMIT 1";
+ protected static final String LOAD_BY_UUID = "SELECT * FROM `" + PREMIUM_TABLE
+ + "` WHERE `UUID`=? LIMIT 1";
protected static final String INSERT_PROFILE = "INSERT INTO `" + PREMIUM_TABLE
- + "` (`UUID`, `Name`, `Premium`, `LastIp`) " + "VALUES (?, ?, ?, ?) ";
+ + "` (`UUID`, `Name`, `Premium`, `Floodgate`, `LastIp`) " + "VALUES (?, ?, ?, ?, ?) ";
// limit not necessary here, because it's unique
protected static final String UPDATE_PROFILE = "UPDATE `" + PREMIUM_TABLE
- + "` SET `UUID`=?, `Name`=?, `Premium`=?, `LastIp`=?, `LastLogin`=CURRENT_TIMESTAMP WHERE `UserID`=?";
+ + "` SET `UUID`=?, `Name`=?, `Premium`=?, `Floodgate`=?, `LastIp`=?, "
+ + "`LastLogin`=CURRENT_TIMESTAMP WHERE `UserID`=?";
protected final Logger log;
protected final HikariDataSource dataSource;
@@ -81,11 +89,23 @@ public void createTables() throws SQLException {
// choose surrogate PK(ID), because UUID can be null for offline players
// if UUID is always Premium UUID we would have to update offline player entries on insert
// name cannot be PK, because it can be changed for premium players
-
//todo: add unique uuid index usage
try (Connection con = dataSource.getConnection();
- Statement createStmt = con.createStatement()) {
- createStmt.executeUpdate(CREATE_TABLE_STMT);
+ Statement stmt = con.createStatement()) {
+ stmt.executeUpdate(getCreateTableStmt());
+
+ // add Floodgate column
+ DatabaseMetaData md = con.getMetaData();
+ if (isColumnMissing(md, "Floodgate")) {
+ stmt.executeUpdate(ADD_FLOODGATE_COLUMN_STMT);
+ }
+
+ }
+ }
+
+ private boolean isColumnMissing(DatabaseMetaData metaData, String columnName) throws SQLException {
+ try (ResultSet rs = metaData.getColumns(null, null, PREMIUM_TABLE, columnName)) {
+ return !rs.next();
}
}
@@ -97,7 +117,8 @@ public StoredProfile loadProfile(String name) {
loadStmt.setString(1, name);
try (ResultSet resultSet = loadStmt.executeQuery()) {
- return parseResult(resultSet).orElseGet(() -> new StoredProfile(null, name, false, ""));
+ return parseResult(resultSet).orElseGet(() -> new StoredProfile(null, name, false,
+ FloodgateState.FALSE, ""));
}
} catch (SQLException sqlEx) {
log.error("Failed to query profile: {}", name, sqlEx);
@@ -124,15 +145,25 @@ public StoredProfile loadProfile(UUID uuid) {
private Optional