Add JwtFilter + separate modules

This commit is contained in:
Hubert Van De Walle 2020-09-29 19:25:39 +02:00
parent a50bd89e18
commit b8e6c4b7ca
8 changed files with 133 additions and 83 deletions

View File

@ -1,76 +1,13 @@
package be.simplenotes
import be.simplenotes.extensions.addShutdownHook
import be.simplenotes.extensions.hikariDataSource
import be.simplenotes.extensions.registerSerializers
import be.simplenotes.repositories.UserRepository
import be.simplenotes.routes.RouteSupplier
import be.simplenotes.routes.UserRoutes
import be.simplenotes.routes.toRouter
import be.simplenotes.security.Argon2PasswordHash
import be.simplenotes.security.PasswordHash
import be.simplenotes.security.SimpleJwt
import be.simplenotes.serializers.UserIdSerializer
import be.simplenotes.serializers.UserSerializer
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import org.flywaydb.core.Flyway
import org.http4k.core.then
import org.http4k.filter.ServerFilters
import org.http4k.format.Jackson
import org.http4k.routing.RoutingHttpHandler
import org.http4k.server.ApacheServer
import org.http4k.server.asServer
import org.jooq.SQLDialect
import org.jooq.impl.DSL
import be.simplenotes.modules.httpModule
import be.simplenotes.modules.jacksonModule
import be.simplenotes.modules.persistanceModule
import be.simplenotes.modules.securityModule
import org.koin.core.context.startKoin
import org.koin.dsl.bind
import org.koin.dsl.module
import org.koin.dsl.onClose
import java.util.concurrent.TimeUnit
import javax.sql.DataSource
fun main() {
val jacksonModule = module {
single { UserIdSerializer() } bind StdSerializer::class
single { UserSerializer() } bind StdSerializer::class
single { Jackson.mapper }
single(createdAtStart = true) {
get<ObjectMapper>().registerSerializers(getAll())
}
}
fun main() = startKoin {
modules(httpModule, securityModule, jacksonModule, persistanceModule)
}.addShutdownHook()
val persistanceModule = module {
single { UserRepository(get(), get()) }
single {
hikariDataSource {
jdbcUrl = "jdbc:h2:./notes.db;MODE=MySQL;"
driverClassName = "org.h2.Driver"
maximumPoolSize = 2
}
} bind DataSource::class onClose { it?.close() }
single { Flyway.configure().dataSource(get()).load() }
single { get<Flyway>().migrate(); DSL.using(get<DataSource>(), SQLDialect.H2) }
}
val securityModule = module {
single { SimpleJwt("super secret", 1, TimeUnit.HOURS) }
single<PasswordHash> { Argon2PasswordHash() }
}
val httpModule = module {
single { UserRoutes(get(), get()) } bind RouteSupplier::class
single { getAll<RouteSupplier>().toRouter() }
single(createdAtStart = true) {
ServerFilters.CatchLensFailure()
.then(get<RoutingHttpHandler>())
.asServer(ApacheServer(7000)).start()
} onClose { it?.close() }
}
startKoin {
modules(httpModule, securityModule, jacksonModule, persistanceModule)
}.koin.addShutdownHook()
}

View File

@ -1,9 +1,9 @@
package be.simplenotes.extensions
import org.koin.core.Koin
import org.koin.core.KoinApplication
import kotlin.concurrent.thread
fun Koin.addShutdownHook() {
fun KoinApplication.addShutdownHook() {
Runtime.getRuntime().addShutdownHook(
thread(start = false) {
close()

View File

@ -0,0 +1,29 @@
package be.simplenotes.filters
import be.simplenotes.security.SimpleJwt
import org.http4k.core.Filter
import org.http4k.core.RequestContexts
import org.http4k.core.Response
import org.http4k.core.Status
import org.http4k.core.cookie.cookie
class JwtFilter(
private val ctx: RequestContexts,
private val simpleJwt: SimpleJwt,
) {
private val authKey = "auth"
operator fun invoke() = Filter { next ->
{ req ->
val extractedToken = req.cookie("Bearer")
?.value
?.trim()
?.let { simpleJwt.extract(it) }
if (extractedToken != null) {
ctx[req][authKey] = extractedToken
next(req)
} else Response(Status.UNAUTHORIZED)
}
}
}

View File

@ -0,0 +1,28 @@
package be.simplenotes.modules
import be.simplenotes.filters.JwtFilter
import be.simplenotes.routes.RouteSupplier
import be.simplenotes.routes.UserRoutes
import be.simplenotes.routes.toRouter
import org.http4k.core.RequestContexts
import org.http4k.core.then
import org.http4k.filter.ServerFilters
import org.http4k.routing.RoutingHttpHandler
import org.http4k.server.ApacheServer
import org.http4k.server.asServer
import org.koin.dsl.bind
import org.koin.dsl.module
import org.koin.dsl.onClose
val httpModule = module {
single { UserRoutes(get(), get(), get(), get()) } bind RouteSupplier::class
single { getAll<RouteSupplier>().toRouter() }
single { RequestContexts() }
single { JwtFilter(get(), get())() }
single(createdAtStart = true) {
ServerFilters.CatchLensFailure()
.then(ServerFilters.InitialiseRequestContext(get<RequestContexts>()))
.then(get<RoutingHttpHandler>())
.asServer(ApacheServer(7000)).start()
} onClose { it?.close() }
}

View File

@ -0,0 +1,17 @@
package be.simplenotes.modules
import be.simplenotes.extensions.registerSerializers
import be.simplenotes.serializers.UserIdSerializer
import be.simplenotes.serializers.UserSerializer
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import org.http4k.format.Jackson
import org.koin.dsl.bind
import org.koin.dsl.module
val jacksonModule = module {
single { UserIdSerializer() } bind StdSerializer::class
single { UserSerializer() } bind StdSerializer::class
single { Jackson.mapper }
single(createdAtStart = true) { get<ObjectMapper>().registerSerializers(getAll()) }
}

View File

@ -0,0 +1,26 @@
package be.simplenotes.modules
import be.simplenotes.extensions.hikariDataSource
import be.simplenotes.repositories.UserRepository
import org.flywaydb.core.Flyway
import org.jooq.SQLDialect
import org.jooq.impl.DSL
import org.koin.dsl.bind
import org.koin.dsl.module
import org.koin.dsl.onClose
import javax.sql.DataSource
val persistanceModule = module {
single { UserRepository(get(), get()) }
single {
hikariDataSource {
jdbcUrl = "jdbc:h2:./notes.db;MODE=MySQL;"
driverClassName = "org.h2.Driver"
maximumPoolSize = 2
}
} bind DataSource::class onClose { it?.close() }
single { Flyway.configure().dataSource(get()).load() }
single { get<Flyway>().migrate(); DSL.using(get<DataSource>(), SQLDialect.H2) }
}

View File

@ -0,0 +1,12 @@
package be.simplenotes.modules
import be.simplenotes.security.Argon2PasswordHash
import be.simplenotes.security.PasswordHash
import be.simplenotes.security.SimpleJwt
import org.koin.dsl.module
import java.util.concurrent.TimeUnit
val securityModule = module {
single { SimpleJwt("super secret", 1, TimeUnit.HOURS) }
single<PasswordHash> { Argon2PasswordHash() }
}

View File

@ -6,16 +6,14 @@ import be.simplenotes.extensions.notFound
import be.simplenotes.extensions.ok
import be.simplenotes.repositories.UserRepository
import be.simplenotes.security.SimpleJwt
import org.http4k.core.Body
import org.http4k.core.*
import org.http4k.core.Method.*
import org.http4k.core.Response
import org.http4k.core.cookie.Cookie
import org.http4k.core.cookie.cookie
import org.http4k.core.with
import org.http4k.format.Jackson.auto
import org.http4k.lens.ContentNegotiation
import org.http4k.lens.Cookies
import org.http4k.lens.Path
import org.http4k.lens.RequestContextKey
import org.http4k.lens.long
import org.http4k.routing.bind
import org.http4k.routing.routes
@ -23,12 +21,17 @@ import org.http4k.routing.routes
data class Message(val msg: String)
data class Login(val username: String, val password: String)
class UserRoutes(private val userRepository: UserRepository, private val simpleJwt: SimpleJwt) : RouteSupplier {
class UserRoutes(
private val userRepository: UserRepository,
private val simpleJwt: SimpleJwt,
private val jwtFilter: Filter,
private val ctx: RequestContexts,
) : RouteSupplier {
private val userLens = Body.auto<User>("user", ContentNegotiation.StrictNoDirective).toLens()
private val msgLens = Body.auto<Message>().toLens()
private val loginLens = Body.auto<Login>().toLens()
private val idLens = Path.long().map { UserId(it) }.of("id")
private val jwtLens = Cookies.optional("Bearer")
private val userIdLens = RequestContextKey.required<UserId>(ctx, "auth")
private fun userNotFound() = Response.notFound().with(msgLens of Message("not found"))
@ -57,11 +60,9 @@ class UserRoutes(private val userRepository: UserRepository, private val simpleJ
Response.ok().with(userLens of user.copy(id = null)).cookie(cookie)
} ?: Response.notFound()
},
"/whoami" bind GET to {
jwtLens(it)?.value
?.let { token -> simpleJwt.extract(token) }
?.let { id -> Response.ok().with(userLens of userRepository.find(id)!!) }
?: Response.notFound()
"/private" bind GET to jwtFilter.then {
val user = userRepository.find(userIdLens(it))!!
Response.ok().with(msgLens of Message("Welcome $user"))
}
)
}