From c718bc2c6ab17fff06b68105c6a66a3da7aa50c5 Mon Sep 17 00:00:00 2001 From: Infendro Date: Thu, 19 Feb 2026 14:54:39 +0100 Subject: [PATCH] Upgrade dependencies --- backend/build.gradle.kts | 18 +++-- backend/gradle/libs.versions.toml | 39 ++++----- .../com/infendro/account/config/Database.kt | 10 +-- .../com/infendro/account/config/Security.kt | 2 +- .../account/dto/request/PostAccountRequest.kt | 12 ++- .../PutAccountCurrentPasswordRequest.kt | 10 +-- .../PutAccountCurrentUsernameRequest.kt | 10 +-- .../account/dto/response/AccountResponse.kt | 14 ++-- .../account/dto/response/SessionResponse.kt | 10 +-- .../kotlin/com/infendro/account/model/Role.kt | 15 +--- .../infendro/account/model/entity/Access.kt | 8 +- .../infendro/account/model/entity/Account.kt | 12 +-- .../infendro/account/model/entity/Session.kt | 10 +-- .../account/model/repository/Repository.kt | 80 +++++-------------- .../infendro/account/service/AccessService.kt | 17 ++-- .../account/service/AccountService.kt | 73 +++++------------ .../account/service/SessionService.kt | 35 +++----- .../com/infendro/account/util/Hasher.kt | 8 +- .../kotlin/com/infendro/account/util/OTP.kt | 15 ++-- .../infendro/account/util/PasswordHasher.kt | 23 ++++++ .../com/infendro/account/util/SecureHasher.kt | 28 ------- .../infendro/account/util/TokenGenerator.kt | 5 +- 22 files changed, 166 insertions(+), 288 deletions(-) create mode 100644 backend/src/main/kotlin/com/infendro/account/util/PasswordHasher.kt delete mode 100644 backend/src/main/kotlin/com/infendro/account/util/SecureHasher.kt diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index 252bda1..c276289 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -1,3 +1,5 @@ +import com.infendro.plugin.infendro + group = "com.infendro.account" version = "0.0.1" @@ -13,7 +15,7 @@ plugins { } repositories { - maven("https://git.infendro.com/api/packages/Infendro/maven") + infendro() mavenCentral() } @@ -27,23 +29,25 @@ dependencies { implementation(libs.ktor.server.contentNegotiation) implementation(libs.ktor.server.serialization) implementation(libs.ktor.server.validation) + implementation(libs.exposed) implementation(libs.exposed.dao) implementation(libs.exposed.jdbc) implementation(libs.migration) implementation(libs.postgres) - implementation(libs.prng) - implementation(libs.otp) - implementation(libs.kdf) - implementation(libs.hash) - implementation(libs.bytearray) + + implementation(libs.bytes) implementation(libs.encoding) + implementation(libs.hash) + implementation(libs.kdf) + implementation(libs.otp) + implementation(libs.prng) + implementation(libs.logback) } kotlin { compilerOptions { - freeCompilerArgs.add("-opt-in=kotlin.ExperimentalUnsignedTypes") freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") } } diff --git a/backend/gradle/libs.versions.toml b/backend/gradle/libs.versions.toml index 37ededa..46c5b40 100644 --- a/backend/gradle/libs.versions.toml +++ b/backend/gradle/libs.versions.toml @@ -1,17 +1,20 @@ [versions] -infendro = "1.0.0" -kotlin = "2.2.21" -ktor = "3.3.3" -exposed = "0.61.0" -migration = "1.1.0" -postgres = "42.7.8" -prng = "1.2.0" -otp = "1.2.0" -kdf = "1.0.3" -hash = "1.3.2" -bytearray = "1.2.4" -encoding = "1.2.0" -logback = "1.5.22" +infendro = "1.1.0" +kotlin = "2.3.0" +ktor = "3.4.0" + +exposed = "1.0.0" +migration = "1.2.0" +postgres = "42.7.10" + +bytes = "1.4.0" +encoding = "1.3.0" +hash = "1.6.0" +kdf = "1.2.0" +otp = "1.3.0" +prng = "1.3.0" + +logback = "1.5.32" [plugins] infendro = { id = "com.infendro.plugin", version.ref = "infendro" } @@ -36,11 +39,11 @@ exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "e migration = { module = "com.infendro:migration-ktor", version.ref = "migration" } postgres = { module = "org.postgresql:postgresql", version.ref = "postgres" } -prng = { module = "com.infendro:prng", version.ref = "prng" } -otp = { module = "com.infendro:otp", version.ref = "otp" } -kdf = { module = "com.infendro:kdf", version.ref = "kdf" } -hash = { module = "com.infendro:hash", version.ref = "hash" } -bytearray = { module = "com.infendro:bytearray", version.ref = "bytearray" } +bytes = { module = "com.infendro:bytes", version.ref = "bytes" } encoding = { module = "com.infendro:encoding", version.ref = "encoding" } +hash = { module = "com.infendro:hash", version.ref = "hash" } +kdf = { module = "com.infendro:kdf", version.ref = "kdf" } +otp = { module = "com.infendro:otp", version.ref = "otp" } +prng = { module = "com.infendro:prng", version.ref = "prng" } logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } diff --git a/backend/src/main/kotlin/com/infendro/account/config/Database.kt b/backend/src/main/kotlin/com/infendro/account/config/Database.kt index 3413788..2449ab5 100644 --- a/backend/src/main/kotlin/com/infendro/account/config/Database.kt +++ b/backend/src/main/kotlin/com/infendro/account/config/Database.kt @@ -2,10 +2,10 @@ package com.infendro.account.config import com.infendro.account.model.Role import com.infendro.account.model.repository.AccountRepository -import com.infendro.account.util.SecureHasher +import com.infendro.account.util.PasswordHasher import com.infendro.ktor.migration.Migration import io.ktor.server.application.* -import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.v1.jdbc.Database fun Application.configureDatabase() { val config = configuration @@ -55,14 +55,14 @@ fun Application.configureDatabase() { REFERENCES account (id) ON DELETE CASCADE NOT NULL ); - """.trimIndent() + """.trimIndent(), ) AccountRepository.insert { - val salt = SecureHasher.generateSalt() + val salt = PasswordHasher.generateSalt() this.username = "infendro" - this.passwordHash = SecureHasher.hash("password", salt) + this.passwordHash = PasswordHasher.hash("password", salt) this.passwordSalt = salt this.secret = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA====" this.role = Role.OWNER diff --git a/backend/src/main/kotlin/com/infendro/account/config/Security.kt b/backend/src/main/kotlin/com/infendro/account/config/Security.kt index 5e082ae..c944f37 100644 --- a/backend/src/main/kotlin/com/infendro/account/config/Security.kt +++ b/backend/src/main/kotlin/com/infendro/account/config/Security.kt @@ -11,7 +11,7 @@ import io.ktor.server.application.* import io.ktor.server.auth.* import io.ktor.server.sessions.* import kotlinx.serialization.Serializable -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.v1.core.eq import kotlin.time.Duration.Companion.days @Serializable diff --git a/backend/src/main/kotlin/com/infendro/account/dto/request/PostAccountRequest.kt b/backend/src/main/kotlin/com/infendro/account/dto/request/PostAccountRequest.kt index 63ea3b3..5141fc6 100644 --- a/backend/src/main/kotlin/com/infendro/account/dto/request/PostAccountRequest.kt +++ b/backend/src/main/kotlin/com/infendro/account/dto/request/PostAccountRequest.kt @@ -18,12 +18,10 @@ data class PostAccountRequest( ) } -fun RequestValidationConfig.validatePostAccountRequest() { - validate { request -> - when { - !USERNAME.matches(request.username) -> Invalid("") - !PASSWORD.matches(request.password) -> Invalid("") - else -> Valid - } +fun RequestValidationConfig.validatePostAccountRequest() = validate { request -> + when { + !USERNAME.matches(request.username) -> Invalid("") + !PASSWORD.matches(request.password) -> Invalid("") + else -> Valid } } diff --git a/backend/src/main/kotlin/com/infendro/account/dto/request/PutAccountCurrentPasswordRequest.kt b/backend/src/main/kotlin/com/infendro/account/dto/request/PutAccountCurrentPasswordRequest.kt index dd1db19..70126dd 100644 --- a/backend/src/main/kotlin/com/infendro/account/dto/request/PutAccountCurrentPasswordRequest.kt +++ b/backend/src/main/kotlin/com/infendro/account/dto/request/PutAccountCurrentPasswordRequest.kt @@ -11,11 +11,9 @@ data class PutAccountCurrentPasswordRequest( val otp: String, ) -fun RequestValidationConfig.validatePutAccountCurrentPasswordRequest() { - validate { request -> - when { - !PASSWORD.matches(request.password) -> Invalid("") - else -> Valid - } +fun RequestValidationConfig.validatePutAccountCurrentPasswordRequest() = validate { request -> + when { + !PASSWORD.matches(request.password) -> Invalid("") + else -> Valid } } diff --git a/backend/src/main/kotlin/com/infendro/account/dto/request/PutAccountCurrentUsernameRequest.kt b/backend/src/main/kotlin/com/infendro/account/dto/request/PutAccountCurrentUsernameRequest.kt index 3850444..088fc9b 100644 --- a/backend/src/main/kotlin/com/infendro/account/dto/request/PutAccountCurrentUsernameRequest.kt +++ b/backend/src/main/kotlin/com/infendro/account/dto/request/PutAccountCurrentUsernameRequest.kt @@ -11,11 +11,9 @@ data class PutAccountCurrentUsernameRequest( val otp: String, ) -fun RequestValidationConfig.validatePutAccountCurrentUsernameRequest() { - validate { request -> - when { - !USERNAME.matches(request.username) -> Invalid("") - else -> Valid - } +fun RequestValidationConfig.validatePutAccountCurrentUsernameRequest() = validate { request -> + when { + !USERNAME.matches(request.username) -> Invalid("") + else -> Valid } } diff --git a/backend/src/main/kotlin/com/infendro/account/dto/response/AccountResponse.kt b/backend/src/main/kotlin/com/infendro/account/dto/response/AccountResponse.kt index aa0cc6d..7bfc7d3 100644 --- a/backend/src/main/kotlin/com/infendro/account/dto/response/AccountResponse.kt +++ b/backend/src/main/kotlin/com/infendro/account/dto/response/AccountResponse.kt @@ -13,11 +13,9 @@ data class AccountResponse( val sessions: List, ) -fun AccountEntity.toResponse(): AccountResponse { - return AccountResponse( - id = id.value, - username = username, - role = role, - sessions = sessions().map(SessionEntity::toResponse), - ) -} +fun AccountEntity.toResponse() = AccountResponse( + id = id.value, + username = username, + role = role, + sessions = sessions().map(SessionEntity::toResponse), +) diff --git a/backend/src/main/kotlin/com/infendro/account/dto/response/SessionResponse.kt b/backend/src/main/kotlin/com/infendro/account/dto/response/SessionResponse.kt index abfd454..043383d 100644 --- a/backend/src/main/kotlin/com/infendro/account/dto/response/SessionResponse.kt +++ b/backend/src/main/kotlin/com/infendro/account/dto/response/SessionResponse.kt @@ -9,9 +9,7 @@ data class SessionResponse( val accountId: Long, ) -fun SessionEntity.toResponse(): SessionResponse { - return SessionResponse( - id = id.value, - accountId = account().id.value, - ) -} +fun SessionEntity.toResponse() = SessionResponse( + id = id.value, + accountId = account().id.value, +) diff --git a/backend/src/main/kotlin/com/infendro/account/model/Role.kt b/backend/src/main/kotlin/com/infendro/account/model/Role.kt index e14191a..18694ae 100644 --- a/backend/src/main/kotlin/com/infendro/account/model/Role.kt +++ b/backend/src/main/kotlin/com/infendro/account/model/Role.kt @@ -13,16 +13,5 @@ enum class Role( } } -infix fun List.except( - roles: List, -): List { - return filterNot { it in roles } - .toList() -} - -infix fun List.except( - role: Role, -): List { - return filterNot { it == role } - .toList() -} +infix fun List.except(roles: List) = filterNot { it in roles } +infix fun List.except(role: Role) = filterNot { it == role } diff --git a/backend/src/main/kotlin/com/infendro/account/model/entity/Access.kt b/backend/src/main/kotlin/com/infendro/account/model/entity/Access.kt index a994a9d..77ab955 100644 --- a/backend/src/main/kotlin/com/infendro/account/model/entity/Access.kt +++ b/backend/src/main/kotlin/com/infendro/account/model/entity/Access.kt @@ -1,9 +1,9 @@ package com.infendro.account.model.entity -import org.jetbrains.exposed.dao.LongEntity -import org.jetbrains.exposed.dao.LongEntityClass -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.dao.id.LongIdTable +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.dao.id.LongIdTable +import org.jetbrains.exposed.v1.dao.LongEntity +import org.jetbrains.exposed.v1.dao.LongEntityClass object AccessTable : LongIdTable("access", "id") { val tokenHash = text("token_hash") diff --git a/backend/src/main/kotlin/com/infendro/account/model/entity/Account.kt b/backend/src/main/kotlin/com/infendro/account/model/entity/Account.kt index acee97a..e6f2e6d 100644 --- a/backend/src/main/kotlin/com/infendro/account/model/entity/Account.kt +++ b/backend/src/main/kotlin/com/infendro/account/model/entity/Account.kt @@ -1,11 +1,11 @@ package com.infendro.account.model.entity import com.infendro.account.model.Role -import org.jetbrains.exposed.dao.LongEntity -import org.jetbrains.exposed.dao.LongEntityClass -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.dao.id.LongIdTable -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.dao.id.LongIdTable +import org.jetbrains.exposed.v1.dao.LongEntity +import org.jetbrains.exposed.v1.dao.LongEntityClass +import org.jetbrains.exposed.v1.jdbc.transactions.transaction object AccountTable : LongIdTable("account", "id") { val username = text("username") @@ -20,7 +20,7 @@ object AccountTable : LongIdTable("account", "id") { else -> throw Error() } }, - toDb = { it.name } + toDb = { it.name }, ) } diff --git a/backend/src/main/kotlin/com/infendro/account/model/entity/Session.kt b/backend/src/main/kotlin/com/infendro/account/model/entity/Session.kt index 0e109c9..67bfa12 100644 --- a/backend/src/main/kotlin/com/infendro/account/model/entity/Session.kt +++ b/backend/src/main/kotlin/com/infendro/account/model/entity/Session.kt @@ -1,10 +1,10 @@ package com.infendro.account.model.entity -import org.jetbrains.exposed.dao.LongEntity -import org.jetbrains.exposed.dao.LongEntityClass -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.dao.id.LongIdTable -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.dao.id.LongIdTable +import org.jetbrains.exposed.v1.dao.LongEntity +import org.jetbrains.exposed.v1.dao.LongEntityClass +import org.jetbrains.exposed.v1.jdbc.transactions.transaction object SessionTable : LongIdTable("session", "id") { val tokenHash = text("token_hash") diff --git a/backend/src/main/kotlin/com/infendro/account/model/repository/Repository.kt b/backend/src/main/kotlin/com/infendro/account/model/repository/Repository.kt index ed4d3d5..606f037 100644 --- a/backend/src/main/kotlin/com/infendro/account/model/repository/Repository.kt +++ b/backend/src/main/kotlin/com/infendro/account/model/repository/Repository.kt @@ -1,9 +1,9 @@ package com.infendro.account.model.repository -import org.jetbrains.exposed.dao.LongEntity -import org.jetbrains.exposed.dao.LongEntityClass -import org.jetbrains.exposed.sql.Op -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.Op +import org.jetbrains.exposed.v1.dao.LongEntity +import org.jetbrains.exposed.v1.dao.LongEntityClass +import org.jetbrains.exposed.v1.jdbc.transactions.transaction abstract class Repository( private val entityClass: LongEntityClass, @@ -12,113 +12,75 @@ abstract class Repository( entityClass.all().toList() } - fun all( - where: Op, - ): List = transaction { + fun all(where: Op): List = transaction { entityClass.find(where).toList() } - fun all( - where: () -> Op, - ): List { + fun all(where: () -> Op): List { return all(where()) } - fun single( - where: Op, - ): ENTITY = transaction { + fun single(where: Op): ENTITY = transaction { entityClass.find(where).single() } - fun single( - where: () -> Op, - ): ENTITY { + fun single(where: () -> Op): ENTITY { return single(where()) } - fun singleOrNull( - where: Op, - ): ENTITY? = transaction { + fun singleOrNull(where: Op): ENTITY? = transaction { entityClass.find(where).singleOrNull() } - fun singleOrNull( - where: () -> Op, - ): ENTITY? { + fun singleOrNull(where: () -> Op): ENTITY? { return singleOrNull(where()) } - fun exists( - where: Op, - ): Boolean = transaction { + fun exists(where: Op): Boolean = transaction { entityClass.find(where).any() } - fun exists( - where: () -> Op, - ): Boolean { + fun exists(where: () -> Op): Boolean { return exists(where()) } - fun insert( - block: ENTITY.() -> Unit, - ): Unit = transaction { + fun insert(block: ENTITY.() -> Unit): Unit = transaction { entityClass.new(block) } - fun update( - entity: ENTITY, - block: ENTITY.() -> Unit, - ): Unit = transaction { + fun update(entity: ENTITY, block: ENTITY.() -> Unit): Unit = transaction { entity.block() } - fun update( - entities: Iterable, - block: ENTITY.() -> Unit, - ) { + fun update(entities: Iterable, block: ENTITY.() -> Unit) { for (entity in entities) { update(entity, block) } } - fun update( - where: Op, - block: ENTITY.() -> Unit, - ) { + fun update(where: Op, block: ENTITY.() -> Unit) { update(all(where), block) } - fun update( - where: () -> Op, - block: ENTITY.() -> Unit, - ) { + fun update(where: () -> Op, block: ENTITY.() -> Unit) { update(where(), block) } - fun delete( - entity: ENTITY, - ): Unit = transaction { + fun delete(entity: ENTITY): Unit = transaction { entity.delete() } - fun delete( - entities: Iterable, - ) { + fun delete(entities: Iterable) { for (entity in entities) { delete(entity) } } - fun delete( - where: Op, - ) { + fun delete(where: Op) { delete(all(where)) } - fun delete( - where: () -> Op, - ) { + fun delete(where: () -> Op) { delete(where()) } } diff --git a/backend/src/main/kotlin/com/infendro/account/service/AccessService.kt b/backend/src/main/kotlin/com/infendro/account/service/AccessService.kt index f6c2e0a..edc565e 100644 --- a/backend/src/main/kotlin/com/infendro/account/service/AccessService.kt +++ b/backend/src/main/kotlin/com/infendro/account/service/AccessService.kt @@ -11,12 +11,10 @@ import com.infendro.account.model.entity.AccessTable import com.infendro.account.model.repository.AccessRepository import com.infendro.account.util.Hasher import com.infendro.account.util.TokenGenerator -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.v1.core.eq object AccessService { - fun post( - principal: AuthenticationPrincipal, - ): PostAccessResponse { + fun post(principal: AuthenticationPrincipal): PostAccessResponse { if (principal.role != OWNER) throw ForbiddenException() @@ -26,13 +24,11 @@ object AccessService { } return PostAccessResponse( - token = token + token = token, ) } - fun getAll( - principal: AuthenticationPrincipal, - ): List { + fun getAll(principal: AuthenticationPrincipal): List { if (principal.role != OWNER) throw ForbiddenException() @@ -41,10 +37,7 @@ object AccessService { .map { it.toResponse() } } - fun deleteId( - principal: AuthenticationPrincipal, - id: Long, - ) { + fun deleteId(principal: AuthenticationPrincipal, id: Long) { if (principal.role != OWNER) throw ForbiddenException() diff --git a/backend/src/main/kotlin/com/infendro/account/service/AccountService.kt b/backend/src/main/kotlin/com/infendro/account/service/AccountService.kt index ceabfb5..f57a4db 100644 --- a/backend/src/main/kotlin/com/infendro/account/service/AccountService.kt +++ b/backend/src/main/kotlin/com/infendro/account/service/AccountService.kt @@ -16,14 +16,12 @@ import com.infendro.account.model.repository.AccountRepository import com.infendro.account.model.repository.SessionRepository import com.infendro.account.util.Hasher import com.infendro.account.util.OTP -import com.infendro.account.util.SecureHasher +import com.infendro.account.util.PasswordHasher import io.ktor.server.sessions.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.v1.core.eq object AccountService { - fun post( - request: PostAccountRequest, - ): PostAccountResponse { + fun post(request: PostAccountRequest): PostAccountResponse { val access = AccessRepository .singleOrNull { AccessTable.tokenHash eq Hasher.hash(request.access.token) } ?: throw UnauthorizedException() @@ -36,23 +34,21 @@ object AccountService { val secret = OTP.generateSecret() AccountRepository.insert { - val salt = SecureHasher.generateSalt() + val salt = PasswordHasher.generateSalt() this.username = request.username - this.passwordHash = SecureHasher.hash(request.password, salt) + this.passwordHash = PasswordHasher.hash(request.password, salt) this.passwordSalt = salt this.secret = secret this.role = USER } return PostAccountResponse( - secret = secret + secret = secret, ) } - fun getAll( - principal: AuthenticationPrincipal, - ): List { + fun getAll(principal: AuthenticationPrincipal): List { if (principal.role != OWNER) throw ForbiddenException() @@ -61,26 +57,18 @@ object AccountService { .map(AccountEntity::toResponse) } - fun getCurrent( - principal: AuthenticationPrincipal, - ): AccountResponse { + fun getCurrent(principal: AuthenticationPrincipal): AccountResponse { return principal.account.toResponse() } - fun deleteCurrent( - principal: AuthenticationPrincipal, - ) { + fun deleteCurrent(principal: AuthenticationPrincipal) { if (principal.role == OWNER) throw ForbiddenException() AccountRepository.delete(principal.account) } - fun putCurrentUsername( - principal: AuthenticationPrincipal, - sessions: CurrentSession, - request: PutAccountCurrentUsernameRequest, - ) { + fun putCurrentUsername(principal: AuthenticationPrincipal, sessions: CurrentSession, request: PutAccountCurrentUsernameRequest) { if (!OTP.verify(principal.account.secret, request.otp)) throw UnauthorizedException() @@ -92,18 +80,14 @@ object AccountService { sessions.clear() } - fun putCurrentPassword( - principal: AuthenticationPrincipal, - sessions: CurrentSession, - request: PutAccountCurrentPasswordRequest, - ) { + fun putCurrentPassword(principal: AuthenticationPrincipal, sessions: CurrentSession, request: PutAccountCurrentPasswordRequest) { if (!OTP.verify(principal.account.secret, request.otp)) throw UnauthorizedException() AccountRepository.update(principal.account) { - val salt = SecureHasher.generateSalt() + val salt = PasswordHasher.generateSalt() - this.passwordHash = SecureHasher.hash(request.password, salt) + this.passwordHash = PasswordHasher.hash(request.password, salt) this.passwordSalt = salt } SessionRepository.delete(principal.account.sessions()) @@ -111,12 +95,8 @@ object AccountService { sessions.clear() } - fun postCurrentSecret( - principal: AuthenticationPrincipal, - sessions: CurrentSession, - request: PostAccountCurrentSecretRequest, - ): PostAccountCurrentSecretResponse { - if (principal.account.passwordHash != SecureHasher.hash(request.password, principal.account.passwordSalt)) + fun postCurrentSecret(principal: AuthenticationPrincipal, sessions: CurrentSession, request: PostAccountCurrentSecretRequest): PostAccountCurrentSecretResponse { + if (principal.account.passwordHash != PasswordHasher.hash(request.password, principal.account.passwordSalt)) throw UnauthorizedException() val secret = OTP.generateSecret() @@ -128,22 +108,16 @@ object AccountService { sessions.clear() return PostAccountCurrentSecretResponse( - secret = secret + secret = secret, ) } - fun getCurrentSessionAll( - principal: AuthenticationPrincipal, - ): List { + fun getCurrentSessionAll(principal: AuthenticationPrincipal): List { return principal.account.sessions() .map(SessionEntity::toResponse) } - fun deleteCurrentSessionId( - principal: AuthenticationPrincipal, - sessions: CurrentSession, - id: Long, - ) { + fun deleteCurrentSessionId(principal: AuthenticationPrincipal, sessions: CurrentSession, id: Long) { val session = SessionRepository .singleOrNull { SessionTable.id eq id } ?: throw NotFoundException() @@ -157,10 +131,7 @@ object AccountService { sessions.clear() } - fun deleteId( - principal: AuthenticationPrincipal, - id: Long, - ) { + fun deleteId(principal: AuthenticationPrincipal, id: Long) { val account = AccountRepository .singleOrNull { AccountTable.id eq id } ?: throw NotFoundException() @@ -171,11 +142,7 @@ object AccountService { AccountRepository.delete(account) } - fun putIdRole( - principal: AuthenticationPrincipal, - id: Long, - request: PutAccountIdRoleRequest, - ) { + fun putIdRole(principal: AuthenticationPrincipal, id: Long, request: PutAccountIdRoleRequest) { val account = AccountRepository .singleOrNull { AccountTable.id eq id } ?: throw NotFoundException() diff --git a/backend/src/main/kotlin/com/infendro/account/service/SessionService.kt b/backend/src/main/kotlin/com/infendro/account/service/SessionService.kt index df6538a..4f1ce35 100644 --- a/backend/src/main/kotlin/com/infendro/account/service/SessionService.kt +++ b/backend/src/main/kotlin/com/infendro/account/service/SessionService.kt @@ -16,17 +16,14 @@ import com.infendro.account.model.repository.AccountRepository import com.infendro.account.model.repository.SessionRepository import com.infendro.account.util.Hasher import com.infendro.account.util.OTP -import com.infendro.account.util.SecureHasher +import com.infendro.account.util.PasswordHasher import com.infendro.account.util.TokenGenerator import io.ktor.server.sessions.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.inList object SessionService { - fun post( - sessions: CurrentSession, - request: PostSessionRequest, - ) { + fun post(sessions: CurrentSession, request: PostSessionRequest) { val account = AccountRepository .singleOrNull { AccountTable.username eq request.username } ?: throw NotFoundException() @@ -34,7 +31,7 @@ object SessionService { if (!OTP.verify(account.secret, request.otp)) throw UnauthorizedException() - if (account.passwordHash != SecureHasher.hash(request.password, account.passwordSalt)) + if (account.passwordHash != PasswordHasher.hash(request.password, account.passwordSalt)) throw UnauthorizedException() val token = TokenGenerator.generate() @@ -43,14 +40,10 @@ object SessionService { this.account = account } - sessions.set( - AuthenticationSession(token) - ) + sessions.set(AuthenticationSession(token)) } - fun getAll( - principal: AuthenticationPrincipal, - ): List { + fun getAll(principal: AuthenticationPrincipal): List { if (principal.role == USER) throw ForbiddenException() @@ -60,10 +53,7 @@ object SessionService { .map(SessionEntity::toResponse) } - fun deleteId( - principal: AuthenticationPrincipal, - id: Long, - ) { + fun deleteId(principal: AuthenticationPrincipal, id: Long) { if (principal.role == USER) throw ForbiddenException() @@ -77,16 +67,11 @@ object SessionService { SessionRepository.delete(session) } - fun getCurrent( - principal: AuthenticationPrincipal, - ): SessionResponse { + fun getCurrent(principal: AuthenticationPrincipal): SessionResponse { return principal.session.toResponse() } - fun deleteCurrent( - principal: AuthenticationPrincipal, - sessions: CurrentSession, - ) { + fun deleteCurrent(principal: AuthenticationPrincipal, sessions: CurrentSession) { SessionRepository.delete(principal.session) sessions.clear() diff --git a/backend/src/main/kotlin/com/infendro/account/util/Hasher.kt b/backend/src/main/kotlin/com/infendro/account/util/Hasher.kt index 37bb75d..04918f4 100644 --- a/backend/src/main/kotlin/com/infendro/account/util/Hasher.kt +++ b/backend/src/main/kotlin/com/infendro/account/util/Hasher.kt @@ -1,15 +1,11 @@ package com.infendro.account.util -import com.infendro.bytearray.decodeToString -import com.infendro.bytearray.encodeToUByteArray import com.infendro.encoding.Base16 import com.infendro.hash.sha2.`SHA-256` object Hasher { - fun hash( - value: String, - ): String { - val bytes = `SHA-256`.hash(value.encodeToUByteArray()) + fun hash(value: String): String { + val bytes = `SHA-256`.hash(value.encodeToByteArray()) return Base16.encode(bytes).decodeToString() } } diff --git a/backend/src/main/kotlin/com/infendro/account/util/OTP.kt b/backend/src/main/kotlin/com/infendro/account/util/OTP.kt index 9cb82a5..4404494 100644 --- a/backend/src/main/kotlin/com/infendro/account/util/OTP.kt +++ b/backend/src/main/kotlin/com/infendro/account/util/OTP.kt @@ -1,32 +1,27 @@ package com.infendro.account.util -import com.infendro.bytearray.decodeToString -import com.infendro.bytearray.encodeToUByteArray import com.infendro.encoding.Base32 import com.infendro.hash.sha2.`SHA-256` -import com.infendro.otp.SecretGenerator import com.infendro.otp.TOTP +import com.infendro.random.csprng.`HMAC-DRBG` import kotlin.time.Clock import kotlin.time.Duration.Companion.seconds object OTP { - private val generator = SecretGenerator(`SHA-256`) + private val random = `HMAC-DRBG`(`SHA-256`) private val totp = TOTP( function = `SHA-256`, length = 8, period = 30.seconds, ) - fun verify( - secret: String, - otp: String, - ): Boolean { - val bytes = Base32.decode(secret.encodeToUByteArray()) + fun verify(secret: String, otp: String): Boolean { + val bytes = Base32.decode(secret.encodeToByteArray()) return totp.verify(bytes, Clock.System.now(), otp) } fun generateSecret(): String { - val bytes = generator.generate() + val bytes = random.nextBytes(32) return Base32.encode(bytes).decodeToString() } } diff --git a/backend/src/main/kotlin/com/infendro/account/util/PasswordHasher.kt b/backend/src/main/kotlin/com/infendro/account/util/PasswordHasher.kt new file mode 100644 index 0000000..f7f3dc2 --- /dev/null +++ b/backend/src/main/kotlin/com/infendro/account/util/PasswordHasher.kt @@ -0,0 +1,23 @@ +package com.infendro.account.util + +import com.infendro.encoding.Base16 +import com.infendro.hash.sha2.`SHA-256` +import com.infendro.kdf.PBKDF2 +import com.infendro.random.csprng.`HMAC-DRBG` + +object PasswordHasher { + private val random = `HMAC-DRBG`(`SHA-256`) + private val kdf = PBKDF2(32, 100_000, `SHA-256`) + + fun hash(value: String, salt: String): String { + val value = value.encodeToByteArray() + val salt = Base16.decode(salt.encodeToByteArray()) + return kdf.hash(value, salt) + .let { Base16.encode(it).decodeToString() } + } + + fun generateSalt(): String { + return random.nextBytes(16) + .let { Base16.encode(it).decodeToString() } + } +} diff --git a/backend/src/main/kotlin/com/infendro/account/util/SecureHasher.kt b/backend/src/main/kotlin/com/infendro/account/util/SecureHasher.kt deleted file mode 100644 index 0a332f4..0000000 --- a/backend/src/main/kotlin/com/infendro/account/util/SecureHasher.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.infendro.account.util - -import com.infendro.bytearray.decodeToString -import com.infendro.bytearray.encodeToUByteArray -import com.infendro.encoding.Base16 -import com.infendro.hash.sha2.`SHA-256` -import com.infendro.kdf.PBKDF2 -import com.infendro.random.csprng.CSPRNG - -object SecureHasher { - private val random = CSPRNG.HMAC(`SHA-256`) - private val kdf = PBKDF2(`SHA-256`) - - fun hash( - value: String, - salt: String, - ): String { - val value = value.encodeToByteArray() - val salt = Base16.decode(salt.encodeToUByteArray()) - return kdf.hash(value, salt, 100_000, 32) - .let { Base16.encode(it).decodeToString() } - } - - fun generateSalt(): String { - return random.nextBytes(16) - .let { Base16.encode(it).decodeToString() } - } -} diff --git a/backend/src/main/kotlin/com/infendro/account/util/TokenGenerator.kt b/backend/src/main/kotlin/com/infendro/account/util/TokenGenerator.kt index cb937ab..f206878 100644 --- a/backend/src/main/kotlin/com/infendro/account/util/TokenGenerator.kt +++ b/backend/src/main/kotlin/com/infendro/account/util/TokenGenerator.kt @@ -1,12 +1,11 @@ package com.infendro.account.util -import com.infendro.bytearray.decodeToString import com.infendro.encoding.Base16 import com.infendro.hash.sha2.`SHA-256` -import com.infendro.random.csprng.CSPRNG +import com.infendro.random.csprng.`HMAC-DRBG` object TokenGenerator { - private val random = CSPRNG.HMAC(`SHA-256`) + private val random = `HMAC-DRBG`(`SHA-256`) fun generate(): String { return random.nextBytes(32)