aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPetr Ilin <hevav@hevav.dev>2023-01-16 07:26:09 +0300
committerPetr Ilin <hevav@hevav.dev>2023-01-16 07:28:15 +0300
commit0e7e93353073dc36bf550de7cb352dbdd5b447de (patch)
tree8c0b3b7d9ff867a3d353a7830343351d2824b484
parenta635de80d46eb3f967c30091283e95819d404b84 (diff)
downloadLimboAuth-0e7e93353073dc36bf550de7cb352dbdd5b447de.tar.gz
LimboAuth-0e7e93353073dc36bf550de7cb352dbdd5b447de.tar.bz2
LimboAuth-0e7e93353073dc36bf550de7cb352dbdd5b447de.zip
1.1.1 release: Client Mod support, RegisteredPlayer refactor
-rw-r--r--build.gradle4
-rw-r--r--src/main/java/net/elytrium/limboauth/LimboAuth.java30
-rw-r--r--src/main/java/net/elytrium/limboauth/Settings.java65
-rw-r--r--src/main/java/net/elytrium/limboauth/command/ChangePasswordCommand.java2
-rw-r--r--src/main/java/net/elytrium/limboauth/command/ForceChangePasswordCommand.java3
-rw-r--r--src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java118
-rw-r--r--src/main/java/net/elytrium/limboauth/model/RegisteredPlayer.java108
7 files changed, 272 insertions, 58 deletions
diff --git a/build.gradle b/build.gradle
index 04712aa..2b8f709 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,7 +10,7 @@ plugins {
}
setGroup("net.elytrium")
-setVersion("1.1.0")
+setVersion("1.1.1")
java {
setSourceCompatibility(JavaVersion.VERSION_11)
@@ -61,6 +61,8 @@ dependencies {
implementation("com.j256.ormlite:ormlite-jdbc:6.1")
implementation("de.mkammerer:argon2-jvm-nolibs:2.11")
+ implementation("io.whitfin:siphash:2.0.0")
+
implementation("org.bstats:bstats-velocity:$bstatsVersion")
compileOnly("com.github.spotbugs:spotbugs-annotations:$spotbugsVersion")
diff --git a/src/main/java/net/elytrium/limboauth/LimboAuth.java b/src/main/java/net/elytrium/limboauth/LimboAuth.java
index 5a422e4..1ca4286 100644
--- a/src/main/java/net/elytrium/limboauth/LimboAuth.java
+++ b/src/main/java/net/elytrium/limboauth/LimboAuth.java
@@ -17,6 +17,8 @@
package net.elytrium.limboauth;
+import com.google.common.primitives.Bytes;
+import com.google.common.primitives.Longs;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@@ -43,6 +45,7 @@ import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.scheduler.ScheduledTask;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import io.whitfin.siphash.SipHasher;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
@@ -554,18 +557,7 @@ public class LimboAuth {
}
if (nicknameRegisteredPlayer == null && registeredPlayer == null && Settings.IMP.MAIN.SAVE_PREMIUM_ACCOUNTS) {
- registeredPlayer = new RegisteredPlayer(
- nickname,
- nickname.toLowerCase(Locale.ROOT),
- "",
- player.getRemoteAddress().getAddress().getHostAddress(),
- "",
- System.currentTimeMillis(),
- player.getUniqueId().toString(),
- player.getUniqueId().toString(),
- player.getRemoteAddress().getAddress().getHostAddress(),
- System.currentTimeMillis()
- );
+ registeredPlayer = new RegisteredPlayer(player).setPremiumUuid(player.getUniqueId());
try {
this.playerDao.create(registeredPlayer);
@@ -645,11 +637,23 @@ public class LimboAuth {
}
public void updateLoginData(Player player) throws SQLException {
+ String lowercaseNickname = player.getUsername().toLowerCase(Locale.ROOT);
UpdateBuilder<RegisteredPlayer, String> updateBuilder = this.playerDao.updateBuilder();
- updateBuilder.where().eq(RegisteredPlayer.LOWERCASE_NICKNAME_FIELD, player.getUsername().toLowerCase(Locale.ROOT));
+ 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();
+
+ if (Settings.IMP.MAIN.MOD.ENABLED) {
+ byte[] lowercaseNicknameSerialized = lowercaseNickname.getBytes(StandardCharsets.UTF_8);
+ long issueTime = System.currentTimeMillis();
+ long hash = SipHasher.init(Settings.IMP.MAIN.MOD.VERIFY_KEY)
+ .update(lowercaseNicknameSerialized)
+ .update(Longs.toByteArray(issueTime))
+ .digest();
+
+ player.sendPluginMessage(AuthSessionHandler.MOD_CHANNEL, Bytes.concat(Longs.toByteArray(issueTime), Longs.toByteArray(hash)));
+ }
}
private boolean validateScheme(JsonElement jsonElement, List<String> scheme) {
diff --git a/src/main/java/net/elytrium/limboauth/Settings.java b/src/main/java/net/elytrium/limboauth/Settings.java
index 9a61af3..15e7db0 100644
--- a/src/main/java/net/elytrium/limboauth/Settings.java
+++ b/src/main/java/net/elytrium/limboauth/Settings.java
@@ -17,7 +17,13 @@
package net.elytrium.limboauth;
+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 net.elytrium.commons.config.ConfigSerializer;
import net.elytrium.commons.config.YamlConfig;
import net.elytrium.commons.kyori.serialization.Serializers;
import net.elytrium.limboapi.api.chunk.Dimension;
@@ -209,6 +215,26 @@ public class Settings extends YamlConfig {
public boolean DISABLE_REGISTRATIONS = false;
@Create
+ public Settings.MAIN.MOD MOD;
+
+ @Comment({
+ "Implement the automatic login using the plugin, the LimboAuth client mod and optionally using a custom launcher",
+ "See https://github.com/Elytrium/LimboAuth-ClientMod"
+ })
+ public static class MOD {
+
+ public boolean ENABLED = true;
+
+ @Comment("Should the plugin forbid logging in without a mod")
+ public boolean LOGIN_ONLY_BY_MOD = false;
+
+ @Comment("The key must be the same in the plugin config and in the server hash issuer, if you use it")
+ @CustomSerializer(serializerClass = MD5KeySerializer.class)
+ public byte[] VERIFY_KEY = null;
+
+ }
+
+ @Create
public Settings.MAIN.WORLD_COORDS WORLD_COORDS;
public static class WORLD_COORDS {
@@ -368,6 +394,8 @@ public class Settings extends YamlConfig {
public String TOTP_RECOVERY = "{PRFX} &aYour recovery codes &7(Click to copy)&a: &6{0}";
public String DESTROY_SESSION_SUCCESSFUL = "{PRFX} &eYour session is now destroyed, you'll need to log in again after reconnecting.";
+
+ public String MOD_SESSION_EXPIRED = "{PRFX} Your session has expired, log in again.";
}
@Create
@@ -399,4 +427,41 @@ public class Settings extends YamlConfig {
public String DATABASE = "limboauth";
public String CONNECTION_PARAMETERS = "?autoReconnect=true&initialTimeout=1&useSSL=false";
}
+
+ public static class MD5KeySerializer extends ConfigSerializer<byte[], String> {
+
+ private final MessageDigest md5;
+ private final Random random;
+ private String originalValue;
+
+ public MD5KeySerializer() throws NoSuchAlgorithmException {
+ super(byte[].class, String.class);
+ this.md5 = MessageDigest.getInstance("MD5");
+ this.random = new SecureRandom();
+ }
+
+ @Override
+ public String serialize(byte[] from) {
+ if (this.originalValue == null || this.originalValue.isEmpty()) {
+ this.originalValue = generateRandomString(24);
+ }
+
+ return this.originalValue;
+ }
+
+ @Override
+ public byte[] deserialize(String from) {
+ this.originalValue = from;
+ return this.md5.digest(from.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private String generateRandomString(int length) {
+ String chars = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890";
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ builder.append(chars.charAt(this.random.nextInt(chars.length())));
+ }
+ return builder.toString();
+ }
+ }
}
diff --git a/src/main/java/net/elytrium/limboauth/command/ChangePasswordCommand.java b/src/main/java/net/elytrium/limboauth/command/ChangePasswordCommand.java
index f22a96f..2fea6f5 100644
--- a/src/main/java/net/elytrium/limboauth/command/ChangePasswordCommand.java
+++ b/src/main/java/net/elytrium/limboauth/command/ChangePasswordCommand.java
@@ -91,7 +91,7 @@ public class ChangePasswordCommand implements SimpleCommand {
try {
UpdateBuilder<RegisteredPlayer, String> updateBuilder = this.playerDao.updateBuilder();
updateBuilder.where().eq(RegisteredPlayer.NICKNAME_FIELD, username);
- updateBuilder.updateColumnValue(RegisteredPlayer.HASH_FIELD, AuthSessionHandler.genHash(needOldPass ? args[1] : args[0]));
+ updateBuilder.updateColumnValue(RegisteredPlayer.HASH_FIELD, RegisteredPlayer.genHash(needOldPass ? args[1] : args[0]));
updateBuilder.update();
source.sendMessage(this.successful);
diff --git a/src/main/java/net/elytrium/limboauth/command/ForceChangePasswordCommand.java b/src/main/java/net/elytrium/limboauth/command/ForceChangePasswordCommand.java
index f497c3c..67d9c67 100644
--- a/src/main/java/net/elytrium/limboauth/command/ForceChangePasswordCommand.java
+++ b/src/main/java/net/elytrium/limboauth/command/ForceChangePasswordCommand.java
@@ -30,7 +30,6 @@ 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.handler.AuthSessionHandler;
import net.elytrium.limboauth.model.RegisteredPlayer;
import net.elytrium.limboauth.model.SQLRuntimeException;
import net.kyori.adventure.text.Component;
@@ -73,7 +72,7 @@ public class ForceChangePasswordCommand implements SimpleCommand {
try {
UpdateBuilder<RegisteredPlayer, String> updateBuilder = this.playerDao.updateBuilder();
updateBuilder.where().eq(RegisteredPlayer.LOWERCASE_NICKNAME_FIELD, nickname.toLowerCase(Locale.ROOT));
- updateBuilder.updateColumnValue(RegisteredPlayer.HASH_FIELD, AuthSessionHandler.genHash(newPassword));
+ updateBuilder.updateColumnValue(RegisteredPlayer.HASH_FIELD, RegisteredPlayer.genHash(newPassword));
updateBuilder.update();
this.server.getPlayer(nickname)
diff --git a/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java b/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java
index ebdd6f4..e9de364 100644
--- a/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java
+++ b/src/main/java/net/elytrium/limboauth/handler/AuthSessionHandler.java
@@ -18,12 +18,18 @@
package net.elytrium.limboauth.handler;
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.api.proxy.messages.ChannelIdentifier;
+import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
+import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import dev.samstevens.totp.code.CodeVerifier;
import dev.samstevens.totp.code.DefaultCodeGenerator;
import dev.samstevens.totp.code.DefaultCodeVerifier;
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;
@@ -55,6 +61,9 @@ public class AuthSessionHandler implements LimboSessionHandler {
private 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();
+ // Architectury API appends /541f59e4256a337ea252bc482a009d46 to the channel name, that is a UUID.nameUUIDFromBytes from the TokenMessage class name
+ public static final ChannelIdentifier MOD_CHANNEL = MinecraftChannelIdentifier.create("limboauth", "mod/541f59e4256a337ea252bc482a009d46");
+ public static final String MOD_CHANNEL_STRING = MOD_CHANNEL.getId();
private static BossBar.Color bossbarColor;
private static BossBar.Overlay bossbarOverlay;
@@ -81,6 +90,7 @@ public class AuthSessionHandler implements LimboSessionHandler {
private static Component registerPasswordTooShort;
private static Component registerPasswordUnsafe;
private static Component loginSuccessful;
+ private static Component sessionExpired;
@Nullable
private static Title loginSuccessfulTitle;
@Nullable
@@ -97,6 +107,7 @@ public class AuthSessionHandler implements LimboSessionHandler {
bossbarColor,
bossbarOverlay
);
+ private final boolean loginOnlyByMod = Settings.IMP.MAIN.MOD.ENABLED && Settings.IMP.MAIN.MOD.LOGIN_ONLY_BY_MOD;
@Nullable
private RegisteredPlayer playerInfo;
@@ -104,10 +115,10 @@ public class AuthSessionHandler implements LimboSessionHandler {
private ScheduledFuture<?> authMainTask;
private LimboPlayer player;
- private String ip;
private int attempts = Settings.IMP.MAIN.LOGIN_ATTEMPTS;
private boolean totpState;
private String tempPassword;
+ private boolean tokenReceived;
public AuthSessionHandler(Dao<RegisteredPlayer, String> playerDao, Player proxyPlayer, LimboAuth plugin, @Nullable RegisteredPlayer playerInfo) {
this.playerDao = playerDao;
@@ -119,7 +130,6 @@ public class AuthSessionHandler implements LimboSessionHandler {
@Override
public void onSpawn(Limbo server, LimboPlayer player) {
this.player = player;
- this.ip = this.proxyPlayer.getRemoteAddress().getAddress().getHostAddress();
if (Settings.IMP.MAIN.DISABLE_FALLING) {
this.player.disableFalling();
@@ -131,7 +141,8 @@ public class AuthSessionHandler implements LimboSessionHandler {
if (this.playerInfo == null) {
try {
- List<RegisteredPlayer> alreadyRegistered = this.playerDao.queryForEq(RegisteredPlayer.IP_FIELD, this.ip);
+ String ip = this.proxyPlayer.getRemoteAddress().getAddress().getHostAddress();
+ List<RegisteredPlayer> alreadyRegistered = this.playerDao.queryForEq(RegisteredPlayer.IP_FIELD, ip);
if (alreadyRegistered != null) {
int sizeOfValidRegistrations = alreadyRegistered.size();
if (Settings.IMP.MAIN.IP_LIMIT_VALID_TIME > 0) {
@@ -162,7 +173,7 @@ public class AuthSessionHandler implements LimboSessionHandler {
}
}
- boolean bossBarEnabled = Settings.IMP.MAIN.ENABLE_BOSSBAR;
+ 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(() -> {
@@ -182,11 +193,17 @@ public class AuthSessionHandler implements LimboSessionHandler {
this.proxyPlayer.showBossBar(this.bossBar);
}
- this.sendMessage(true);
+ if (!this.loginOnlyByMod) {
+ this.sendMessage(true);
+ }
}
@Override
public void onChat(String message) {
+ if (this.loginOnlyByMod) {
+ return;
+ }
+
String[] args = message.split(" ");
if (args.length != 0 && this.checkArgsLength(args.length)) {
Command command = Command.parse(args[0]);
@@ -194,19 +211,7 @@ public class AuthSessionHandler implements LimboSessionHandler {
String password = args[1];
if (this.checkPasswordsRepeat(args) && this.checkPasswordLength(password) && this.checkPasswordStrength(password)) {
this.saveTempPassword(password);
- String username = this.proxyPlayer.getUsername();
- RegisteredPlayer registeredPlayer = new RegisteredPlayer(
- username,
- username.toLowerCase(Locale.ROOT),
- genHash(password),
- this.ip,
- "",
- System.currentTimeMillis(),
- this.proxyPlayer.getUniqueId().toString(),
- "",
- this.proxyPlayer.getRemoteAddress().getAddress().getHostAddress(),
- System.currentTimeMillis()
- );
+ RegisteredPlayer registeredPlayer = new RegisteredPlayer(this.proxyPlayer);
try {
this.playerDao.create(registeredPlayer);
@@ -263,6 +268,64 @@ public class AuthSessionHandler implements LimboSessionHandler {
this.sendMessage(false);
}
+ @Override
+ public void onGeneric(Object packet) {
+ if (Settings.IMP.MAIN.MOD.ENABLED && packet instanceof PluginMessage) {
+ PluginMessage pluginMessage = (PluginMessage) 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(MOD_CHANNEL, new byte[0]);
+ }
+ } else if (channel.equals(MOD_CHANNEL_STRING)) {
+ 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();
+ }
+ }
+ }
+
private void checkBruteforceAttempts() {
this.plugin.incrementBruteforceAttempts(this.proxyPlayer.getRemoteAddress().getAddress());
if (this.plugin.getBruteforceAttempts(this.proxyPlayer.getRemoteAddress().getAddress()) >= Settings.IMP.MAIN.BRUTEFORCE_MAX_ATTEMPTS) {
@@ -355,10 +418,6 @@ public class AuthSessionHandler implements LimboSessionHandler {
}
private void finishAuth(TaskEvent event) {
- if (Settings.IMP.MAIN.CRACKED_TITLE_SETTINGS.CLEAR_AFTER_LOGIN) {
- this.proxyPlayer.clearTitle();
- }
-
if (event.getResult() == TaskEvent.Result.CANCEL) {
this.proxyPlayer.disconnect(event.getReason());
return;
@@ -366,6 +425,14 @@ public class AuthSessionHandler implements LimboSessionHandler {
return;
}
+ this.finishAuth();
+ }
+
+ private void finishAuth() {
+ if (Settings.IMP.MAIN.CRACKED_TITLE_SETTINGS.CLEAR_AFTER_LOGIN) {
+ this.proxyPlayer.clearTitle();
+ }
+
try {
this.plugin.updateLoginData(this.proxyPlayer);
} catch (SQLException e) {
@@ -438,6 +505,7 @@ public class AuthSessionHandler implements LimboSessionHandler {
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 {
@@ -461,7 +529,7 @@ public class AuthSessionHandler implements LimboSessionHandler {
if (!isCorrect && migrationHash != null) {
isCorrect = migrationHash.checkPassword(hash, password);
if (isCorrect) {
- player.setHash(genHash(password));
+ player.setPassword(password);
try {
playerDao.update(player);
} catch (SQLException e) {
@@ -491,6 +559,10 @@ public class AuthSessionHandler implements LimboSessionHandler {
}
}
+ /**
+ * 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());
}
diff --git a/src/main/java/net/elytrium/limboauth/model/RegisteredPlayer.java b/src/main/java/net/elytrium/limboauth/model/RegisteredPlayer.java
index 052b9f0..0b0b85c 100644
--- a/src/main/java/net/elytrium/limboauth/model/RegisteredPlayer.java
+++ b/src/main/java/net/elytrium/limboauth/model/RegisteredPlayer.java
@@ -17,8 +17,14 @@
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 {
@@ -33,6 +39,9 @@ public class RegisteredPlayer {
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;
@@ -41,7 +50,7 @@ public class RegisteredPlayer {
private String lowercaseNickname;
@DatabaseField(canBeNull = false, columnName = HASH_FIELD)
- private String hash;
+ private String hash = "";
@DatabaseField(columnName = IP_FIELD)
private String ip;
@@ -50,20 +59,24 @@ public class RegisteredPlayer {
private String totpToken;
@DatabaseField(columnName = REG_DATE_FIELD)
- private Long regDate;
+ private Long regDate = System.currentTimeMillis();
@DatabaseField(columnName = UUID_FIELD)
- private String uuid;
+ private String uuid = "";
@DatabaseField(columnName = RegisteredPlayer.PREMIUM_UUID_FIELD)
- private String premiumUuid;
+ private String premiumUuid = "";
@DatabaseField(columnName = LOGIN_IP_FIELD)
private String loginIp;
@DatabaseField(columnName = LOGIN_DATE_FIELD)
- private Long loginDate;
+ private Long loginDate = System.currentTimeMillis();
+
+ @DatabaseField(columnName = TOKEN_ISSUED_AT_FIELD)
+ private Long tokenIssuedAt = System.currentTimeMillis();
+ @Deprecated
public RegisteredPlayer(String nickname, String lowercaseNickname,
String hash, String ip, String totpToken, Long regDate, String uuid, String premiumUuid, String loginIp, Long loginDate) {
this.nickname = nickname;
@@ -78,68 +91,113 @@ public class RegisteredPlayer {
this.loginDate = loginDate;
}
+ public RegisteredPlayer(Player player) {
+ this(player.getUsername(), player.getUniqueId(), player.getRemoteAddress());
+ }
+
+ public RegisteredPlayer(String nickname, UUID uuid, InetSocketAddress ip) {
+ this(nickname, uuid.toString(), ip.getAddress().getHostAddress());
+ }
+
+ public RegisteredPlayer(String nickname, String uuid, String ip) {
+ this.nickname = nickname;
+ this.lowercaseNickname = nickname.toLowerCase(Locale.ROOT);
+ this.uuid = uuid;
+ this.ip = ip;
+ this.loginIp = ip;
+ }
+
public RegisteredPlayer() {
}
- public void setNickname(String nickname) {
+ public static String genHash(String password) {
+ return HASHER.hashToString(Settings.IMP.MAIN.BCRYPT_COST, password.toCharArray());
+ }
+
+ public RegisteredPlayer setNickname(String nickname) {
this.nickname = nickname;
+ this.lowercaseNickname = nickname.toLowerCase(Locale.ROOT);
+
+ return this;
}
public String getNickname() {
return this.nickname == null ? this.lowercaseNickname : this.nickname;
}
- public void setLowercaseNickname(String lowercaseNickname) {
- this.lowercaseNickname = lowercaseNickname;
- }
-
public String getLowercaseNickname() {
return this.lowercaseNickname;
}
- public void setHash(String hash) {
+ public RegisteredPlayer setPassword(String password) {
+ this.hash = genHash(password);
+ this.tokenIssuedAt = System.currentTimeMillis();
+
+ return this;
+ }
+
+ public RegisteredPlayer setHash(String hash) {
this.hash = hash;
+ this.tokenIssuedAt = System.currentTimeMillis();
+
+ return this;
}
public String getHash() {
return this.hash == null ? "" : this.hash;
}
- public void setIP(String ip) {
+ public RegisteredPlayer setIP(String ip) {
this.ip = ip;
+
+ return this;
}
public String getIP() {
return this.ip == null ? "" : this.ip;
}
- public void setTotpToken(String totpToken) {
+ public RegisteredPlayer setTotpToken(String totpToken) {
this.totpToken = totpToken;
+
+ return this;
}
public String getTotpToken() {
return this.totpToken == null ? "" : this.totpToken;
}
- public void setRegDate(Long regDate) {
+ public RegisteredPlayer setRegDate(Long regDate) {
this.regDate = regDate;
+
+ return this;
}
public long getRegDate() {
return this.regDate == null ? Long.MIN_VALUE : this.regDate;
}
- public void setUuid(String uuid) {
+ public RegisteredPlayer setUuid(String uuid) {
this.uuid = uuid;
+
+ return this;
}
public String getUuid() {
return this.uuid == null ? "" : this.uuid;
}
- public void setPremiumUuid(String premiumUuid) {
+ public RegisteredPlayer setPremiumUuid(String premiumUuid) {
this.premiumUuid = premiumUuid;
+
+ return this;
+ }
+
+ public RegisteredPlayer setPremiumUuid(UUID premiumUuid) {
+ this.premiumUuid = premiumUuid.toString();
+
+ return this;
}
public String getPremiumUuid() {
@@ -150,15 +208,29 @@ public class RegisteredPlayer {
return this.loginIp == null ? "" : this.uuid;
}
- public void setLoginIp(String loginIp) {
+ public RegisteredPlayer setLoginIp(String loginIp) {
this.loginIp = loginIp;
+
+ return this;
}
public long getLoginDate() {
return this.loginDate == null ? Long.MIN_VALUE : this.loginDate;
}
- public void setLoginDate(Long loginDate) {
+ public RegisteredPlayer setLoginDate(Long loginDate) {
this.loginDate = loginDate;
+
+ return this;
+ }
+
+ public long getTokenIssuedAt() {
+ return this.tokenIssuedAt == null ? Long.MIN_VALUE : this.tokenIssuedAt;
+ }
+
+ public RegisteredPlayer setTokenIssuedAt(Long tokenIssuedAt) {
+ this.tokenIssuedAt = tokenIssuedAt;
+
+ return this;
}
}