Introduced modular components for authentication, including the storage backend
Servers in dev mode now have a no-auth equivalent that allows any user/pass combo without registration Added a ban command Added a mute command Hooked up the mute functionality of the report screen (for pmods+) Cleaned up all the now-unused classes for player SQL stuff Player SQL stuff now uses entirely prepared statements No longer storing PC name, MAC address, serial number as these are inauthentic components of the protocol Packet to be corrected in the future to allow closer compatibility with authentic clients Used less threading for the SQL queries/updates as these were causing issues both with the old system and the new Updated ::resetpassword and ::setpasswordother commands to use the new server authentication pipeline (to ensure things are always correctly set) Refactored the login read event, now handles more exceptions and edge cases
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
package rs09.auth
|
||||
|
||||
import core.game.node.entity.player.Player
|
||||
import rs09.storage.AccountStorageProvider
|
||||
|
||||
abstract class AuthProvider<T: AccountStorageProvider> {
|
||||
lateinit var storageProvider: T
|
||||
|
||||
abstract fun configureFor(provider: T)
|
||||
|
||||
fun canCreateAccountWith(info: UserAccountInfo) : Boolean {
|
||||
return !storageProvider.checkUsernameTaken(info.username)
|
||||
}
|
||||
|
||||
abstract fun createAccountWith(info: UserAccountInfo) : Boolean
|
||||
|
||||
abstract fun checkLogin(username: String, password: String) : Pair<AuthResponse,UserAccountInfo?>
|
||||
|
||||
abstract fun checkPassword(player: Player, password: String) : Boolean
|
||||
|
||||
abstract fun updatePassword(username: String, newPassword: String)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package rs09.auth
|
||||
|
||||
enum class AuthResponse {
|
||||
UnexpectedError,
|
||||
CouldNotAd,
|
||||
Success,
|
||||
InvalidCredentials,
|
||||
AccountDisabled,
|
||||
AlreadyOnline,
|
||||
Updated,
|
||||
FullWorld,
|
||||
LoginServerOffline,
|
||||
LoginLimitExceeded,
|
||||
BadSessionID,
|
||||
WeakPassword,
|
||||
MembersWorld,
|
||||
CouldNotLogin,
|
||||
Updating,
|
||||
TooManyIncorrectLogins,
|
||||
StandingInMembersArea,
|
||||
AccountLocked,
|
||||
ClosedBeta,
|
||||
InvalidLoginServer,
|
||||
MovingWorld,
|
||||
ErrorLoadingProfile,
|
||||
BannedUser
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package rs09.auth
|
||||
|
||||
import core.game.node.entity.player.Player
|
||||
import rs09.storage.InMemoryStorageProvider
|
||||
|
||||
class DevelopmentAuthenticator : AuthProvider<InMemoryStorageProvider>() {
|
||||
override fun configureFor(provider: InMemoryStorageProvider) {
|
||||
storageProvider = provider
|
||||
}
|
||||
|
||||
override fun checkLogin(username: String, password: String): Pair<AuthResponse, UserAccountInfo?> {
|
||||
val info: UserAccountInfo
|
||||
if(!storageProvider.checkUsernameTaken(username.toLowerCase())) {
|
||||
info = UserAccountInfo.createDefault()
|
||||
info.username = username
|
||||
createAccountWith(info)
|
||||
} else {
|
||||
info = storageProvider.getAccountInfo(username.toLowerCase())
|
||||
}
|
||||
return Pair(AuthResponse.Success, storageProvider.getAccountInfo(username))
|
||||
}
|
||||
|
||||
override fun createAccountWith(info: UserAccountInfo): Boolean {
|
||||
info.username = info.username.toLowerCase()
|
||||
info.rights = 2
|
||||
storageProvider.store(info)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun checkPassword(player: Player, password: String): Boolean {
|
||||
return password == player.details.password
|
||||
}
|
||||
|
||||
override fun updatePassword(username: String, newPassword: String) {
|
||||
val info = storageProvider.getAccountInfo(username)
|
||||
info.password = newPassword
|
||||
storageProvider.update(info)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package rs09.auth
|
||||
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.system.SystemManager
|
||||
import rs09.ServerConstants
|
||||
import rs09.storage.AccountStorageProvider
|
||||
import rs09.storage.SQLStorageProvider
|
||||
import java.sql.SQLDataException
|
||||
|
||||
class ProductionAuthenticator : AuthProvider<AccountStorageProvider>() {
|
||||
override fun configureFor(provider: AccountStorageProvider) {
|
||||
storageProvider = provider
|
||||
if (provider is SQLStorageProvider) {
|
||||
provider.configure(ServerConstants.DATABASE_ADDRESS!!, ServerConstants.DATABASE_NAME!!, ServerConstants.DATABASE_USER!!, ServerConstants.DATABASE_PASS!!)
|
||||
}
|
||||
}
|
||||
|
||||
override fun createAccountWith(info: UserAccountInfo): Boolean {
|
||||
try {
|
||||
info.password = SystemManager.getEncryption().hashPassword(info.password)
|
||||
storageProvider.store(info)
|
||||
} catch (e: SQLDataException) {
|
||||
return false
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun checkLogin(username: String, password: String): Pair<AuthResponse, UserAccountInfo?> {
|
||||
val info: UserAccountInfo
|
||||
try {
|
||||
if (!storageProvider.checkUsernameTaken(username.toLowerCase())) {
|
||||
return Pair(AuthResponse.InvalidCredentials, null)
|
||||
}
|
||||
info = storageProvider.getAccountInfo(username.toLowerCase())
|
||||
val passCorrect = SystemManager.getEncryption().checkPassword(password, info.password)
|
||||
if(!passCorrect || info.password.isEmpty())
|
||||
return Pair(AuthResponse.InvalidCredentials, null)
|
||||
if(info.banEndTime > System.currentTimeMillis())
|
||||
return Pair(AuthResponse.AccountDisabled, null)
|
||||
if(info.online)
|
||||
return Pair(AuthResponse.AlreadyOnline, null)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return Pair(AuthResponse.CouldNotLogin, null)
|
||||
}
|
||||
return Pair(AuthResponse.Success, info)
|
||||
}
|
||||
|
||||
override fun checkPassword(player: Player, password: String): Boolean {
|
||||
return SystemManager.getEncryption().checkPassword(password, player.details.password)
|
||||
}
|
||||
|
||||
override fun updatePassword(username: String, newPassword: String) {
|
||||
val info = storageProvider.getAccountInfo(username)
|
||||
info.password = SystemManager.getEncryption().hashPassword(newPassword)
|
||||
storageProvider.update(info)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package rs09.auth
|
||||
|
||||
class UserAccountInfo(
|
||||
var username: String,
|
||||
var password: String,
|
||||
var uid: Int,
|
||||
var rights: Int,
|
||||
var credits: Int,
|
||||
var ip: String,
|
||||
var lastUsedIp: String,
|
||||
var muteEndTime: Long,
|
||||
var banEndTime: Long,
|
||||
var contacts: String,
|
||||
var blocked: String,
|
||||
var clanName: String,
|
||||
var currentClan: String,
|
||||
var clanReqs: String,
|
||||
var timePlayed: Long,
|
||||
var lastLogin: Long,
|
||||
var online: Boolean
|
||||
) {
|
||||
companion object {
|
||||
@JvmStatic fun createDefault() : UserAccountInfo {
|
||||
return UserAccountInfo("", "", 0, 0, 0, "", "", 0L, 0L, "", "", "", "", "1,0,8,9", 0L, 0L, false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "USER:$username,PASS:$password,UID:$uid,RIGHTS:$rights,CREDITS:$credits,IP:$ip,LASTIP:$lastUsedIp"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as UserAccountInfo
|
||||
|
||||
if (username != other.username) return false
|
||||
if (password != other.password) return false
|
||||
if (uid != other.uid) return false
|
||||
if (rights != other.rights) return false
|
||||
if (credits != other.credits) return false
|
||||
if (ip != other.ip) return false
|
||||
if (lastUsedIp != other.lastUsedIp) return false
|
||||
if (muteEndTime != other.muteEndTime) return false
|
||||
if (banEndTime != other.banEndTime) return false
|
||||
if (contacts != other.contacts) return false
|
||||
if (blocked != other.blocked) return false
|
||||
if (clanName != other.clanName) return false
|
||||
if (currentClan != other.currentClan) return false
|
||||
if (clanReqs != other.clanReqs) return false
|
||||
if (timePlayed != other.timePlayed) return false
|
||||
if (lastLogin != other.lastLogin) return false
|
||||
if (online != other.online) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = username.hashCode()
|
||||
result = 31 * result + password.hashCode()
|
||||
result = 31 * result + uid
|
||||
result = 31 * result + rights
|
||||
result = 31 * result + credits
|
||||
result = 31 * result + ip.hashCode()
|
||||
result = 31 * result + lastUsedIp.hashCode()
|
||||
result = 31 * result + muteEndTime.hashCode()
|
||||
result = 31 * result + banEndTime.hashCode()
|
||||
result = 31 * result + contacts.hashCode()
|
||||
result = 31 * result + blocked.hashCode()
|
||||
result = 31 * result + clanName.hashCode()
|
||||
result = 31 * result + currentClan.hashCode()
|
||||
result = 31 * result + clanReqs.hashCode()
|
||||
result = 31 * result + timePlayed.hashCode()
|
||||
result = 31 * result + lastLogin.hashCode()
|
||||
result = 31 * result + online.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+1
-1
@@ -60,7 +60,7 @@ class EasterBunnyDialogueFile(val NEED_BASKET : Boolean) : DialogueFile() {
|
||||
player!!.dialogueInterpreter.sendDialogue("You need 5 eggs to afford that.")
|
||||
} else {
|
||||
player!!.incrementAttribute(EGG_ATTRIBUTE, -5)
|
||||
player!!.details.credits += 1
|
||||
player!!.details.accountInfo.credits += 1
|
||||
player!!.dialogueInterpreter.sendDialogue(
|
||||
"You turn in 5 eggs in exchange for a credit.",
|
||||
"You now have ${player!!.getAttribute(EGG_ATTRIBUTE, 0)} eggs."
|
||||
|
||||
@@ -13,6 +13,7 @@ import core.game.system.task.Pulse
|
||||
import core.net.amsc.MSPacketRepository
|
||||
import core.net.amsc.ManagementServerState
|
||||
import core.net.amsc.WorldCommunicator
|
||||
import rs09.auth.AuthResponse
|
||||
import rs09.game.system.SystemLogger
|
||||
import rs09.game.world.GameWorld
|
||||
import rs09.game.world.GameWorld.loginListeners
|
||||
@@ -70,7 +71,7 @@ class LoginParser(
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
try {
|
||||
flag(Response.ERROR_LOADING_PROFILE)
|
||||
flag(AuthResponse.ErrorLoadingProfile)
|
||||
Repository.LOGGED_IN_PLAYERS.remove(details.username)
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
@@ -84,10 +85,6 @@ class LoginParser(
|
||||
*/
|
||||
private fun handleLogin() {
|
||||
val p = worldInstance
|
||||
if (!details.parse()) {
|
||||
flag(Response.INVALID_LOGIN_SERVER)
|
||||
return
|
||||
}
|
||||
val player = p ?: Player(details)
|
||||
player.setAttribute("login_type", type)
|
||||
if (p != null) { // Reconnecting
|
||||
@@ -122,7 +119,7 @@ class LoginParser(
|
||||
Repository.lobbyPlayers.remove(player)
|
||||
Repository.playerNames.remove(player.name)
|
||||
MSPacketRepository.sendPlayerRemoval(player.name)
|
||||
flag(Response.ERROR_LOADING_PROFILE)
|
||||
flag(AuthResponse.ErrorLoadingProfile)
|
||||
}
|
||||
//Repository.getPlayerNames().put(player.getName(), player);
|
||||
GameWorld.Pulser.submit(object : Pulse(1) {
|
||||
@@ -140,7 +137,7 @@ class LoginParser(
|
||||
Repository.addPlayer(player)
|
||||
}
|
||||
player.details.session.setObject(player)
|
||||
flag(Response.SUCCESSFUL)
|
||||
flag(AuthResponse.Success)
|
||||
player.init()
|
||||
player.monitor.log(player.details.ipAddress, PlayerMonitor.ADDRESS_LOG)
|
||||
player.monitor.log(player.details.serial, PlayerMonitor.ADDRESS_LOG)
|
||||
@@ -156,7 +153,7 @@ class LoginParser(
|
||||
Repository.lobbyPlayers.remove(player)
|
||||
Repository.playerNames.remove(player.name)
|
||||
MSPacketRepository.sendPlayerRemoval(player.name)
|
||||
flag(Response.ERROR_LOADING_PROFILE)
|
||||
flag(AuthResponse.ErrorLoadingProfile)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -185,7 +182,7 @@ class LoginParser(
|
||||
Repository.disconnectionQueue.remove(details.username)
|
||||
player.initReconnect()
|
||||
player.isActive = true
|
||||
flag(Response.SUCCESSFUL)
|
||||
flag(AuthResponse.Success)
|
||||
player.updateSceneGraph(true)
|
||||
player.configManager.init()
|
||||
LoginConfiguration.configureGameWorld(player)
|
||||
@@ -208,22 +205,22 @@ class LoginParser(
|
||||
//This is supposed to prevent the double-logging issue. Will it work? Who knows.
|
||||
if (Repository.LOGGED_IN_PLAYERS.contains(details.username)) {
|
||||
SystemLogger.logWarn("LOGGED_IN_PLAYERS contains ${details.username}")
|
||||
return flag(Response.ALREADY_ONLINE)
|
||||
return flag(AuthResponse.AlreadyOnline)
|
||||
}
|
||||
if (WorldCommunicator.getState() == ManagementServerState.CONNECTING) {
|
||||
return flag(Response.LOGIN_SERVER_OFFLINE)
|
||||
return flag(AuthResponse.LoginServerOffline)
|
||||
}
|
||||
if (!details.session.isActive) {
|
||||
return false
|
||||
}
|
||||
if (SystemManager.isUpdating()) {
|
||||
return flag(Response.UPDATING)
|
||||
return flag(AuthResponse.Updating)
|
||||
}
|
||||
if (Repository.getPlayerByName(details.username).also { gamePlayer = it } != null && gamePlayer!!.session.isActive) {
|
||||
return flag(Response.ALREADY_ONLINE)
|
||||
return flag(AuthResponse.AlreadyOnline)
|
||||
}
|
||||
return if (details.isBanned) {
|
||||
flag(Response.ACCOUNT_DISABLED)
|
||||
flag(AuthResponse.AccountDisabled)
|
||||
} else true
|
||||
}
|
||||
|
||||
@@ -232,9 +229,9 @@ class LoginParser(
|
||||
* @param response the [Response].
|
||||
* @return `True` if successfully logged in.
|
||||
*/
|
||||
fun flag(response: Response): Boolean {
|
||||
fun flag(response: AuthResponse): Boolean {
|
||||
details.session.write(response, true)
|
||||
return response == Response.SUCCESSFUL
|
||||
return response == AuthResponse.Success
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package rs09.game.system
|
||||
|
||||
import rs09.auth.AuthProvider
|
||||
import rs09.auth.DevelopmentAuthenticator
|
||||
import rs09.auth.ProductionAuthenticator
|
||||
import rs09.storage.AccountStorageProvider
|
||||
import rs09.storage.InMemoryStorageProvider
|
||||
import rs09.storage.SQLStorageProvider
|
||||
|
||||
object Auth {
|
||||
lateinit var authenticator: AuthProvider<*>
|
||||
lateinit var storageProvider: AccountStorageProvider
|
||||
|
||||
fun configureFor(devMode: Boolean) {
|
||||
if (devMode) {
|
||||
authenticator = DevelopmentAuthenticator()
|
||||
storageProvider = InMemoryStorageProvider()
|
||||
(authenticator as DevelopmentAuthenticator).configureFor(storageProvider as InMemoryStorageProvider)
|
||||
SystemLogger.logInfo("[AUTH] Using Development Authenticator with In-Memory Storage")
|
||||
} else {
|
||||
authenticator = ProductionAuthenticator()
|
||||
storageProvider = SQLStorageProvider()
|
||||
(authenticator as ProductionAuthenticator).configureFor(storageProvider as SQLStorageProvider)
|
||||
SystemLogger.logInfo("[AUTH] Using Production Authenticator with SQL Storage")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package rs09.game.system.command.sets
|
||||
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.entity.player.info.Rights
|
||||
import rs09.game.system.command.Command
|
||||
import core.game.system.task.Pulse
|
||||
import rs09.game.world.GameWorld
|
||||
import core.game.world.map.Location
|
||||
@@ -25,7 +24,7 @@ class ModerationCommandSet : CommandSet(Privilege.MODERATOR){
|
||||
* Kick a player
|
||||
* =============================================================================================================
|
||||
*/
|
||||
define("kick", Privilege.ADMIN){ player, args ->
|
||||
define("kick", Privilege.MODERATOR){ player, args ->
|
||||
val playerToKick: Player? = Repository.getPlayerByName(args[1])
|
||||
if (playerToKick != null) {
|
||||
playerToKick.clear(true)
|
||||
@@ -38,6 +37,100 @@ class ModerationCommandSet : CommandSet(Privilege.MODERATOR){
|
||||
* =============================================================================================================
|
||||
*/
|
||||
|
||||
/**
|
||||
* Ban a player
|
||||
* =============================================================================================================
|
||||
*/
|
||||
define("ban", Privilege.ADMIN){ player, args ->
|
||||
val name = args[1]
|
||||
if(!GameWorld.accountStorage.checkUsernameTaken(name)) {
|
||||
reject(player, "Invalid username: $name")
|
||||
}
|
||||
val playerToKick: Player? = Repository.getPlayerByName(name)
|
||||
val durationString = args[2]
|
||||
val durationTokens = durationString.toCharArray()
|
||||
var intToken = ""
|
||||
var durationMillis = 0L
|
||||
var durationUnit: TimeUnit = TimeUnit.NANOSECONDS
|
||||
for(token in durationTokens){
|
||||
if(token.toString().toIntOrNull() != null) intToken += token
|
||||
else {
|
||||
val durationInt: Int = (intToken.toIntOrNull() ?: -1).also { if(it == -1) reject(player, "Invalid duration: $intToken") }
|
||||
durationUnit = when(token) {
|
||||
'd' -> TimeUnit.DAYS
|
||||
's' -> TimeUnit.SECONDS
|
||||
'm' -> TimeUnit.MINUTES
|
||||
'h' -> TimeUnit.HOURS
|
||||
else -> TimeUnit.SECONDS
|
||||
}
|
||||
durationMillis = durationUnit.toMillis(durationInt.toLong())
|
||||
}
|
||||
}
|
||||
|
||||
playerToKick?.details?.accountInfo?.banEndTime = System.currentTimeMillis() + durationMillis
|
||||
playerToKick?.clear(true)
|
||||
GameWorld.Pulser.submit(object : Pulse(2) {
|
||||
override fun pulse(): Boolean {
|
||||
val info = GameWorld.accountStorage.getAccountInfo(name)
|
||||
info.banEndTime = System.currentTimeMillis() + durationMillis
|
||||
GameWorld.accountStorage.update(info)
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
notify(player, "Banned user $name for $intToken ${durationUnit.name.toLowerCase()}.")
|
||||
}
|
||||
/**
|
||||
* =============================================================================================================
|
||||
*/
|
||||
|
||||
/**
|
||||
* Mute a player
|
||||
* =============================================================================================================
|
||||
*/
|
||||
define("mute", Privilege.MODERATOR){ player, args ->
|
||||
val name = args[1]
|
||||
if(!GameWorld.accountStorage.checkUsernameTaken(name)) {
|
||||
reject(player, "Invalid username: $name")
|
||||
}
|
||||
val playerToMute: Player? = Repository.getPlayerByName(name)
|
||||
val durationString = args[2]
|
||||
val durationTokens = durationString.toCharArray()
|
||||
var intToken = ""
|
||||
var durationMillis = 0L
|
||||
var durationUnit: TimeUnit = TimeUnit.NANOSECONDS
|
||||
for(token in durationTokens){
|
||||
if(token.toString().toIntOrNull() != null) intToken += token
|
||||
else {
|
||||
val durationInt: Int = (intToken.toIntOrNull() ?: -1).also { if(it == -1) reject(player, "Invalid duration: $intToken") }
|
||||
durationUnit = when(token) {
|
||||
'd' -> TimeUnit.DAYS
|
||||
's' -> TimeUnit.SECONDS
|
||||
'm' -> TimeUnit.MINUTES
|
||||
'h' -> TimeUnit.HOURS
|
||||
else -> TimeUnit.SECONDS
|
||||
}
|
||||
durationMillis = durationUnit.toMillis(durationInt.toLong())
|
||||
}
|
||||
}
|
||||
|
||||
playerToMute?.details?.accountInfo?.muteEndTime = System.currentTimeMillis() + durationMillis
|
||||
if(playerToMute == null) { //Player was offline at the time
|
||||
GameWorld.Pulser.submit(object : Pulse(2) {
|
||||
override fun pulse(): Boolean {
|
||||
val info = GameWorld.accountStorage.getAccountInfo(name)
|
||||
info.muteEndTime = System.currentTimeMillis() + durationMillis
|
||||
GameWorld.accountStorage.update(info)
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
notify(player, "Muted user $name for $intToken ${durationUnit.name.toLowerCase()}.")
|
||||
}
|
||||
/**
|
||||
* =============================================================================================================
|
||||
*/
|
||||
|
||||
/**
|
||||
* Jail a player
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package rs09.game.system.command.sets
|
||||
|
||||
import core.cache.Cache
|
||||
import core.cache.def.Definition
|
||||
import api.InputType
|
||||
import api.runTask
|
||||
import api.sendDialogue
|
||||
import api.sendInputDialogue
|
||||
import core.cache.def.impl.ItemDefinition
|
||||
import core.game.node.entity.player.info.login.Response
|
||||
import core.game.node.entity.player.info.portal.PlayerSQLManager
|
||||
import core.game.node.item.Item
|
||||
import core.game.system.SystemManager
|
||||
import core.game.system.SystemState
|
||||
import core.plugin.Initializable
|
||||
import org.rs09.consts.Items
|
||||
import rs09.game.system.command.Command
|
||||
import rs09.game.system.SystemLogger
|
||||
import rs09.game.system.command.Privilege
|
||||
import rs09.game.world.GameWorld
|
||||
import rs09.game.world.repository.Repository
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@@ -40,31 +41,29 @@ class SystemCommandSet : CommandSet(Privilege.ADMIN) {
|
||||
* Allows a player to reset their password
|
||||
*/
|
||||
define("resetpassword", Privilege.STANDARD) { player, args ->
|
||||
if (args.size != 3) {
|
||||
reject(player, "Usage: ::resetpassword current new", "WARNING: THIS IS PERMANENT.", "WARNING: PASSWORD CAN NOT CONTAIN SPACES.")
|
||||
sendInputDialogue(player, InputType.STRING_SHORT, "Enter Current Password:"){value ->
|
||||
val pass = value.toString()
|
||||
SystemLogger.logInfo(pass)
|
||||
runTask(player) {
|
||||
if (GameWorld.authenticator.checkPassword(player, pass)) {
|
||||
sendInputDialogue(player, InputType.STRING_SHORT, "Enter New Password:") { value2 ->
|
||||
val newPass = value2.toString()
|
||||
if (pass == newPass) {
|
||||
sendDialogue(player, "Failed: Passwords Match")
|
||||
} else if (newPass.length !in 5..20) {
|
||||
sendDialogue(player, "Failed: Password Too Long Or Too Short")
|
||||
} else if (newPass == player.details.accountInfo.username) {
|
||||
sendDialogue(player, "Failed: Password Is Username")
|
||||
} else {
|
||||
GameWorld.authenticator.updatePassword(player.details.accountInfo.username, newPass)
|
||||
sendDialogue(player, "Success: Password Updated!")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendDialogue(player, "Fail: Wrong Password.")
|
||||
}
|
||||
}
|
||||
}
|
||||
val oldPass = args[1]
|
||||
var newPass = args[2]
|
||||
|
||||
if (PlayerSQLManager.getCredentialResponse(player.details.username, oldPass) != Response.SUCCESSFUL) {
|
||||
reject(player, "INVALID PASSWORD!")
|
||||
}
|
||||
|
||||
if (newPass.length < 5 || newPass.length > 20) {
|
||||
reject(player, "NEW PASSWORD MUST BE BETWEEN 5 AND 20 CHARACTERS")
|
||||
}
|
||||
|
||||
if (newPass == player.username) {
|
||||
reject(player, "PASSWORD CAN NOT BE SAME AS USERNAME.")
|
||||
}
|
||||
|
||||
if (newPass == oldPass) {
|
||||
reject(player, "PASSWORDS CAN NOT BE THE SAME")
|
||||
}
|
||||
|
||||
newPass = SystemManager.getEncryption().hashPassword(newPass)
|
||||
PlayerSQLManager.updatePassword(player.username.toLowerCase().replace(" ", "_"), newPass)
|
||||
notify(player, "Password updated successfully.")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,9 +74,9 @@ class SystemCommandSet : CommandSet(Privilege.ADMIN) {
|
||||
reject(player, "Usage: ::resetpasswordother user new", "WARNING: THIS IS PERMANENT.", "WARNING: PASSWORD CAN NOT CONTAIN SPACES.")
|
||||
}
|
||||
val otherUser = args[1]
|
||||
var newPass = args[2]
|
||||
val newPass = args[2]
|
||||
|
||||
if (PlayerSQLManager.hasSqlAccount(otherUser, "username")) {
|
||||
if (GameWorld.accountStorage.checkUsernameTaken(otherUser)) {
|
||||
|
||||
if (newPass.length < 5 || newPass.length > 20) {
|
||||
reject(player, "NEW PASSWORD MUST BE BETWEEN 5 AND 20 CHARACTERS")
|
||||
@@ -87,8 +86,7 @@ class SystemCommandSet : CommandSet(Privilege.ADMIN) {
|
||||
reject(player, "PASSWORD CAN NOT BE SAME AS USERNAME.")
|
||||
}
|
||||
|
||||
newPass = SystemManager.getEncryption().hashPassword(newPass)
|
||||
PlayerSQLManager.updatePassword(otherUser.toLowerCase().replace(" ","_"),newPass)
|
||||
GameWorld.authenticator.updatePassword(otherUser, newPass)
|
||||
notify(player, "Password updated successfully.")
|
||||
|
||||
} else {
|
||||
|
||||
@@ -12,13 +12,15 @@ import core.game.world.map.Location
|
||||
import core.game.world.map.RegionManager
|
||||
import core.plugin.CorePluginTypes.StartupPlugin
|
||||
import core.tools.RandomFunction
|
||||
import core.tools.mysql.DatabaseManager
|
||||
import rs09.ServerConstants
|
||||
import rs09.auth.AuthProvider
|
||||
import rs09.game.system.Auth
|
||||
import rs09.game.system.SystemLogger
|
||||
import rs09.game.system.SystemLogger.logInfo
|
||||
import rs09.game.system.config.ConfigParser
|
||||
import rs09.game.world.repository.Repository
|
||||
import rs09.plugin.ClassScanner
|
||||
import rs09.storage.AccountStorageProvider
|
||||
import rs09.worker.MajorUpdateWorker
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
@@ -83,14 +85,17 @@ object GameWorld {
|
||||
*/
|
||||
@JvmStatic
|
||||
var settings: GameSettings? = null
|
||||
@JvmStatic
|
||||
val authenticator: AuthProvider<*>
|
||||
get() = Auth.authenticator
|
||||
@JvmStatic
|
||||
val accountStorage: AccountStorageProvider
|
||||
get() = Auth.storageProvider
|
||||
/**
|
||||
* The current amount of (600ms) cycles elapsed.
|
||||
*/
|
||||
@JvmStatic
|
||||
var ticks = 0
|
||||
@JvmStatic
|
||||
var databaseManager: DatabaseManager? = null
|
||||
private set
|
||||
|
||||
@JvmStatic
|
||||
var Pulser = PulseRunner()
|
||||
@@ -158,9 +163,12 @@ object GameWorld {
|
||||
fun prompt(run: Boolean, directory: String?){
|
||||
logInfo("Prompting ${settings?.name} Game World...")
|
||||
Cache.init(ServerConstants.CACHE_PATH)
|
||||
databaseManager = DatabaseManager(ServerConstants.DATABASE)
|
||||
databaseManager!!.connect()
|
||||
configParser.prePlugin()
|
||||
//go overboard with checks to make sure dev mode authenticator never triggers on live
|
||||
Auth.configureFor(
|
||||
settings!!.isDevMode
|
||||
&& ServerConstants.MS_SECRET_KEY == "2009scape_development"
|
||||
)
|
||||
ConfigParser().prePlugin()
|
||||
ClassScanner.scanClasspath()
|
||||
ClassScanner.loadPureInterfaces()
|
||||
worldPersists.forEach { it.parse() }
|
||||
|
||||
@@ -195,12 +195,6 @@ class DisconnectionQueue {
|
||||
fun save(player: Player, sql: Boolean): Boolean {
|
||||
try {
|
||||
PlayerParser.save(player)
|
||||
if (sql) {
|
||||
player.details.sqlManager.update(player)
|
||||
player.details.save()
|
||||
/*SQLEntryHandler.write(HighscoreSQLHandler(player))
|
||||
SQLEntryHandler.write(PlayerLogSQLHandler(player.monitor, player.name))*/
|
||||
}
|
||||
return true
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
|
||||
@@ -1,190 +1,39 @@
|
||||
package rs09.net.event
|
||||
|
||||
import core.cache.Cache
|
||||
import core.cache.crypto.ISAACCipher
|
||||
import core.cache.crypto.ISAACPair
|
||||
import core.cache.misc.buffer.ByteBufferUtils
|
||||
import core.game.node.entity.player.info.ClientInfo
|
||||
import core.game.node.entity.player.info.PlayerDetails
|
||||
import core.game.node.entity.player.info.UIDInfo
|
||||
import core.game.node.entity.player.info.login.LoginType
|
||||
import core.game.node.entity.player.info.login.Response
|
||||
import core.game.node.entity.player.info.portal.PlayerSQLManager
|
||||
import core.game.system.task.TaskExecutor
|
||||
import core.net.Constants
|
||||
import core.net.IoReadEvent
|
||||
import core.net.IoSession
|
||||
import core.net.amsc.WorldCommunicator
|
||||
import core.tools.StringUtils
|
||||
import rs09.ServerConstants
|
||||
import rs09.game.node.entity.player.info.login.LoginParser
|
||||
import rs09.game.system.SystemLogger
|
||||
import rs09.game.world.repository.Repository
|
||||
import java.lang.Runnable
|
||||
import java.math.BigInteger
|
||||
import rs09.auth.AuthResponse
|
||||
import rs09.game.world.GameWorld
|
||||
import rs09.net.packet.`in`.Login
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Handles login reading events.
|
||||
* @author Emperor
|
||||
* @author Ceikry
|
||||
*/
|
||||
class LoginReadEvent
|
||||
/**
|
||||
* Constructs a new `LoginReadEvent`.
|
||||
* @param session The session.
|
||||
* @param buffer The buffer with data to read from.
|
||||
*/
|
||||
(session: IoSession?, buffer: ByteBuffer?) : IoReadEvent(session, buffer) {
|
||||
class LoginReadEvent(session: IoSession?, buffer: ByteBuffer?) : IoReadEvent(session, buffer) {
|
||||
override fun read(session: IoSession, buffer: ByteBuffer) {
|
||||
SystemLogger.logInfo("login read")
|
||||
val opcode: Int = buffer.get().toInt()
|
||||
if (buffer.short.toInt() != buffer.remaining()) {
|
||||
session.write(Response.BAD_SESSION_ID)
|
||||
val (response, info) = Login.decodeFromBuffer(buffer)
|
||||
|
||||
if(response != AuthResponse.Success || info == null) {
|
||||
session.write(response)
|
||||
return
|
||||
}
|
||||
val build = buffer.int
|
||||
if (build != Constants.REVISION) { // || buffer.getInt() != Constants.CLIENT_BUILD) {
|
||||
session.write(Response.UPDATED)
|
||||
|
||||
val (authResponse, accountInfo) = GameWorld.authenticator.checkLogin(info.username, info.password)
|
||||
|
||||
if(authResponse != AuthResponse.Success || accountInfo == null) {
|
||||
session.write(authResponse)
|
||||
return
|
||||
}
|
||||
when (opcode) {
|
||||
12 -> println("User details event detected")
|
||||
16, 18 -> decodeWorld(opcode, session, buffer)
|
||||
else -> {
|
||||
SystemLogger.logErr("[Login] Unhandled login type [opcode=$opcode]!")
|
||||
session.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val details = PlayerDetails(info.username)
|
||||
details.accountInfo = accountInfo
|
||||
session.clientInfo = ClientInfo(info.displayMode, info.windowMode, info.screenWidth, info.screenHeight)
|
||||
session.isaacPair = info.isaacPair
|
||||
|
||||
/**
|
||||
* Decodes a world login request.
|
||||
* @param session The session.
|
||||
* @param buffer The buffer to read from.
|
||||
*/
|
||||
private fun decodeWorld(opcode: Int, session: IoSession, buffer: ByteBuffer) {
|
||||
var buffer = buffer
|
||||
val d = buffer.get() // Memory?
|
||||
val e = buffer.get() // no advertisement = 1
|
||||
val f = buffer.get() // 1
|
||||
val windowMode = buffer.get().toInt() // Screen size mode
|
||||
val screenWidth = buffer.short.toInt() // Screen size Width
|
||||
val screenHeight = buffer.short.toInt() // Screen size Height
|
||||
val displayMode = buffer.get().toInt() // Display mode
|
||||
val data = ByteArray(24) // random.dat data.
|
||||
buffer[data]
|
||||
ByteBufferUtils.getString(buffer)
|
||||
buffer.int // Affiliate id
|
||||
buffer.int // Hash containing a bunch of settings
|
||||
val curpackets = buffer.short //Current interface packet counter.
|
||||
for (i in Cache.getIndexes().indices) {
|
||||
val crc = if (Cache.getIndexes()[i] == null) 0 else Cache.getIndexes()[i].information.informationContainer.crc
|
||||
if (crc != buffer.int && crc != 0) {
|
||||
/*session.write(Response.UPDATED);
|
||||
return;*/
|
||||
}
|
||||
}
|
||||
buffer = getRSABlock(buffer)
|
||||
buffer.rewind()
|
||||
if(buffer.get().toInt() != 10){
|
||||
session.write(Response.COULD_NOT_LOGIN)
|
||||
return
|
||||
}
|
||||
val isaacSeed = getISAACSeed(buffer)
|
||||
val inCipher = ISAACCipher(isaacSeed)
|
||||
for(i in 0..curpackets){
|
||||
inCipher.nextValue
|
||||
}
|
||||
for (i in 0..3) {
|
||||
isaacSeed[i] += 50
|
||||
}
|
||||
val outCipher = ISAACCipher(isaacSeed)
|
||||
session.isaacPair = ISAACPair(inCipher, outCipher)
|
||||
session.clientInfo = ClientInfo(displayMode, windowMode, screenWidth, screenHeight)
|
||||
val b = buffer
|
||||
SystemLogger.logInfo("spawning thread to handle login")
|
||||
TaskExecutor.executeSQL {
|
||||
SystemLogger.logInfo("login thread start")
|
||||
Thread.currentThread().name = "Login Password Response"
|
||||
SystemLogger.logInfo("login thread named")
|
||||
try {
|
||||
val username = StringUtils.longToString(b.long)
|
||||
SystemLogger.logInfo("got username")
|
||||
val password = ByteBufferUtils.getString(b)
|
||||
SystemLogger.logInfo("got password")
|
||||
val response = PlayerSQLManager.getCredentialResponse(username, password)
|
||||
SystemLogger.logInfo("got sql response")
|
||||
if (response != Response.SUCCESSFUL) {
|
||||
SystemLogger.logInfo("not success :(")
|
||||
session.write(response, true)
|
||||
return@executeSQL
|
||||
}
|
||||
SystemLogger.logInfo("great success, attempting login")
|
||||
login(PlayerDetails(username, password), session, b, opcode)
|
||||
SystemLogger.logInfo("done")
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.logInfo("big whoops")
|
||||
e.printStackTrace()
|
||||
session.write(Response.COULD_NOT_LOGIN)
|
||||
}
|
||||
SystemLogger.logInfo("end login thread")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the login procedure after we check an acc is registered & certified.
|
||||
* @param details the player's details.
|
||||
* @param session the session.
|
||||
* @param buffer the byte buffer.
|
||||
* @param opcode the opcode.
|
||||
*/
|
||||
@JvmStatic
|
||||
private fun login(details: PlayerDetails, session: IoSession, buffer: ByteBuffer, opcode: Int) {
|
||||
SystemLogger.logInfo("login")
|
||||
if(!Repository.LOGGED_IN_PLAYERS.contains(details.username))
|
||||
Repository.LOGGED_IN_PLAYERS.add(details.username)
|
||||
val parser = LoginParser(details, LoginType.fromType(opcode))
|
||||
details.session = session
|
||||
details.info.translate(UIDInfo(details.ipAddress, ByteBufferUtils.getString(buffer), ByteBufferUtils.getString(buffer), ByteBufferUtils.getString(buffer)))
|
||||
if (WorldCommunicator.isEnabled()) {
|
||||
WorldCommunicator.register(parser)
|
||||
} else {
|
||||
TaskExecutor.executeSQL {parser.run()}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ISAAC seed from the buffer.
|
||||
* @param buffer The buffer to read from.
|
||||
* @return The ISAAC seed.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getISAACSeed(buffer: ByteBuffer): IntArray {
|
||||
val seed = IntArray(4)
|
||||
for (i in 0..3) {
|
||||
seed[i] = buffer.int
|
||||
}
|
||||
return seed
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the RSA block buffer.
|
||||
* @param buffer The buffer to get the RSA block from.
|
||||
* @return The RSA block buffer.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getRSABlock(buffer: ByteBuffer): ByteBuffer {
|
||||
SystemLogger.logInfo("getRSABlock")
|
||||
fun ByteArray.toHex(): String = joinToString(separator = "") { eachByte -> "%02x".format(eachByte) }
|
||||
SystemLogger.logInfo(buffer.array().sliceArray(0..1500).toHex())
|
||||
val numBytes = 256 + buffer.get()
|
||||
val encryptedByteArray = ByteArray(numBytes)
|
||||
buffer.get(encryptedByteArray)
|
||||
val encryptedBytes = BigInteger(encryptedByteArray)
|
||||
return ByteBuffer.wrap(encryptedBytes.modPow(ServerConstants.EXPONENT, ServerConstants.MODULUS).toByteArray())
|
||||
}
|
||||
Login.proceedWith(session, details, info.opcode)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package rs09.net.packet.`in`
|
||||
|
||||
import core.cache.crypto.ISAACCipher
|
||||
import core.cache.crypto.ISAACPair
|
||||
import core.cache.misc.buffer.ByteBufferUtils
|
||||
import core.game.node.entity.player.info.PlayerDetails
|
||||
import core.game.node.entity.player.info.UIDInfo
|
||||
import core.game.node.entity.player.info.login.LoginType
|
||||
import core.net.Constants
|
||||
import core.net.IoSession
|
||||
import core.net.amsc.WorldCommunicator
|
||||
import core.tools.StringUtils
|
||||
import rs09.ServerConstants
|
||||
import rs09.auth.AuthResponse
|
||||
import rs09.game.node.entity.player.info.login.LoginParser
|
||||
import rs09.game.system.SystemLogger
|
||||
import rs09.game.world.repository.Repository
|
||||
import java.math.BigInteger
|
||||
import java.nio.BufferUnderflowException
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
object Login {
|
||||
private const val ENCRYPTION_VERIFICATION_BYTE: Int = 10
|
||||
private const val NORMAL_LOGIN_OP = 16
|
||||
private const val RECONNECT_LOGIN_OP = 18
|
||||
const val CACHE_INDEX_COUNT = 29
|
||||
|
||||
fun decodeFromBuffer(buffer: ByteBuffer) : Pair<AuthResponse, LoginInfo?> {
|
||||
try {
|
||||
val info = LoginInfo.createDefault()
|
||||
|
||||
info.opcode = buffer.get().toInt()
|
||||
if (buffer.short.toInt() != buffer.remaining()) {
|
||||
return Pair(AuthResponse.BadSessionID, null)
|
||||
}
|
||||
val revision = buffer.int
|
||||
if (revision != Constants.REVISION) {
|
||||
return Pair(AuthResponse.Updated, null)
|
||||
}
|
||||
if (info.opcode != NORMAL_LOGIN_OP && info.opcode != RECONNECT_LOGIN_OP) {
|
||||
SystemLogger.logInfo("Invalid Login Opcode: ${info.opcode}")
|
||||
return Pair(AuthResponse.InvalidLoginServer, null)
|
||||
}
|
||||
|
||||
noop(buffer)
|
||||
info.showAds = buffer.get().toInt() == 1
|
||||
noop(buffer)
|
||||
info.windowMode = buffer.get().toInt()
|
||||
info.screenWidth = buffer.short.toInt()
|
||||
info.screenHeight = buffer.short.toInt()
|
||||
info.displayMode = buffer.get().toInt()
|
||||
noop(buffer, 24) //Skip past a bunch of random (actually random) data the client sends
|
||||
ByteBufferUtils.getString(buffer) //same as above
|
||||
info.adAffiliateId = buffer.int
|
||||
info.settingsHash = buffer.int
|
||||
info.currentPacketCount = buffer.short.toInt()
|
||||
|
||||
//Read client-reported CRC sums
|
||||
for (i in 0 until CACHE_INDEX_COUNT) info.crcSums[i] = buffer.int
|
||||
|
||||
val decryptedBuffer = decryptRSABuffer(buffer, ServerConstants.EXPONENT, ServerConstants.MODULUS)
|
||||
decryptedBuffer.rewind()
|
||||
|
||||
if (decryptedBuffer.get().toInt() != ENCRYPTION_VERIFICATION_BYTE) {
|
||||
return Pair(AuthResponse.UnexpectedError, info)
|
||||
}
|
||||
|
||||
info.isaacPair = produceISAACPairFrom(decryptedBuffer)
|
||||
info.username = StringUtils.longToString(decryptedBuffer.long)
|
||||
info.password = ByteBufferUtils.getString(decryptedBuffer)
|
||||
return Pair(AuthResponse.Success, info)
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.logErr("Exception encountered during login packet parsing! See stack trace below.")
|
||||
e.printStackTrace()
|
||||
return Pair(AuthResponse.UnexpectedError, null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun produceISAACPairFrom(buffer: ByteBuffer): ISAACPair {
|
||||
val incomingSeed = IntArray(4)
|
||||
for(i in incomingSeed.indices) {
|
||||
incomingSeed[i] = buffer.int
|
||||
}
|
||||
val inCipher = ISAACCipher(incomingSeed)
|
||||
for(i in incomingSeed.indices) {
|
||||
incomingSeed[i] += 50
|
||||
}
|
||||
val outCipher = ISAACCipher(incomingSeed)
|
||||
return ISAACPair(inCipher, outCipher)
|
||||
}
|
||||
|
||||
@JvmStatic fun decryptRSABuffer(buffer: ByteBuffer, exponent: BigInteger, modulus: BigInteger): ByteBuffer {
|
||||
return try {
|
||||
val numBytes = buffer.get().toInt() and 0xFF
|
||||
val encryptedBytes = ByteArray(numBytes)
|
||||
buffer[encryptedBytes]
|
||||
|
||||
val encryptedBigInt = BigInteger(encryptedBytes)
|
||||
ByteBuffer.wrap(encryptedBigInt.modPow(exponent, modulus).toByteArray())
|
||||
} catch (e: BufferUnderflowException) {
|
||||
ByteBuffer.wrap(byteArrayOf(-1))
|
||||
}
|
||||
}
|
||||
|
||||
private fun noop(buffer: ByteBuffer, amount: Int = 1) {buffer[ByteArray(amount)]}
|
||||
|
||||
fun proceedWith(session: IoSession, details: PlayerDetails, opcode: Int) {
|
||||
if (!Repository.LOGGED_IN_PLAYERS.contains(details.username))
|
||||
Repository.LOGGED_IN_PLAYERS.add(details.username)
|
||||
details.session = session
|
||||
details.info.translate(UIDInfo(details.ipAddress, "DEPRECATED", "DEPRECATED", "DEPRECATED"))
|
||||
WorldCommunicator.register(LoginParser(details, LoginType.fromType(opcode)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package rs09.net.packet.`in`
|
||||
|
||||
import core.cache.crypto.ISAACCipher
|
||||
import core.cache.crypto.ISAACPair
|
||||
|
||||
class LoginInfo(
|
||||
var showAds: Boolean, //Unused
|
||||
var windowMode : Int,
|
||||
var screenWidth: Int,
|
||||
var screenHeight: Int,
|
||||
var displayMode: Int,
|
||||
var adAffiliateId: Int, //Unused
|
||||
var settingsHash: Int,
|
||||
var currentPacketCount: Int,
|
||||
var username: String,
|
||||
var password: String,
|
||||
var isaacPair: ISAACPair,
|
||||
var opcode: Int,
|
||||
var crcSums: IntArray
|
||||
) {
|
||||
companion object {
|
||||
fun createDefault(): LoginInfo {
|
||||
return LoginInfo(false, 0, 0, 0, 0, 0, 0, 0, "", "", ISAACPair(ISAACCipher(intArrayOf()), ISAACCipher(intArrayOf())), 0, IntArray(Login.CACHE_INDEX_COUNT))
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ads:$showAds,wm:$windowMode,sw:$screenWidth,sh:$screenHeight,dm:$displayMode,adid:$adAffiliateId,settings:$settingsHash,pkt:$currentPacketCount,un:$username,pw:$password"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package rs09.storage
|
||||
|
||||
import rs09.auth.UserAccountInfo
|
||||
|
||||
interface AccountStorageProvider {
|
||||
fun checkUsernameTaken(username: String): Boolean
|
||||
fun getAccountInfo(username: String): UserAccountInfo
|
||||
fun store(info: UserAccountInfo)
|
||||
fun update(info: UserAccountInfo)
|
||||
fun remove(info: UserAccountInfo)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package rs09.storage
|
||||
|
||||
import rs09.auth.UserAccountInfo
|
||||
|
||||
class InMemoryStorageProvider : AccountStorageProvider {
|
||||
private val storage = HashMap<String, UserAccountInfo>()
|
||||
|
||||
override fun checkUsernameTaken(username: String): Boolean {
|
||||
return storage[username] != null
|
||||
}
|
||||
|
||||
override fun getAccountInfo(username: String): UserAccountInfo {
|
||||
return storage[username] ?: UserAccountInfo.createDefault()
|
||||
}
|
||||
|
||||
override fun store(info: UserAccountInfo) {
|
||||
storage[info.username] = info
|
||||
}
|
||||
|
||||
override fun update(info: UserAccountInfo) {
|
||||
storage[info.username] = info
|
||||
}
|
||||
|
||||
override fun remove(info: UserAccountInfo) {
|
||||
storage.remove(info.username)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package rs09.storage
|
||||
|
||||
import rs09.auth.UserAccountInfo
|
||||
import java.lang.Long.max
|
||||
import java.sql.*
|
||||
|
||||
class SQLStorageProvider : AccountStorageProvider {
|
||||
var connectionString = ""
|
||||
var connectionUsername = ""
|
||||
var connectionPassword = ""
|
||||
|
||||
fun getConnection(): Connection {
|
||||
Class.forName("com.mysql.cj.jdbc.Driver")
|
||||
return DriverManager.getConnection(connectionString, connectionUsername, connectionPassword)
|
||||
}
|
||||
|
||||
fun configure(host: String, databaseName: String, username: String, password: String) {
|
||||
connectionString = "jdbc:mysql://$host/$databaseName?useTimezone=true&serverTimezone=UTC"
|
||||
connectionUsername = username
|
||||
connectionPassword = password
|
||||
}
|
||||
|
||||
override fun checkUsernameTaken(username: String): Boolean {
|
||||
val conn = getConnection()
|
||||
conn.use {
|
||||
val compiledUsernameQuery = it.prepareStatement(usernameQuery)
|
||||
compiledUsernameQuery.setString(1, username.toLowerCase())
|
||||
val result = compiledUsernameQuery.executeQuery()
|
||||
val exists = result.next()
|
||||
result.close()
|
||||
return exists
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAccountInfo(username: String): UserAccountInfo {
|
||||
val conn = getConnection()
|
||||
conn.use { con ->
|
||||
val compiledAccountInfoQuery = con.prepareStatement(accountInfoQuery)
|
||||
compiledAccountInfoQuery.setString(1, username.toLowerCase())
|
||||
val result = compiledAccountInfoQuery.executeQuery()
|
||||
if (result.next()) {
|
||||
val userData = UserAccountInfo.createDefault()
|
||||
userData.username = username
|
||||
|
||||
result.getString(2) ?.let { userData.password = it }
|
||||
result.getInt(3) .let { userData.uid = it }
|
||||
result.getInt(4) .let { userData.rights = it }
|
||||
result.getInt(5) .let { userData.credits = it }
|
||||
result.getString(6) ?.let { userData.ip = it }
|
||||
result.getString(7) ?.let { userData.lastUsedIp = it }
|
||||
result.getLong(8) .let { userData.muteEndTime = max(0L, it) }
|
||||
result.getLong(9) .let { userData.banEndTime = max(0L, it) }
|
||||
result.getString(10)?.let { userData.contacts = it }
|
||||
result.getString(11)?.let { userData.blocked = it }
|
||||
result.getString(12)?.let { userData.clanName = it }
|
||||
result.getString(13)?.let { userData.currentClan = it }
|
||||
result.getString(14)?.let { userData.clanReqs = it }
|
||||
result.getLong(15) .let { userData.timePlayed = max(0L, it) }
|
||||
result.getLong(16) .let { userData.lastLogin = max(0L, it) }
|
||||
result.getBoolean(17).let { userData.online = it }
|
||||
|
||||
return userData
|
||||
} else {
|
||||
return UserAccountInfo.createDefault()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun store(info: UserAccountInfo) {
|
||||
val conn = getConnection()
|
||||
conn.use {
|
||||
val compiledInsertInfoQuery = it.prepareStatement(insertInfoQuery, Statement.RETURN_GENERATED_KEYS)
|
||||
val emptyInfo = UserAccountInfo.createDefault()
|
||||
if (info == emptyInfo) {
|
||||
throw IllegalStateException("Tried to store empty data!")
|
||||
}
|
||||
emptyInfo.username = info.username
|
||||
if (info == emptyInfo) {
|
||||
throw IllegalStateException("Tried to store empty data!")
|
||||
}
|
||||
|
||||
if (checkUsernameTaken(info.username)) {
|
||||
throw SQLDataException("Account already exists!")
|
||||
}
|
||||
compiledInsertInfoQuery.setString(1, info.username)
|
||||
compiledInsertInfoQuery.setString(2, info.password)
|
||||
compiledInsertInfoQuery.setInt(3, info.rights)
|
||||
compiledInsertInfoQuery.setInt(4, info.credits)
|
||||
compiledInsertInfoQuery.setString(5, info.ip)
|
||||
compiledInsertInfoQuery.setString(6, info.ip)
|
||||
compiledInsertInfoQuery.setLong(7, info.muteEndTime)
|
||||
compiledInsertInfoQuery.setLong(8, info.banEndTime)
|
||||
compiledInsertInfoQuery.setString(9, info.contacts)
|
||||
compiledInsertInfoQuery.setString(10, info.blocked)
|
||||
compiledInsertInfoQuery.setString(11, info.clanName)
|
||||
compiledInsertInfoQuery.setString(12, info.currentClan)
|
||||
compiledInsertInfoQuery.setString(13, info.clanReqs)
|
||||
compiledInsertInfoQuery.setLong(14, info.timePlayed)
|
||||
compiledInsertInfoQuery.setLong(15, info.lastLogin)
|
||||
compiledInsertInfoQuery.setBoolean(16, info.online)
|
||||
compiledInsertInfoQuery.execute()
|
||||
val result = compiledInsertInfoQuery.generatedKeys
|
||||
if (result.next()) {
|
||||
info.uid = result.getInt(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun update(info: UserAccountInfo) {
|
||||
val conn = getConnection()
|
||||
conn.use {
|
||||
val compiledUpdateInfoQuery = it.prepareStatement(updateInfoQuery)
|
||||
val emptyInfo = UserAccountInfo.createDefault()
|
||||
if (info == emptyInfo) {
|
||||
throw IllegalStateException("Tried to store empty data!")
|
||||
}
|
||||
emptyInfo.username = info.username
|
||||
if (info == emptyInfo) {
|
||||
throw IllegalStateException("Tried to store empty data!")
|
||||
}
|
||||
emptyInfo.password = info.password
|
||||
if (info == emptyInfo) {
|
||||
throw IllegalStateException("Tried to store empty data!")
|
||||
}
|
||||
|
||||
compiledUpdateInfoQuery.setString(1, info.username)
|
||||
compiledUpdateInfoQuery.setString(2, info.password)
|
||||
compiledUpdateInfoQuery.setInt(3, info.rights)
|
||||
compiledUpdateInfoQuery.setInt(4, info.credits)
|
||||
compiledUpdateInfoQuery.setString(5, info.ip)
|
||||
compiledUpdateInfoQuery.setLong(6, info.muteEndTime)
|
||||
compiledUpdateInfoQuery.setLong(7, info.banEndTime)
|
||||
compiledUpdateInfoQuery.setString(8, info.contacts)
|
||||
compiledUpdateInfoQuery.setString(9, info.blocked)
|
||||
compiledUpdateInfoQuery.setString(10, info.clanName)
|
||||
compiledUpdateInfoQuery.setString(11, info.currentClan)
|
||||
compiledUpdateInfoQuery.setString(12, info.clanReqs)
|
||||
compiledUpdateInfoQuery.setLong(13, info.timePlayed)
|
||||
compiledUpdateInfoQuery.setLong(14, info.lastLogin)
|
||||
compiledUpdateInfoQuery.setBoolean(15, info.online)
|
||||
compiledUpdateInfoQuery.setInt(16, info.uid)
|
||||
compiledUpdateInfoQuery.execute()
|
||||
}
|
||||
}
|
||||
|
||||
override fun remove(info: UserAccountInfo) {
|
||||
val conn = getConnection()
|
||||
conn.use {
|
||||
val compiledRemoveInfoQuery = it.prepareStatement(removeInfoQuery)
|
||||
compiledRemoveInfoQuery.setString(1, info.username)
|
||||
compiledRemoveInfoQuery.execute()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val usernameQuery = "SELECT username FROM members WHERE username = ?;"
|
||||
private const val removeInfoQuery = "DELETE FROM members WHERE username = ?;"
|
||||
private const val accountInfoQuery = "SELECT " +
|
||||
"username," +
|
||||
"password," +
|
||||
"UID," +
|
||||
"rights," +
|
||||
"credits," +
|
||||
"ip," +
|
||||
"lastGameIp," +
|
||||
"muteTime," +
|
||||
"banTime," +
|
||||
"contacts," +
|
||||
"blocked," +
|
||||
"clanName," +
|
||||
"currentClan," +
|
||||
"clanReqs," +
|
||||
"timePlayed," +
|
||||
"lastLogin," +
|
||||
"online" +
|
||||
" FROM members WHERE username = ?;"
|
||||
private const val insertInfoQuery = "INSERT INTO members (" +
|
||||
"username," +
|
||||
"password," +
|
||||
"rights," +
|
||||
"credits," +
|
||||
"ip," +
|
||||
"lastGameIp," +
|
||||
"muteTime," +
|
||||
"banTime," +
|
||||
"contacts," +
|
||||
"blocked," +
|
||||
"clanName," +
|
||||
"currentClan," +
|
||||
"clanReqs," +
|
||||
"timePlayed," +
|
||||
"lastLogin," +
|
||||
"online" +
|
||||
") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);"
|
||||
|
||||
private const val updateInfoQuery = "UPDATE members SET " +
|
||||
"username = ?," +
|
||||
"password = ?," +
|
||||
"rights = ?," +
|
||||
"credits = ?," +
|
||||
"lastGameIp = ?," +
|
||||
"muteTime = ?," +
|
||||
"banTime = ?," +
|
||||
"contacts = ?," +
|
||||
"blocked = ?," +
|
||||
"clanName = ?," +
|
||||
"currentClan = ?," +
|
||||
"clanReqs = ?," +
|
||||
"timePlayed = ?," +
|
||||
"lastLogin = ?," +
|
||||
"online = ?" +
|
||||
" WHERE uid = ?;"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user