diff --git a/api/src/Dependencies.kt b/api/src/Dependencies.kt index c77fcd7..3c86e37 100644 --- a/api/src/Dependencies.kt +++ b/api/src/Dependencies.kt @@ -1,5 +1,7 @@ package be.vandewalleh +import be.vandewalleh.features.BcryptPasswordHash +import be.vandewalleh.features.PasswordHash import be.vandewalleh.features.configurationModule import be.vandewalleh.migrations.Migration import be.vandewalleh.services.serviceModule @@ -18,4 +20,5 @@ val mainModule = Kodein.Module("main") { bind() with singleton { LoggerFactory.getLogger("Application") } bind() with singleton { Migration(this.kodein) } bind() with singleton { Database.connect(this.instance()) } + bind() with singleton { BcryptPasswordHash() } } diff --git a/api/src/entities/User.kt b/api/src/entities/User.kt index 6f12fdb..3b08b37 100644 --- a/api/src/entities/User.kt +++ b/api/src/entities/User.kt @@ -1,12 +1,15 @@ package be.vandewalleh.entities +import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty import me.liuwj.ktorm.entity.* interface User : Entity { companion object : Entity.Factory() + @get:JsonIgnore val id: Int + var username: String @get:JsonProperty(access = JsonProperty.Access.WRITE_ONLY) diff --git a/api/src/features/PasswordHash.kt b/api/src/features/PasswordHash.kt new file mode 100644 index 0000000..d56668b --- /dev/null +++ b/api/src/features/PasswordHash.kt @@ -0,0 +1,13 @@ +package be.vandewalleh.features + +import org.mindrot.jbcrypt.BCrypt + +interface PasswordHash { + fun crypt(password: String): String + fun verify(password: String, hashedPassword: String): Boolean +} + +class BcryptPasswordHash : PasswordHash { + override fun crypt(password: String) = BCrypt.hashpw(password, BCrypt.gensalt())!! + override fun verify(password: String, hashedPassword: String) = BCrypt.checkpw(password, hashedPassword) +} diff --git a/api/src/routing/AuthController.kt b/api/src/routing/AuthController.kt index 65f767c..de0711a 100644 --- a/api/src/routing/AuthController.kt +++ b/api/src/routing/AuthController.kt @@ -4,6 +4,7 @@ import be.vandewalleh.auth.SimpleJWT import be.vandewalleh.auth.UserDbIdPrincipal import be.vandewalleh.auth.UsernamePasswordCredential import be.vandewalleh.extensions.respondStatus +import be.vandewalleh.features.PasswordHash import be.vandewalleh.services.UserService import com.auth0.jwt.exceptions.JWTVerificationException import io.ktor.application.* @@ -14,7 +15,6 @@ import io.ktor.response.* import io.ktor.routing.* import org.kodein.di.Kodein import org.kodein.di.generic.instance -import org.mindrot.jbcrypt.BCrypt data class RefreshToken(val refreshToken: String) data class DualToken(val token: String, val refreshToken: String) @@ -23,6 +23,7 @@ fun Routing.auth(kodein: Kodein) { val authSimpleJwt by kodein.instance("auth") val refreshSimpleJwt by kodein.instance("refresh") val userService by kodein.instance() + val passwordHash by kodein.instance() post("/user/login") { val credential = call.receive() @@ -30,7 +31,7 @@ fun Routing.auth(kodein: Kodein) { val user = userService.find(credential.username) ?: return@post call.respondStatus(HttpStatusCode.Unauthorized) - if (!BCrypt.checkpw(credential.password, user.password)) { + if (!passwordHash.verify(credential.password, user.password)) { return@post call.respondStatus(HttpStatusCode.Unauthorized) } diff --git a/api/src/routing/UserController.kt b/api/src/routing/UserController.kt index d03eac9..988e18a 100644 --- a/api/src/routing/UserController.kt +++ b/api/src/routing/UserController.kt @@ -12,7 +12,6 @@ import io.ktor.response.* import io.ktor.routing.* import org.kodein.di.Kodein import org.kodein.di.generic.instance -import org.mindrot.jbcrypt.BCrypt fun Routing.user(kodein: Kodein) { val userService by kodein.instance() @@ -24,9 +23,7 @@ fun Routing.user(kodein: Kodein) { if (userService.exists(user.username)) return@post call.respondStatus(HttpStatusCode.Conflict) - val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt()) - - val newUser = userService.create(user.username, hashedPassword) + val newUser = userService.create(user.username, user.password) ?: return@post call.respondStatus(HttpStatusCode.Conflict) call.respond(HttpStatusCode.Created, newUser) diff --git a/api/src/services/UserService.kt b/api/src/services/UserService.kt index a5d7d26..4c5137b 100644 --- a/api/src/services/UserService.kt +++ b/api/src/services/UserService.kt @@ -1,10 +1,9 @@ package be.vandewalleh.services import be.vandewalleh.entities.User -import be.vandewalleh.extensions.ioAsync import be.vandewalleh.extensions.launchIo +import be.vandewalleh.features.PasswordHash import be.vandewalleh.tables.Users -import kotlinx.coroutines.Deferred import me.liuwj.ktorm.database.* import me.liuwj.ktorm.dsl.* import me.liuwj.ktorm.entity.* @@ -12,13 +11,13 @@ import org.kodein.di.Kodein import org.kodein.di.KodeinAware import org.kodein.di.generic.instance import java.sql.SQLIntegrityConstraintViolationException -import java.time.LocalDateTime /** * service to handle database queries for users. */ class UserService(override val kodein: Kodein) : KodeinAware { private val db by instance() + private val passwordHash by instance() /** @@ -49,10 +48,10 @@ class UserService(override val kodein: Kodein) : KodeinAware { * create a new user * password should already be hashed */ - suspend fun create(username: String, hashedPassword: String): User? { + suspend fun create(username: String, password: String): User? { val newUser = User { this.username = username - password = hashedPassword + this.password = passwordHash.crypt(password) } return try { diff --git a/api/test/integration/routing/AuthControllerKtTest.kt b/api/test/integration/routing/AuthControllerKtTest.kt index b792897..0eeacbc 100644 --- a/api/test/integration/routing/AuthControllerKtTest.kt +++ b/api/test/integration/routing/AuthControllerKtTest.kt @@ -3,6 +3,7 @@ package integration.routing import be.vandewalleh.auth.SimpleJWT import be.vandewalleh.entities.User import be.vandewalleh.features.Config +import be.vandewalleh.features.PasswordHash import be.vandewalleh.mainModule import be.vandewalleh.module import be.vandewalleh.services.UserService @@ -19,7 +20,6 @@ import org.junit.jupiter.api.* import org.kodein.di.Kodein import org.kodein.di.generic.bind import org.kodein.di.generic.instance -import org.mindrot.jbcrypt.BCrypt import utils.* import java.util.* @@ -28,10 +28,18 @@ class AuthControllerKtTest { private val userService = mockk() + private val kodein = Kodein { + import(mainModule, allowOverride = true) + bind(overrides = true) with instance(userService) + } + + private val passwordHash by kodein.instance() + + init { val user = User { - password = BCrypt.hashpw("password", BCrypt.gensalt()) + password = passwordHash.crypt("password") username = "existing" } user["id"] = 1 @@ -43,7 +51,7 @@ class AuthControllerKtTest { } val user2 = User { - password = BCrypt.hashpw("right password", BCrypt.gensalt()) + password = passwordHash.crypt("right password") username = "wrong" } user["id"] = 2 @@ -56,11 +64,6 @@ class AuthControllerKtTest { } - private val kodein = Kodein { - import(mainModule, allowOverride = true) - bind(overrides = true) with instance(userService) - } - private val testEngine = TestApplicationEngine().apply { start() application.module(kodein) diff --git a/api/test/integration/services/UserServiceTest.kt b/api/test/integration/services/UserServiceTest.kt index 0a4ac1a..8ed0548 100644 --- a/api/test/integration/services/UserServiceTest.kt +++ b/api/test/integration/services/UserServiceTest.kt @@ -52,7 +52,7 @@ class UserServiceTest { @Order(2) fun `test create same user`() { runBlocking { - userService.create(username = "hubert", hashedPassword = "password") `should be` null + userService.create(username = "hubert", password = "password") `should be` null } }