diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java index a9aafffc..46933090 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java @@ -11,6 +11,7 @@ import pro.gravit.launcher.request.auth.GetAvailabilityAuthRequest; import pro.gravit.launchserver.auth.core.AuthCoreProvider; import pro.gravit.launchserver.auth.handler.AuthHandler; +import pro.gravit.launchserver.auth.password.PasswordVerifier; import pro.gravit.launchserver.auth.protect.ProtectHandler; import pro.gravit.launchserver.auth.protect.hwid.HWIDProvider; import pro.gravit.launchserver.auth.provider.AuthProvider; @@ -203,6 +204,7 @@ public static void initGson(LaunchServerModulesManager modulesManager) { @SuppressWarnings("deprecation") public static void registerAll() { AuthCoreProvider.registerProviders(); + PasswordVerifier.registerProviders(); AuthHandler.registerHandlers(); AuthProvider.registerProviders(); TextureProvider.registerProviders(); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/AuthCoreProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/AuthCoreProvider.java index be0b576a..84c07018 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/AuthCoreProvider.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/AuthCoreProvider.java @@ -24,6 +24,7 @@ public abstract class AuthCoreProvider implements AutoCloseable { public static void registerProviders() { if (!registredProviders) { providers.register("reject", RejectAuthCoreProvider.class); + providers.register("mysql", MySQLCoreProvider.class); registredProviders = true; } } @@ -33,7 +34,7 @@ public static void registerProviders() { public abstract PasswordVerifyReport verifyPassword(User user, AuthRequest.AuthPasswordInterface password); public abstract void init(LaunchServer server); // Auth Handler methods - protected abstract boolean updateAuth(User user) throws IOException; + protected abstract boolean updateAuth(User user, String accessToken) throws IOException; protected abstract boolean updateServerID(User user, String serverID) throws IOException; public List getDetails(Client client) { diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/MySQLCoreProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/MySQLCoreProvider.java new file mode 100644 index 00000000..58a2178c --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/MySQLCoreProvider.java @@ -0,0 +1,185 @@ +package pro.gravit.launchserver.auth.core; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import pro.gravit.launcher.ClientPermissions; +import pro.gravit.launcher.request.auth.AuthRequest; +import pro.gravit.launcher.request.auth.password.AuthPlainPassword; +import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.auth.AuthException; +import pro.gravit.launchserver.auth.MySQLSourceConfig; +import pro.gravit.launchserver.auth.handler.CachedAuthHandler; +import pro.gravit.launchserver.auth.password.DigestPasswordVerifier; +import pro.gravit.launchserver.socket.response.auth.AuthResponse; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.UUID; + +public class MySQLCoreProvider extends AuthCoreProvider { + private transient final Logger logger = LogManager.getLogger(); + public MySQLSourceConfig mySQLHolder; + + public String uuidColumn; + public String usernameColumn; + public String accessTokenColumn; + public String passwordColumn; + public String serverIDColumn; + public String table; + public DigestPasswordVerifier passwordVerifier; + + // Prepared SQL queries + private transient String queryByUUIDSQL; + private transient String queryByUsernameSQL; + private transient String updateAuthSQL; + private transient String updateServerIDSQL; + + @Override + public User getUserByUsername(String username) { + try { + return query(queryByUsernameSQL, username); + } catch (IOException e) { + logger.error("SQL error", e); + return null; + } + } + + @Override + public User getUserByUUID(UUID uuid) { + try { + return query(queryByUUIDSQL, uuid.toString()); + } catch (IOException e) { + logger.error("SQL error", e); + return null; + } + } + + @Override + public void verifyAuth(AuthResponse.AuthContext context) throws AuthException { + + } + + @Override + public PasswordVerifyReport verifyPassword(User user, AuthRequest.AuthPasswordInterface password) { + if(passwordVerifier.check(((MySQLUser)user).password, ((AuthPlainPassword)password).password)) { + return new PasswordVerifyReport(true); + } else { + return PasswordVerifyReport.FAILED; + } + } + + @Override + public void init(LaunchServer server) { + if (mySQLHolder == null) logger.error("mySQLHolder cannot be null"); + if (uuidColumn == null) logger.error("uuidColumn cannot be null"); + if (usernameColumn == null) logger.error("usernameColumn cannot be null"); + if (accessTokenColumn == null) logger.error("accessTokenColumn cannot be null"); + if (serverIDColumn == null) logger.error("serverIDColumn cannot be null"); + if (table == null) logger.error("table cannot be null"); + // Prepare SQL queries + queryByUUIDSQL = String.format("SELECT %s, %s, %s, %s, %s FROM %s WHERE %s=? LIMIT 1", + uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn, table, uuidColumn); + queryByUsernameSQL = String.format("SELECT %s, %s, %s, %s, %s FROM %s WHERE %s=? LIMIT 1", + uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn, table, usernameColumn); + + updateAuthSQL = String.format("UPDATE %s SET %s=?, %s=NULL WHERE %s=? LIMIT 1", + table, accessTokenColumn, serverIDColumn, uuidColumn); + updateServerIDSQL = String.format("UPDATE %s SET %s=? WHERE %s=? LIMIT 1", + table, serverIDColumn, uuidColumn); + } + + @Override + protected boolean updateAuth(User user, String accessToken) throws IOException { + try (Connection c = mySQLHolder.getConnection()) { + PreparedStatement s = c.prepareStatement(updateAuthSQL); + s.setString(1, accessToken); + s.setString(2, user.getUUID().toString()); + s.setQueryTimeout(MySQLSourceConfig.TIMEOUT); + return s.executeUpdate() > 0; + } catch (SQLException e) { + throw new IOException(e); + } + } + + @Override + protected boolean updateServerID(User user, String serverID) throws IOException { + try (Connection c = mySQLHolder.getConnection()) { + PreparedStatement s = c.prepareStatement(updateServerIDSQL); + s.setString(1, serverID); + s.setString(2, user.getUUID().toString()); + s.setQueryTimeout(MySQLSourceConfig.TIMEOUT); + return s.executeUpdate() > 0; + } catch (SQLException e) { + throw new IOException(e); + } + } + + @Override + public void close() throws IOException { + mySQLHolder.close(); + } + + private MySQLUser constructUser(ResultSet set) throws SQLException { + return set.next() ? new MySQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn), + set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), new ClientPermissions()) : null; + } + + private User query(String sql, String value) throws IOException { + try (Connection c = mySQLHolder.getConnection()) { + PreparedStatement s = c.prepareStatement(sql); + s.setString(1, value); + s.setQueryTimeout(MySQLSourceConfig.TIMEOUT); + try (ResultSet set = s.executeQuery()) { + return constructUser(set); + } + } catch (SQLException e) { + throw new IOException(e); + } + } + + public static class MySQLUser implements User { + private final UUID uuid; + private final String username; + private final String accessToken; + private final String serverId; + private final String password; + private final ClientPermissions permissions; + + public MySQLUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions) { + this.uuid = uuid; + this.username = username; + this.accessToken = accessToken; + this.serverId = serverId; + this.password = password; + this.permissions = permissions; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public UUID getUUID() { + return uuid; + } + + @Override + public String getServerId() { + return serverId; + } + + @Override + public String getAccessToken() { + return accessToken; + } + + @Override + public ClientPermissions getPermissions() { + return permissions; + } + } +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/RejectAuthCoreProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/RejectAuthCoreProvider.java index 41500f72..7f5afc8b 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/RejectAuthCoreProvider.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/RejectAuthCoreProvider.java @@ -35,7 +35,7 @@ public void init(LaunchServer server) { } @Override - protected boolean updateAuth(User user) throws IOException { + protected boolean updateAuth(User user, String accessToken) throws IOException { return false; } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/password/DigestPasswordVerifier.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/password/DigestPasswordVerifier.java new file mode 100644 index 00000000..962c4574 --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/password/DigestPasswordVerifier.java @@ -0,0 +1,26 @@ +package pro.gravit.launchserver.auth.password; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import pro.gravit.utils.helper.SecurityHelper; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +public class DigestPasswordVerifier extends PasswordVerifier { + private transient final Logger logger = LogManager.getLogger(); + public String algo; + @Override + public boolean check(String encryptedPassword, String password) { + try { + MessageDigest digest = MessageDigest.getInstance(algo); + byte[] bytes = SecurityHelper.fromHex(encryptedPassword); + return Arrays.equals(password.getBytes(StandardCharsets.UTF_8), digest.digest(bytes)); + } catch (NoSuchAlgorithmException e) { + logger.error("Digest algorithm {} not supported", algo); + return false; + } + } +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/password/DoubleDigestPasswordVerifier.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/password/DoubleDigestPasswordVerifier.java new file mode 100644 index 00000000..d603e610 --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/password/DoubleDigestPasswordVerifier.java @@ -0,0 +1,28 @@ +package pro.gravit.launchserver.auth.password; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import pro.gravit.utils.helper.SecurityHelper; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +public class DoubleDigestPasswordVerifier extends PasswordVerifier { + private transient final Logger logger = LogManager.getLogger(); + public String algo; + public boolean toHexMode; + @Override + public boolean check(String encryptedPassword, String password) { + try { + MessageDigest digest = MessageDigest.getInstance(algo); + byte[] bytes = SecurityHelper.fromHex(encryptedPassword); + byte[] firstDigest = digest.digest(bytes); + return Arrays.equals(password.getBytes(StandardCharsets.UTF_8), toHexMode ? digest.digest(SecurityHelper.toHex(firstDigest).getBytes(StandardCharsets.UTF_8)) : digest.digest(firstDigest)); + } catch (NoSuchAlgorithmException e) { + logger.error("Digest algorithm {} not supported", algo); + return false; + } + } +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/password/PasswordVerifier.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/password/PasswordVerifier.java new file mode 100644 index 00000000..9f5ade22 --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/password/PasswordVerifier.java @@ -0,0 +1,17 @@ +package pro.gravit.launchserver.auth.password; + +import pro.gravit.utils.ProviderMap; + +public abstract class PasswordVerifier { + public static final ProviderMap providers = new ProviderMap<>("PasswordVerifier"); + private static boolean registeredProviders = false; + public static void registerProviders() { + if(!registeredProviders) { + providers.register("plain", PlainPasswordVerifier.class); + providers.register("digest", DigestPasswordVerifier.class); + providers.register("doubleDigest", DoubleDigestPasswordVerifier.class); + registeredProviders = true; + } + } + public abstract boolean check(String encryptedPassword, String password); +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/password/PlainPasswordVerifier.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/password/PlainPasswordVerifier.java new file mode 100644 index 00000000..5983c75e --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/password/PlainPasswordVerifier.java @@ -0,0 +1,8 @@ +package pro.gravit.launchserver.auth.password; + +public class PlainPasswordVerifier extends PasswordVerifier { + @Override + public boolean check(String encryptedPassword, String password) { + return encryptedPassword.equals(password); + } +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/LaunchServerGsonManager.java b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/LaunchServerGsonManager.java index cacdb4f0..ca0532b2 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/LaunchServerGsonManager.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/LaunchServerGsonManager.java @@ -11,6 +11,7 @@ import pro.gravit.launcher.request.auth.GetAvailabilityAuthRequest; import pro.gravit.launchserver.auth.core.AuthCoreProvider; import pro.gravit.launchserver.auth.handler.AuthHandler; +import pro.gravit.launchserver.auth.password.PasswordVerifier; import pro.gravit.launchserver.auth.protect.ProtectHandler; import pro.gravit.launchserver.auth.protect.hwid.HWIDProvider; import pro.gravit.launchserver.auth.provider.AuthProvider; @@ -39,6 +40,7 @@ public void registerAdapters(GsonBuilder builder) { builder.registerTypeAdapter(TextureProvider.class, new UniversalJsonAdapter<>(TextureProvider.providers)); builder.registerTypeAdapter(AuthHandler.class, new UniversalJsonAdapter<>(AuthHandler.providers)); builder.registerTypeAdapter(AuthCoreProvider.class, new UniversalJsonAdapter<>(AuthCoreProvider.providers)); + builder.registerTypeAdapter(PasswordVerifier.class, new UniversalJsonAdapter<>(PasswordVerifier.providers)); builder.registerTypeAdapter(Component.class, new UniversalJsonAdapter<>(Component.providers)); builder.registerTypeAdapter(ProtectHandler.class, new UniversalJsonAdapter<>(ProtectHandler.providers)); builder.registerTypeAdapter(DaoProvider.class, new UniversalJsonAdapter<>(DaoProvider.providers));