Merge branch 'leveldb' - switched from SQLite to leveldb database.

This commit is contained in:
samo_lego 2020-04-12 16:27:50 +02:00
commit 30e261c767
11 changed files with 75 additions and 124 deletions

View File

@ -18,7 +18,7 @@ Requires Fabric API.
## License ## License
Libraries that the project is using: Libraries that the project is using:
- `Argon2 (LGPLv3)` https://github.com/phxql/argon2-jvm - `Argon2 (LGPLv3)` https://github.com/phxql/argon2-jvm
- `JDBC (Apache 2)` https://github.com/xerial/sqlite-jdbc - `leveldb (BSD-3-Clause)` https://github.com/google/leveldb
- `JNA (Apache 2 || LGPLv3)` https://github.com/java-native-access/jna - `JNA (Apache 2 || LGPLv3)` https://github.com/java-native-access/jna
This project is licensed under the `MIT` license. This project is licensed under the `MIT` license.

View File

@ -26,9 +26,12 @@ dependencies {
implementation 'de.mkammerer:argon2-jvm:2.6' implementation 'de.mkammerer:argon2-jvm:2.6'
include 'de.mkammerer:argon2-jvm:2.6' include 'de.mkammerer:argon2-jvm:2.6'
// JDBC SQLite Driver // leveldb
implementation group:'org.xerial', name:'sqlite-jdbc', version:'3.8.11.2' implementation group: 'org.iq80.leveldb', name: 'leveldb', version: '0.12'
include group:'org.xerial', name:'sqlite-jdbc', version:'3.8.11.2' implementation group: 'org.iq80.leveldb', name: 'leveldb-api', version: '0.12'
include group: 'org.iq80.leveldb', name: 'leveldb', version: '0.12'
include group: 'org.iq80.leveldb', name: 'leveldb-api', version: '0.12'
// JNA lib // JNA lib
include 'net.java.dev.jna:jna:5.5.0' include 'net.java.dev.jna:jna:5.5.0'

View File

@ -52,15 +52,14 @@ public class SimpleAuth implements DedicatedServerModInitializer {
LOGGER.info("[SimpleAuth] This mod wouldn't exist without the awesome Fabric Community. TYSM guys!"); LOGGER.info("[SimpleAuth] This mod wouldn't exist without the awesome Fabric Community. TYSM guys!");
// Creating data directory (database and config files are stored there) // Creating data directory (database and config files are stored there)
File file = new File(gameDirectory + "/mods/SimpleAuth"); File file = new File(gameDirectory + "/mods/SimpleAuth/levelDBStore");
if (!file.exists() && !file.mkdir()) if (!file.exists() && !file.mkdir())
LOGGER.error("[SimpleAuth] Error creating directory!"); LOGGER.error("[SimpleAuth] Error creating directory!");
// Loading config // Loading config
config = AuthConfig.load(new File(gameDirectory + "/mods/SimpleAuth/config.json")); config = AuthConfig.load(new File(gameDirectory + "/mods/SimpleAuth/config.json"));
// Connecting to db // Connecting to db
db.openConnection(); db.openConnection();
// Making a table in the database
db.makeTable();
// Registering the commands // Registering the commands
CommandRegistry.INSTANCE.register(false, dispatcher -> { CommandRegistry.INSTANCE.register(false, dispatcher -> {

View File

@ -112,7 +112,6 @@ public class AuthCommand {
SimpleAuth.db.update( SimpleAuth.db.update(
uuid, uuid,
username,
AuthHelper.hashPass(pass.toCharArray()) AuthHelper.hashPass(pass.toCharArray())
); );
if(sender != null) if(sender != null)
@ -124,7 +123,7 @@ public class AuthCommand {
} }
private static int removeAccount(ServerCommandSource source, String uuid, String username) { private static int removeAccount(ServerCommandSource source, String uuid, String username) {
Entity sender = source.getEntity(); Entity sender = source.getEntity();
SimpleAuth.db.delete(uuid, username); SimpleAuth.db.delete(uuid);
// TODO -> Kick player that was unregistered? // TODO -> Kick player that was unregistered?

View File

@ -54,7 +54,7 @@ public class ChangepwCommand {
player.sendMessage(cannotChangePassword); player.sendMessage(cannotChangePassword);
return 0; return 0;
} }
else if (AuthHelper.checkPass(player.getUuidAsString(), oldPass.toCharArray())) { else if (AuthHelper.checkPass(player.getUuidAsString(), oldPass.toCharArray()) == 1) {
if(newPass.length() < SimpleAuth.config.main.minPasswordChars) { if(newPass.length() < SimpleAuth.config.main.minPasswordChars) {
player.sendMessage(new LiteralText( player.sendMessage(new LiteralText(
String.format(SimpleAuth.config.lang.minPasswordChars, SimpleAuth.config.main.minPasswordChars) String.format(SimpleAuth.config.lang.minPasswordChars, SimpleAuth.config.main.minPasswordChars)
@ -69,7 +69,6 @@ public class ChangepwCommand {
} }
SimpleAuth.db.update( SimpleAuth.db.update(
player.getUuidAsString(), player.getUuidAsString(),
null,
AuthHelper.hashPass(newPass.toCharArray()) AuthHelper.hashPass(newPass.toCharArray())
); );
player.sendMessage(passwordUpdated); player.sendMessage(passwordUpdated);

View File

@ -18,6 +18,7 @@ public class LoginCommand {
private static Text enterPassword = new LiteralText(SimpleAuth.config.lang.enterPassword); private static Text enterPassword = new LiteralText(SimpleAuth.config.lang.enterPassword);
private static Text wrongPassword = new LiteralText(SimpleAuth.config.lang.wrongPassword); private static Text wrongPassword = new LiteralText(SimpleAuth.config.lang.wrongPassword);
private static Text alreadyAuthenticated = new LiteralText(SimpleAuth.config.lang.alreadyAuthenticated); private static Text alreadyAuthenticated = new LiteralText(SimpleAuth.config.lang.alreadyAuthenticated);
private static Text notRegistered = new LiteralText(SimpleAuth.config.lang.notRegistered);
private static Text loginTriesExceeded = new LiteralText(SimpleAuth.config.lang.loginTriesExceeded); private static Text loginTriesExceeded = new LiteralText(SimpleAuth.config.lang.loginTriesExceeded);
private static Text successfullyAuthenticated = new LiteralText(SimpleAuth.config.lang.successfullyAuthenticated); private static Text successfullyAuthenticated = new LiteralText(SimpleAuth.config.lang.successfullyAuthenticated);
private static int maxLoginTries = SimpleAuth.config.main.maxLoginTries; private static int maxLoginTries = SimpleAuth.config.main.maxLoginTries;
@ -47,15 +48,13 @@ public class LoginCommand {
player.networkHandler.disconnect(loginTriesExceeded); player.networkHandler.disconnect(loginTriesExceeded);
return 0; return 0;
} }
else if(SimpleAuth.config.main.enableGlobalPassword) { else if (AuthHelper.checkPass(player.getUuidAsString(), pass.toCharArray()) == 1) {
if (AuthHelper.checkPass(null, pass.toCharArray())) {
SimpleAuth.authenticatePlayer(player, successfullyAuthenticated); SimpleAuth.authenticatePlayer(player, successfullyAuthenticated);
return 1; return 1;
} }
} else if(AuthHelper.checkPass(player.getUuidAsString(), pass.toCharArray()) == -1) {
else if (AuthHelper.checkPass(player.getUuidAsString(), pass.toCharArray())) { player.sendMessage(notRegistered);
SimpleAuth.authenticatePlayer(player, successfullyAuthenticated); return 0;
return 1;
} }
// Kicking the player out // Kicking the player out
else if(maxLoginTries == 1) { else if(maxLoginTries == 1) {

View File

@ -62,7 +62,7 @@ public class RegisterCommand {
return 0; return 0;
} }
String hash = AuthHelper.hashPass(pass1.toCharArray()); String hash = AuthHelper.hashPass(pass1.toCharArray());
if (SimpleAuth.db.registerUser(player.getUuidAsString(), source.getName(), hash)) { if (SimpleAuth.db.registerUser(player.getUuidAsString(), hash)) {
SimpleAuth.authenticatePlayer(player, registerSuccess); SimpleAuth.authenticatePlayer(player, registerSuccess);
return 1; return 1;
} }

View File

@ -45,9 +45,9 @@ public class UnregisterCommand {
player.sendMessage(cannotUnregister); player.sendMessage(cannotUnregister);
return 0; return 0;
} }
else if (AuthHelper.checkPass(player.getUuidAsString(), pass.toCharArray())) { else if (AuthHelper.checkPass(player.getUuidAsString(), pass.toCharArray()) == 1) {
SimpleAuth.deauthenticatePlayer(player); SimpleAuth.deauthenticatePlayer(player);
SimpleAuth.db.delete(player.getUuidAsString(), null); SimpleAuth.db.delete(player.getUuidAsString());
player.sendMessage(accountDeleted); player.sendMessage(accountDeleted);
return 1; return 1;
} }

View File

@ -2,146 +2,93 @@ package org.samo_lego.simpleauth.database;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.iq80.leveldb.DB;
import org.iq80.leveldb.DBException;
import org.iq80.leveldb.Options;
import org.samo_lego.simpleauth.SimpleAuth; import org.samo_lego.simpleauth.SimpleAuth;
import java.sql.*; import java.io.File;
import java.io.IOException;
/** import static org.iq80.leveldb.impl.Iq80DBFactory.bytes;
* Thanks to import static org.iq80.leveldb.impl.Iq80DBFactory.factory;
* @author sqlitetutorial.net
*/
public class SimpleAuthDatabase { public class SimpleAuthDatabase {
private static final Logger LOGGER = LogManager.getLogger(); private static final Logger LOGGER = LogManager.getLogger();
private DB levelDBStore;
// Connects to the DB // Connects to the DB
private Connection conn;
public void openConnection() { public void openConnection() {
// SQLite connection string
String url = "jdbc:sqlite:" + SimpleAuth.gameDirectory + "/mods/SimpleAuth/players.db";
try { try {
conn = DriverManager.getConnection(url);
} catch (SQLException e) { Options options = new Options();
levelDBStore = factory.open(new File(SimpleAuth.gameDirectory + "/mods/SimpleAuth/levelDBStore"), options);
} catch (Error | IOException e) {
LOGGER.error("[SimpleAuth] " + e.getMessage()); LOGGER.error("[SimpleAuth] " + e.getMessage());
} }
} }
// Closing connection // Closing connection
public void close() { public void close() {
if (conn != null) { if (levelDBStore != null) {
try { try {
conn.close(); levelDBStore.close();
LOGGER.info("[SimpleAuth] Database connection closed successfully."); LOGGER.info("[SimpleAuth] Database connection closed successfully.");
} catch (SQLException e) { } catch (Error | IOException e) {
LOGGER.info("[SimpleAuth] Error: " + e.getMessage()); LOGGER.info("[SimpleAuth] Error: " + e.getMessage());
} }
} }
} }
// If the mod runs for the first time, we need to create the DB table
public void makeTable() {
try {
// Creating database table if it doesn't exist yet
String sql = "CREATE TABLE IF NOT EXISTS users (\n" +
" `UserID` INTEGER PRIMARY KEY AUTOINCREMENT,\n" +
" `UUID` BINARY(16) NOT NULL,\n" +
" `Username` VARCHAR(16) NOT NULL,\n" +
" `Password` VARCHAR(64) NOT NULL,\n" +
" UNIQUE (`UUID`)\n" +
" UNIQUE (`Username`)\n" +
");";
Statement stmt = conn.createStatement();
stmt.execute(sql);
} catch (SQLException e) {
LOGGER.error("[SimpleAuth] Error: " + e.getMessage());
}
}
// When player registers, we insert the data into DB // When player registers, we insert the data into DB
public boolean registerUser(String uuid, String username, String password) { public boolean registerUser(String uuid, String password) {
String sql = "INSERT INTO users(uuid, username, password) VALUES(?,?,?)";
String sqlCheck = "SELECT UUID "
+ "FROM users WHERE UUID = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql);
PreparedStatement pstmtCheck = conn.prepareStatement(sqlCheck)) {
pstmtCheck.setString(1, uuid);
ResultSet rs = pstmtCheck.executeQuery();
// Getting the password
//String dbUuid = null;
try { try {
rs.getString("UUID"); if(!this.isRegistered(uuid)) {
return false; levelDBStore.put(bytes("UUID:" + uuid), bytes("password:" + password));
} catch(SQLException ignored) {
// User is not registered
} finally {
pstmt.setString(1, uuid);
pstmt.setString(2, username);
pstmt.setString(3, password);
pstmt.executeUpdate();
}
return true; return true;
} catch (SQLException e) { }
return false;
} catch (Error e) {
LOGGER.error("[SimpleAuth] Register error: " + e.getMessage()); LOGGER.error("[SimpleAuth] Register error: " + e.getMessage());
return false; return false;
} }
} }
// Checks if user is registered
private boolean isRegistered(String uuid) {
try {
return levelDBStore.get(bytes("UUID:" + uuid)) != null;
} catch (DBException e) {
LOGGER.error("[SimpleAuth] " + e.getMessage());
}
return false;
}
// Deletes row containing the username provided // Deletes row containing the username provided
public void delete(String uuid, String username) { public void delete(String uuid) {
String sql = "DELETE FROM users WHERE uuid = ? OR username = ?"; try {
levelDBStore.delete(bytes("UUID:" + uuid));
try (PreparedStatement pstmt = conn.prepareStatement(sql)) { } catch (Error e) {
// set the corresponding param
pstmt.setString(1, uuid);
pstmt.setString(2, username);
// execute the delete statement
pstmt.executeUpdate();
} catch (SQLException e) {
LOGGER.error("[SimpleAuth] " + e.getMessage()); LOGGER.error("[SimpleAuth] " + e.getMessage());
} }
} }
// Updates the password of the user // Updates the password of the user
public void update(String uuid, String username, String pass) { public void update(String uuid, String password) {
String sql = "UPDATE users SET password = ? " try {
+ "WHERE uuid = ? OR username = ?"; levelDBStore.put(bytes("UUID:" + uuid),bytes("password:" + password));
} catch (Error e) {
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
// set the corresponding param
pstmt.setString(1, pass);
pstmt.setString(2, uuid);
pstmt.setString(3, username);
// update
pstmt.executeUpdate();
} catch (SQLException e) {
LOGGER.error("[SimpleAuth] " + e.getMessage()); LOGGER.error("[SimpleAuth] " + e.getMessage());
} }
} }
// Gets the hashed password from DB // Gets the hashed password from DB
public String getPassword(String uuid){ public String getPassword(String uuid){
String sql = "SELECT UUID, Password " try {
+ "FROM users WHERE UUID = ?"; if(this.isRegistered(uuid)) // Gets password from db and removes "password:" prefix from it
String pass = null; return new String(levelDBStore.get(bytes("UUID:" + uuid))).substring(9);
} catch (Error e) {
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
// Setting statement
pstmt.setString(1, uuid);
ResultSet rs = pstmt.executeQuery();
// Getting the password
pass = rs.getString("Password");
} catch (SQLException e) {
LOGGER.error("[SimpleAuth] Error getting password: " + e.getMessage()); LOGGER.error("[SimpleAuth] Error getting password: " + e.getMessage());
} }
return pass; return "";
} }
} }

View File

@ -95,6 +95,7 @@ public class AuthConfig {
public String successfulPortalRescue = "§aYou were rescued from nether portal successfully!"; public String successfulPortalRescue = "§aYou were rescued from nether portal successfully!";
public String maxPasswordChars = "§6Password can be at most %d characters long!"; public String maxPasswordChars = "§6Password can be at most %d characters long!";
public String minPasswordChars = "§6Password needs to be at least %d characters long!"; public String minPasswordChars = "§6Password needs to be at least %d characters long!";
public String notRegistered = "§6This account is not yet registered! Type `/register` first";
} }
private static final Logger LOGGER = LogManager.getLogger(); private static final Logger LOGGER = LogManager.getLogger();
private static final Gson gson = new GsonBuilder() private static final Gson gson = new GsonBuilder()

View File

@ -12,15 +12,17 @@ public class AuthHelper {
// Creating the instance // Creating the instance
private static Argon2 argon2 = Argon2Factory.create(); private static Argon2 argon2 = Argon2Factory.create();
public static boolean checkPass(String uuid, char[] pass) { // Returns 1 if password is correct, 0 if not
// and -1 if user is not registered yet
public static int checkPass(String uuid, char[] pass) {
if(SimpleAuth.config.main.enableGlobalPassword) { if(SimpleAuth.config.main.enableGlobalPassword) {
// We have global password enabled // We have global password enabled
try { try {
return argon2.verify(SimpleAuth.config.main.globalPassword, pass); return argon2.verify(SimpleAuth.config.main.globalPassword, pass) ? 1 : 0;
} }
catch (Error e) { catch (Error e) {
LOGGER.error("[SimpleAuth] Argon2 error: " + e); LOGGER.error("[SimpleAuth] Argon2 error: " + e);
return false; return 0;
} finally { } finally {
// Wipe confidential data // Wipe confidential data
argon2.wipeArray(pass); argon2.wipeArray(pass);
@ -30,11 +32,13 @@ public class AuthHelper {
try { try {
// Hashed password from DB // Hashed password from DB
String hashed = SimpleAuth.db.getPassword(uuid); String hashed = SimpleAuth.db.getPassword(uuid);
if(hashed.equals(""))
return -1; // User is not yet registered
// Verify password // Verify password
return argon2.verify(hashed, pass); return argon2.verify(hashed, pass) ? 1 : 0;
} catch (Error e) { } catch (Error e) {
LOGGER.error("[SimpleAuth] error: " + e); LOGGER.error("[SimpleAuth] error: " + e);
return false; return 0;
} finally { } finally {
// Wipe confidential data // Wipe confidential data
argon2.wipeArray(pass); argon2.wipeArray(pass);