diff --git a/LaunchServer/LaunchServer.iml b/LaunchServer/LaunchServer.iml
index 0c1bb78..f54725d 100644
--- a/LaunchServer/LaunchServer.iml
+++ b/LaunchServer/LaunchServer.iml
@@ -69,5 +69,14 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LaunchServer/MANIFEST.MF b/LaunchServer/MANIFEST.MF
index 0eb35aa..8a1a321 100644
--- a/LaunchServer/MANIFEST.MF
+++ b/LaunchServer/MANIFEST.MF
@@ -2,7 +2,8 @@
Class-Path: libraries/jansi.jar libraries/jline2.jar libraries/mysql.j
ar libraries/hikaricp/hikaricp.jar libraries/hikaricp/javassist.jar l
ibraries/hikaricp/slf4j-api.jar libraries/hikaricp/slf4j-simple.jar l
- ibraries/launch4j/launch4j.jar ibraries/jbcrypt.jar
+ ibraries/launch4j/launch4j.jar ibraries/jbcrypt.jar ibraries/postgres
+ ql.jar
Main-Class: launchserver.LaunchServer
Name: launchserver/
diff --git a/LaunchServer/source/auth/MySQLSourceConfig.java b/LaunchServer/source/auth/MySQLSourceConfig.java
index e65b26e..387b544 100644
--- a/LaunchServer/source/auth/MySQLSourceConfig.java
+++ b/LaunchServer/source/auth/MySQLSourceConfig.java
@@ -14,7 +14,7 @@
import java.sql.Connection;
import java.sql.SQLException;
-public final class MySQLSourceConfig extends ConfigObject implements AutoCloseable
+public final class MySQLSourceConfig extends ConfigObject implements AutoCloseable, SQLSourceConfig
{
@LauncherAPI
public static final int TIMEOUT = VerifyHelper.verifyInt(
diff --git a/LaunchServer/source/auth/PostgreSQLSourceConfig.java b/LaunchServer/source/auth/PostgreSQLSourceConfig.java
new file mode 100644
index 0000000..7686e62
--- /dev/null
+++ b/LaunchServer/source/auth/PostgreSQLSourceConfig.java
@@ -0,0 +1,105 @@
+package launchserver.auth;
+
+import org.postgresql.ds.PGSimpleDataSource;
+import com.zaxxer.hikari.HikariDataSource;
+import launcher.LauncherAPI;
+import launcher.helper.LogHelper;
+import launcher.helper.VerifyHelper;
+import launcher.serialize.config.ConfigObject;
+import launcher.serialize.config.entry.BlockConfigEntry;
+import launcher.serialize.config.entry.IntegerConfigEntry;
+import launcher.serialize.config.entry.StringConfigEntry;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+public final class PostgreSQLSourceConfig extends ConfigObject implements AutoCloseable, SQLSourceConfig
+{
+ @LauncherAPI
+ public static final int TIMEOUT = VerifyHelper.verifyInt(
+ Integer.parseInt(System.getProperty("launcher.postgresql.idleTimeout", Integer.toString(5000))),
+ VerifyHelper.POSITIVE, "launcher.postgresql.idleTimeout can't be <= 5000");
+ private static final int MAX_POOL_SIZE = VerifyHelper.verifyInt(
+ Integer.parseInt(System.getProperty("launcher.postgresql.maxPoolSize", Integer.toString(3))),
+ VerifyHelper.POSITIVE, "launcher.postgresql.maxPoolSize can't be <= 0");
+
+ // Instance
+ private final String poolName;
+
+ // Config
+ private final String address;
+ private final int port;
+ private final String username;
+ private final String password;
+ private final String database;
+
+ // Cache
+ private DataSource source;
+ private boolean hikari;
+
+ @LauncherAPI
+ public PostgreSQLSourceConfig(String poolName, BlockConfigEntry block)
+ {
+ super(block);
+ this.poolName = poolName;
+ address = VerifyHelper.verify(block.getEntryValue("address", StringConfigEntry.class),
+ VerifyHelper.NOT_EMPTY, "PostgreSQL address can't be empty");
+ port = VerifyHelper.verifyInt(block.getEntryValue("port", IntegerConfigEntry.class),
+ VerifyHelper.range(0, 65535), "Illegal PostgreSQL port");
+ username = VerifyHelper.verify(block.getEntryValue("username", StringConfigEntry.class),
+ VerifyHelper.NOT_EMPTY, "PostgreSQL username can't be empty");
+ password = block.getEntryValue("password", StringConfigEntry.class);
+ database = VerifyHelper.verify(block.getEntryValue("database", StringConfigEntry.class),
+ VerifyHelper.NOT_EMPTY, "PostgreSQL database can't be empty");
+
+ // Password shouldn't be verified
+ }
+
+ @Override
+ public synchronized void close()
+ {
+ if (hikari)
+ { // Shutdown hikari pool
+ ((HikariDataSource) source).close();
+ }
+ }
+
+ @LauncherAPI
+ public synchronized Connection getConnection() throws SQLException {
+ if (source == null) { // New data source
+ PGSimpleDataSource postgresqlSource = new PGSimpleDataSource();
+
+ // Set credentials
+ postgresqlSource.setServerName(address);
+ postgresqlSource.setPortNumber(port);
+ postgresqlSource.setUser(username);
+ postgresqlSource.setPassword(password);
+ postgresqlSource.setDatabaseName(database);
+
+ // Try using HikariCP
+ source = postgresqlSource;
+ try {
+ Class.forName("com.zaxxer.hikari.HikariDataSource");
+ hikari = true; // Used for shutdown. Not instanceof because of possible classpath error
+
+ // Set HikariCP pool
+ HikariDataSource hikariSource = new HikariDataSource();
+ hikariSource.setDataSource(source);
+
+ // Set pool settings
+ hikariSource.setPoolName(poolName);
+ hikariSource.setMinimumIdle(0);
+ hikariSource.setMaximumPoolSize(MAX_POOL_SIZE);
+ hikariSource.setIdleTimeout(TIMEOUT * 1000L);
+
+ // Replace source with hds
+ source = hikariSource;
+ LogHelper.info("HikariCP pooling enabled for '%s'", poolName);
+ } catch (ClassNotFoundException ignored) {
+ LogHelper.warning("HikariCP isn't in classpath for '%s'", poolName);
+ }
+ }
+ return source.getConnection();
+ }
+}
diff --git a/LaunchServer/source/auth/SQLSourceConfig.java b/LaunchServer/source/auth/SQLSourceConfig.java
new file mode 100644
index 0000000..df5a6e4
--- /dev/null
+++ b/LaunchServer/source/auth/SQLSourceConfig.java
@@ -0,0 +1,10 @@
+package launchserver.auth;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+public interface SQLSourceConfig
+{
+ void close();
+ Connection getConnection() throws SQLException;
+}
diff --git a/LaunchServer/source/auth/handler/AuthHandler.java b/LaunchServer/source/auth/handler/AuthHandler.java
index 7c69691..bca0496 100644
--- a/LaunchServer/source/auth/handler/AuthHandler.java
+++ b/LaunchServer/source/auth/handler/AuthHandler.java
@@ -26,6 +26,7 @@
registerHandler("binaryFile", BinaryFileAuthHandler::new);
registerHandler("textFile", TextFileAuthHandler::new);
registerHandler("mysql", MySQLAuthHandler::new);
+ registerHandler("postgresql", PostgreSQLAuthHandler::new);
}
@LauncherAPI
diff --git a/LaunchServer/source/auth/handler/PostgreSQLAuthHandler.java b/LaunchServer/source/auth/handler/PostgreSQLAuthHandler.java
new file mode 100644
index 0000000..4fe8921
--- /dev/null
+++ b/LaunchServer/source/auth/handler/PostgreSQLAuthHandler.java
@@ -0,0 +1,134 @@
+package launchserver.auth.handler;
+
+import launcher.helper.LogHelper;
+import launcher.helper.VerifyHelper;
+import launcher.serialize.config.entry.BlockConfigEntry;
+import launcher.serialize.config.entry.BooleanConfigEntry;
+import launcher.serialize.config.entry.StringConfigEntry;
+import launchserver.auth.PostgreSQLSourceConfig;
+
+import java.io.IOException;
+import java.sql.*;
+import java.util.UUID;
+
+public final class PostgreSQLAuthHandler extends CachedAuthHandler {
+ private final PostgreSQLSourceConfig postgreSQLHolder;
+ private final String uuidColumn;
+ private final String usernameColumn;
+ private final String accessTokenColumn;
+ private final String serverIDColumn;
+
+
+ private final String queryByUUIDSQL;
+ private final String queryByUsernameSQL;
+ private final String updateAuthSQL;
+ private final String updateServerIDSQL;
+
+ PostgreSQLAuthHandler(BlockConfigEntry block) {
+ super(block);
+ postgreSQLHolder = new PostgreSQLSourceConfig("authHandlerPool", block);
+
+ // Read query params
+ String table = VerifyHelper.verifyIDName(
+ block.getEntryValue("table", StringConfigEntry.class));
+ uuidColumn = VerifyHelper.verifyIDName(
+ block.getEntryValue("uuidColumn", StringConfigEntry.class));
+ usernameColumn = VerifyHelper.verifyIDName(
+ block.getEntryValue("usernameColumn", StringConfigEntry.class));
+ accessTokenColumn = VerifyHelper.verifyIDName(
+ block.getEntryValue("accessTokenColumn", StringConfigEntry.class));
+ serverIDColumn = VerifyHelper.verifyIDName(
+ block.getEntryValue("serverIDColumn", StringConfigEntry.class));
+
+ // Prepare SQL queries
+ queryByUUIDSQL = String.format("SELECT %s, %s, %s, %s FROM %s WHERE %s=? LIMIT 1",
+ uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, table, uuidColumn);
+ queryByUsernameSQL = String.format("SELECT %s, %s, %s, %s FROM %s WHERE %s=? LIMIT 1",
+ uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, table, usernameColumn);
+ updateAuthSQL = String.format("UPDATE %s SET %s=?, %s=?, %s=NULL WHERE %s=? LIMIT 1",
+ table, usernameColumn, accessTokenColumn, serverIDColumn, uuidColumn);
+ updateServerIDSQL = String.format("UPDATE %s SET %s=? WHERE %s=? LIMIT 1",
+ table, serverIDColumn, uuidColumn);
+
+ // Fetch all entries
+ if (block.getEntryValue("fetchAll", BooleanConfigEntry.class)) {
+ LogHelper.info("Fetching all AuthHandler entries");
+ String query = String.format("SELECT %s, %s, %s, %s FROM %s",
+ uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, table);
+ try (Connection c = postgreSQLHolder.getConnection(); Statement statement = c.createStatement();
+ ResultSet set = statement.executeQuery(query)) {
+ for (Entry entry = constructEntry(set); entry != null; entry = constructEntry(set)) {
+ addEntry(entry);
+ }
+ } catch (SQLException e) {
+ LogHelper.error(e);
+ }
+ }
+ }
+
+ @Override
+ public void close() {
+ postgreSQLHolder.close();
+ }
+
+ private Entry constructEntry(ResultSet set) throws SQLException {
+ return set.next() ? new Entry(UUID.fromString(set.getString(uuidColumn)),
+ set.getString(usernameColumn), set.getString(accessTokenColumn), set.getString(serverIDColumn)) : null;
+ }
+
+ @Override
+ protected Entry fetchEntry(String username) throws IOException {
+ return query(queryByUsernameSQL, username);
+ }
+
+ @Override
+ protected Entry fetchEntry(UUID uuid) throws IOException {
+ return query(queryByUUIDSQL, uuid.toString());
+ }
+
+ @Override
+ protected boolean updateAuth(UUID uuid, String username, String accessToken) throws IOException {
+ try (Connection c = postgreSQLHolder.getConnection();
+ PreparedStatement s = c.prepareStatement(updateAuthSQL)) {
+ s.setString(1, username); // Username case
+ s.setString(2, accessToken);
+ s.setString(3, uuid.toString());
+
+ // Execute update
+ s.setQueryTimeout(PostgreSQLSourceConfig.TIMEOUT);
+ return s.executeUpdate() > 0;
+ } catch (SQLException e) {
+ throw new IOException(e);
+ }
+ }
+
+ @Override
+ protected boolean updateServerID(UUID uuid, String serverID) throws IOException {
+ try (Connection c = postgreSQLHolder.getConnection();
+ PreparedStatement s = c.prepareStatement(updateServerIDSQL)) {
+ s.setString(1, serverID);
+ s.setString(2, uuid.toString());
+
+ // Execute update
+ s.setQueryTimeout(PostgreSQLSourceConfig.TIMEOUT);
+ return s.executeUpdate() > 0;
+ } catch (SQLException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private Entry query(String sql, String value) throws IOException {
+ try (Connection c = postgreSQLHolder.getConnection();
+ PreparedStatement s = c.prepareStatement(sql)) {
+ s.setString(1, value);
+
+ // Execute query
+ s.setQueryTimeout(PostgreSQLSourceConfig.TIMEOUT);
+ try (ResultSet set = s.executeQuery()) {
+ return constructEntry(set);
+ }
+ } catch (SQLException e) {
+ throw new IOException(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/LaunchServer/source/auth/provider/AuthProvider.java b/LaunchServer/source/auth/provider/AuthProvider.java
index 9ff33bc..1590414 100644
--- a/LaunchServer/source/auth/provider/AuthProvider.java
+++ b/LaunchServer/source/auth/provider/AuthProvider.java
@@ -27,6 +27,7 @@
registerProvider("mysql", MySQLAuthProvider::new);
registerProvider("mysql-bcrypt", MySQLBcryptAuthProvider::new);
registerProvider("request", RequestAuthProvider::new);
+ registerProvider("postgresql", PostgreSQLAuthProvider::new);
}
@LauncherAPI
diff --git a/LaunchServer/source/auth/provider/PostgreSQLAuthProvider.java b/LaunchServer/source/auth/provider/PostgreSQLAuthProvider.java
new file mode 100644
index 0000000..c331d7f
--- /dev/null
+++ b/LaunchServer/source/auth/provider/PostgreSQLAuthProvider.java
@@ -0,0 +1,59 @@
+package launchserver.auth.provider;
+
+import launcher.helper.CommonHelper;
+import launcher.helper.SecurityHelper;
+import launcher.helper.VerifyHelper;
+import launcher.serialize.config.entry.BlockConfigEntry;
+import launcher.serialize.config.entry.ListConfigEntry;
+import launcher.serialize.config.entry.StringConfigEntry;
+import launchserver.auth.AuthException;
+import launchserver.auth.PostgreSQLSourceConfig;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public final class PostgreSQLAuthProvider extends AuthProvider
+{
+ private final PostgreSQLSourceConfig postgreSQLHolder;
+ private final String query;
+ private final String[] queryParams;
+
+ PostgreSQLAuthProvider(BlockConfigEntry block) {
+ super(block);
+ postgreSQLHolder = new PostgreSQLSourceConfig("authProviderPool", block);
+
+ // Read query
+ query = VerifyHelper.verify(block.getEntryValue("query", StringConfigEntry.class),
+ VerifyHelper.NOT_EMPTY, "PostgreSQL query can't be empty");
+ queryParams = block.getEntry("queryParams", ListConfigEntry.class).
+ stream(StringConfigEntry.class).toArray(String[]::new);
+ }
+
+ @Override
+ public AuthProviderResult auth(String login, String password, String ip) throws SQLException, AuthException
+ {
+ try (Connection c = postgreSQLHolder.getConnection(); PreparedStatement s = c.prepareStatement(query))
+ {
+ String[] replaceParams = {"login", login, "password", password};
+ for (int i = 0; i < queryParams.length; i++)
+ {
+ s.setString(i + 1, CommonHelper.replace(queryParams[i], replaceParams));
+ }
+
+ // Execute SQL query
+ s.setQueryTimeout(PostgreSQLSourceConfig.TIMEOUT);
+ try (ResultSet set = s.executeQuery())
+ {
+ return set.next() ? new AuthProviderResult(set.getString(1), SecurityHelper.randomStringToken()) : authError("Incorrect username or password");
+ }
+ }
+ }
+
+ @Override
+ public void close()
+ {
+ // Do nothing
+ }
+}
\ No newline at end of file
diff --git a/build/libraries/postgresql.jar b/build/libraries/postgresql.jar
new file mode 100644
index 0000000..21bc998
--- /dev/null
+++ b/build/libraries/postgresql.jar
Binary files differ