diff options
author | Petr Ilin <hevav@hevav.dev> | 2023-01-16 07:26:09 +0300 |
---|---|---|
committer | Petr Ilin <hevav@hevav.dev> | 2023-01-16 07:28:15 +0300 |
commit | 0e7e93353073dc36bf550de7cb352dbdd5b447de (patch) | |
tree | 8c0b3b7d9ff867a3d353a7830343351d2824b484 | |
parent | a635de80d46eb3f967c30091283e95819d404b84 (diff) | |
download | LimboAuth-0e7e93353073dc36bf550de7cb352dbdd5b447de.tar.gz LimboAuth-0e7e93353073dc36bf550de7cb352dbdd5b447de.tar.bz2 LimboAuth-0e7e93353073dc36bf550de7cb352dbdd5b447de.zip |
1.1.1 release: Client Mod support, RegisteredPlayer refactor
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; } } |