Refactor: Move bcrypt inside kodein module for easier testing
This commit is contained in:
parent
214286a6eb
commit
1611ca6ab4
@ -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<Logger>() with singleton { LoggerFactory.getLogger("Application") }
|
||||
bind<Migration>() with singleton { Migration(this.kodein) }
|
||||
bind<Database>() with singleton { Database.connect(this.instance<DataSource>()) }
|
||||
bind<PasswordHash>() with singleton { BcryptPasswordHash() }
|
||||
}
|
||||
|
||||
@ -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<User> {
|
||||
companion object : Entity.Factory<User>()
|
||||
|
||||
@get:JsonIgnore
|
||||
val id: Int
|
||||
|
||||
var username: String
|
||||
|
||||
@get:JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
|
||||
13
api/src/features/PasswordHash.kt
Normal file
13
api/src/features/PasswordHash.kt
Normal file
@ -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)
|
||||
}
|
||||
@ -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<SimpleJWT>("auth")
|
||||
val refreshSimpleJwt by kodein.instance<SimpleJWT>("refresh")
|
||||
val userService by kodein.instance<UserService>()
|
||||
val passwordHash by kodein.instance<PasswordHash>()
|
||||
|
||||
post("/user/login") {
|
||||
val credential = call.receive<UsernamePasswordCredential>()
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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<UserService>()
|
||||
@ -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)
|
||||
|
||||
@ -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<Database>()
|
||||
private val passwordHash by instance<PasswordHash>()
|
||||
|
||||
|
||||
/**
|
||||
@ -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 {
|
||||
|
||||
@ -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<UserService>()
|
||||
|
||||
private val kodein = Kodein {
|
||||
import(mainModule, allowOverride = true)
|
||||
bind<UserService>(overrides = true) with instance(userService)
|
||||
}
|
||||
|
||||
private val passwordHash by kodein.instance<PasswordHash>()
|
||||
|
||||
|
||||
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<UserService>(overrides = true) with instance(userService)
|
||||
}
|
||||
|
||||
private val testEngine = TestApplicationEngine().apply {
|
||||
start()
|
||||
application.module(kodein)
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user