Auth fixed

This commit is contained in:
2026-05-16 11:33:58 +03:00
parent 655a0d50d6
commit 6ba78edce4
23 changed files with 134 additions and 2253 deletions
-2126
View File
File diff suppressed because it is too large Load Diff
@@ -21,6 +21,7 @@ class SyncCommand (
jwt = client.getToken(),
)
val res = client.sendRequest(initialRequest)
print(res)
if (res == null) {
return ("[!] Сервер недоступен. Попробуйте позже.")
@@ -146,17 +146,12 @@ class NetworkManager(
}
fun setToken(newToken: String) {
logger.warn {"New token set"}
token = newToken
}
fun getToken(): String {
logger.warn {"token used"}
return token
}
fun getToken(): String = token
fun clearToken() {
logger.warn {"clear token"}
token = "no token"
}
+1 -1
View File
@@ -9,5 +9,5 @@ interface Command {
val args: List<CommandArgType>
val type: CommandType
fun execute(args: List<String>, humanBeing: HumanBeing? = null): Response
fun execute(args: List<String>, humanBeing: HumanBeing? = null, userId: String = ""): Response
}
+1 -2
View File
@@ -67,7 +67,7 @@ fun registerServerCommands(
AddCommand(manager, repository),
UpdateCommand(manager, repository),
RemoveByIdCommand(manager, repository),
ClearCommand(manager),
ClearCommand(manager, repository),
AddIfMaxCommand(manager, repository),
AddIfMinCommand(manager, repository),
HistoryCommand(invoker),
@@ -75,7 +75,6 @@ fun registerServerCommands(
SumOfMinutesCommand(manager),
MinByNameCommand(manager),
PrintDescendingMinutesCommand(manager),
ExitCommand()
).forEach { invoker.register(it)}
logger.info{ " [startup] Registered commands: ${invoker.getCommands().keys}" }
}
+20 -12
View File
@@ -13,30 +13,38 @@ import java.time.ZonedDateTime
class JWTManager (
private val jwtSecret: String = "random",
private val issuer: String = "aklivtsov.tech",
private val subject: String = "f1e33ab3-027f-47c5-bb07-8dd8ab37a2d3"
) {
private val signer: Signer = HMACSigner.newSHA256Signer(jwtSecret)
private val verifier: Verifier = HMACVerifier.newVerifier(jwtSecret)
fun createJWT(expiredAfterMinutes: Long): String {
fun createJWT(expiredAfterMinutes: Long, userId: String): String {
val jwt: JWT = JWT()
.setIssuer(issuer)
.setIssuedAt(ZonedDateTime.now(ZoneOffset.UTC))
.setSubject(subject)
.setSubject(userId)
.setExpiration(ZonedDateTime.now(ZoneOffset.UTC).plusMinutes(expiredAfterMinutes))
return JWT.getEncoder().encode(jwt, signer)
}
fun validateJWT(jwt: String): Boolean {
val decoded: JWT
try {
decoded = JWT.getDecoder().decode(jwt, verifier)
} catch (e: JWTExpiredException) {
return false
} catch (e: JWTUnavailableForProcessingException) {
return false
fun getUserId(jwt: String): String? {
return try {
JWT.getDecoder().decode(jwt, verifier).subject
} catch (e: Exception) {
null
}
}
fun validateJWT(jwt: String, userId: String): Boolean {
return try {
JWT.getDecoder().decode(jwt, verifier)
true
} catch (e: JWTExpiredException) {
false
} catch (e: JWTUnavailableForProcessingException) {
false
} catch (e: Exception) {
false
}
return decoded.subject == subject
}
}
@@ -6,6 +6,7 @@ import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing
import network.Response
import java.util.UUID
@Serializable
class AddCommand(
@@ -17,13 +18,14 @@ class AddCommand(
override val args = listOf(CommandArgType.NONE)
override val type = CommandType.MODEL
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
humanBeing.id = repository.write(humanBeing)
humanBeing.id ?: return Response(false, "Не удалось добавить элемент")
val secured = humanBeing.copy(ownerId = UUID.fromString(userId))
secured.id = repository.write(secured)
secured.id ?: return Response(false, "Не удалось добавить элемент")
return if (manager.add(humanBeing))
Response(true, "Элемент добавлен: ${humanBeing.name} [${humanBeing.id}]")
return if (manager.add(secured))
Response(true, "Элемент добавлен: ${secured.name} [${secured.id}]")
else
Response(false, "Элемент уже существует в коллекции.")
@@ -6,6 +6,7 @@ import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing
import network.Response
import java.util.UUID
@Serializable
class AddIfMaxCommand(
@@ -17,13 +18,14 @@ class AddIfMaxCommand(
override val type = CommandType.MODEL
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
val secured = humanBeing.copy(ownerId = UUID.fromString(userId))
val max = manager.getMax()
return if (max == null || humanBeing > max) {
humanBeing.id = repository.write(humanBeing)
humanBeing.id ?: return Response(false, "Не удалось добавить элемент")
manager.add(humanBeing)
return if (max == null || secured > max) {
secured.id = repository.write(secured)
secured.id ?: return Response(false, "Не удалось добавить элемент")
manager.add(secured)
Response(true, "Элемент добавлен (превышает максимум).")
} else {
Response(false, "Элемент не добавлен: не превышает текущий максимум (${max.name}).")
@@ -6,6 +6,7 @@ import models.HumanBeing
import kotlinx.serialization.Serializable
import kotlinx.serialization.Contextual
import network.Response
import java.util.UUID
@Serializable
class AddIfMinCommand(
@@ -17,13 +18,14 @@ class AddIfMinCommand(
override val type = CommandType.MODEL
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
val secured = humanBeing.copy(ownerId = UUID.fromString(userId))
val min = manager.getMin()
return if (min == null || humanBeing < min) {
humanBeing.id = repository.write(humanBeing)
humanBeing.id ?: return Response(false, "Не удалось добавить элемент")
manager.add(humanBeing)
return if (min == null || secured < min) {
secured.id = repository.write(secured)
secured.id ?: return Response(false, "Не удалось добавить элемент")
manager.add(secured)
Response(true, "Элемент добавлен (меньше минимума).")
} else {
Response(false, "Элемент не добавлен: не меньше текущего минимума (${min.name}).")
@@ -1,20 +1,31 @@
package commands
import collection.CollectionManager
import database.Repository
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing
import network.Response
import java.util.UUID
@Serializable
class ClearCommand(@Contextual private val manager: CollectionManager) : Command {
class ClearCommand(
@Contextual private val manager: CollectionManager,
@Contextual private var repository: Repository
) : Command {
override val name = "clear"
override val description = "очистить коллекцию"
override val args = listOf(CommandArgType.NONE)
override val type = CommandType.SIMPLE
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
manager.clear()
return Response(true, "Коллекция очищена.")
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
if (userId.isBlank()) return Response(false, "Не удалось определить пользователя.")
val ownerId = UUID.fromString(userId)
val deletedCount = repository.deleteByOwner(ownerId)
val toRemove = manager.getAll().filter { it.ownerId == ownerId }
toRemove.forEach { manager.removeById(it.id!!) }
return Response(true, "Удалено объектов: $deletedCount.")
}
}
@@ -1,24 +0,0 @@
package commands
import models.HumanBeing
import network.Response
/**
* Базовый интерфейс команды.
*
* Теперь execute() возвращает String — результат для отправки клиенту.
* Зависимость от IOManager полностью убрана из команд.
*/
interface Command {
val name: String
val description: String
val type : CommandType
val args : List<CommandArgType>
/**
* @param args строковые аргументы (UUID и т.п.)
* @param humanBeing объект из запроса — только для add/update/add_if_max/add_if_min
* @return строка-результат для клиента
*/
fun execute(args: List<String>, humanBeing: HumanBeing? = null): Response
}
@@ -1,18 +0,0 @@
package commands
/**
* Завершает программу без сохранения.
*/
import kotlinx.serialization.Serializable
import models.HumanBeing
import network.Response
@Serializable
class ExitCommand() : Command {
override val name = "exit"
override val description = "завершить программу"
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response = Response(true, "")
}
@@ -13,7 +13,7 @@ class HistoryCommand(@Contextual private val invoker: CommandInvoker) : Command
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
val history = invoker.getHistory()
if (history.isEmpty()) return Response(true, "История пуста.")
return Response(true, history.mapIndexed { i, cmd -> " ${i + 1}. $cmd" }.joinToString("\n"))
@@ -13,6 +13,6 @@ class InfoCommand(@Contextual private val manager: CollectionManager) : Command
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response =
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response =
Response(true, manager.getInfo())
}
@@ -16,6 +16,6 @@ class MinByNameCommand(@Contextual private val manager: CollectionManager) : Com
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response =
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response =
Response(true, manager.minByName()?.toString() ?: "Коллекция пуста.")
}
@@ -16,7 +16,7 @@ class PrintDescendingMinutesCommand(@Contextual private val manager: CollectionM
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
val values = manager.getMinutesOfWaitingDescending()
return if (values.isEmpty()) Response(true, "Коллекция пуста.")
else Response(true, values.joinToString("\n"))
@@ -18,7 +18,7 @@ class RemoveByIdCommand(
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.STRING)
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
if (args.isEmpty()) return Response(false, "[Ошибка] Укажите id. Пример: remove_by_id <uuid>")
val id = try {
@@ -27,6 +27,12 @@ class RemoveByIdCommand(
return Response(false, "[Ошибка] Некорректный UUID: '${args[0]}'")
}
val existing = manager.getById(id)
?: return Response(false, "Элемент не найден.")
if (existing.ownerId.toString() != userId)
return Response(false, "Нет прав: объект принадлежит другому пользователю.")
if (repository.delete(id)) {
manager.removeById(id)
return Response(true, "Элемент удалён.")
@@ -13,7 +13,7 @@ class ShowCommand(@Contextual private val manager: CollectionManager) : Command
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
if (manager.isEmpty()) return Response(true, "Коллекция пуста.")
return Response(true, manager.getAll().joinToString("\n") { it.toString() })
}
@@ -16,6 +16,6 @@ class SumOfMinutesCommand(@Contextual private val manager: CollectionManager) :
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response =
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response =
Response(true, "Сумма minutesOfWaiting: ${manager.sumOfMinutesOfWaiting()}")
}
@@ -16,12 +16,12 @@ class SyncCommand(
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
val commands = mutableMapOf<String, Command>()
for (command in invoker.getCommands().values) {
if (command.name == "sync" || command.name == "exit") {
if (command.name == "sync") {
continue
}
commands[command.name] = command
@@ -18,7 +18,7 @@ class UpdateCommand(
override val type = CommandType.MODEL
override val args = listOf(CommandArgType.STRING)
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
override fun execute(args: List<String>, humanBeing: HumanBeing?, userId: String): Response {
if (args.isEmpty()) return Response(false, "[Ошибка] Укажите id. Пример: update <UUID>")
humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
@@ -28,6 +28,12 @@ class UpdateCommand(
return Response(false, "[Ошибка] Некорректный UUID: '${args[0]}'")
}
val existing = manager.getById(id)
?: return Response(false, "Элемент не найден.")
if (existing.ownerId.toString() != userId)
return Response(false, "Нет прав: объект принадлежит другому пользователю.")
if (repository.update(id, humanBeing)) {
manager.update(id, humanBeing)
return Response(true, "Элемент обновлён.")
+45 -29
View File
@@ -142,6 +142,7 @@ class Repository(
return allGood
}
// nuke 'em
fun delete(id: UUID): Boolean {
var allGood = true
try {
@@ -154,45 +155,60 @@ class Repository(
return allGood
}
fun login (username: String, password: String): Pair<String, String>? {
val newToken = jwtManager.createJWT(60)
fun login(username: String, password: String): Pair<String, String>? {
var id: String? = null
try {
transaction {
id = UserTable
.select(UserTable.id)
.where{ (UserTable.username eq username) and (UserTable.password eq password) }
.single()[UserTable.id].toString()
UserTable
.update({ UserTable.id eq UUID.fromString(id) }) {
it[UserTable.username] = newToken
}
}
} catch (e: Exception) {
return null
transaction {
id = UserTable.select(UserTable.id)
.where { (UserTable.username eq username) and (UserTable.password eq password) }
.singleOrNull()
?.get(UserTable.id)?.toString()
}
return if (id != null) Pair(newToken, id) else null
if (id == null) return null
val newToken = jwtManager.createJWT(60, id!!)
transaction {
UserTable.update({ UserTable.id eq UUID.fromString(id) }) {
it[UserTable.token] = newToken
}
}
return Pair(newToken, id!!)
}
fun register(username: String, password: String): Pair<String, String>? {
val newToken = jwtManager.createJWT(60)
var id: String? = null
transaction {
val check = UserTable
.select(UserTable.username)
.where { (UserTable.username eq username) or (UserTable.password eq password) }
val check = UserTable.select(UserTable.username)
.where { UserTable.username eq username }
.singleOrNull()
if (check == null) {
id = UserTable
.insertAndGetId {
it[UserTable.username] = username
it[UserTable.password] = password
it[UserTable.token] = newToken
}.value.toString()
id = UserTable.insertAndGetId {
it[UserTable.username] = username
it[UserTable.password] = password
}.value.toString()
}
}
return if (id != null) Pair(newToken, id) else null
if (id == null) return null
val newToken = jwtManager.createJWT(60, id!!)
transaction {
UserTable.update({ UserTable.id eq UUID.fromString(id) }) {
it[UserTable.token] = newToken
}
}
return Pair(newToken, id!!)
}
fun deleteByOwner(ownerId: UUID): Int {
var deletedCount = 0
transaction {
val humanIds = HumanBeingTable
.select(HumanBeingTable.id, HumanBeingTable.coordinates, HumanBeingTable.car)
.where { HumanBeingTable.owner_id eq ownerId }
.toList()
val coordIds = humanIds.map { it[HumanBeingTable.coordinates] }
val carIds = humanIds.map { it[HumanBeingTable.car] }
deletedCount = HumanBeingTable.deleteWhere { HumanBeingTable.owner_id eq ownerId }
CoordinatesTable.deleteWhere { CoordinatesTable.id inList coordIds }
CarTable.deleteWhere { CarTable.id inList carIds }
}
return deletedCount
}
}
@@ -30,18 +30,19 @@ class CommandInvoker(
val command = commands[request.commandName]
?: return Response(false, "Неизвестная команда: '${request.commandName}'")
if (request.jwt == "no token") {
if (request.jwt == "no token")
return Response(false, "Нет токена. Войдите или зарегистрируйтесь")
}
val check = jwtManager.validateJWT(request.jwt)
if (!check) {
if (!jwtManager.validateJWT(request.jwt, request.jwt))
return Response(false, "Время истекло. Выполните вход заново.")
}
val userId = jwtManager.getUserId(request.jwt)
?: return Response(false, "Не удалось определить пользователя")
addToHistory(request.commandName)
return try {
command.execute(request.args, request.humanBeing)
command.execute(request.args, request.humanBeing, userId)
} catch (e: Exception) {
Response(false, "[Ошибка выполнения] ${e.message}")
}