Add JwtFilter + separate modules
This commit is contained in:
parent
a50bd89e18
commit
b8e6c4b7ca
@ -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()
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
29
src/main/kotlin/filters/JwtFilter.kt
Normal file
29
src/main/kotlin/filters/JwtFilter.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/main/kotlin/modules/HttpModule.kt
Normal file
28
src/main/kotlin/modules/HttpModule.kt
Normal 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() }
|
||||
}
|
||||
17
src/main/kotlin/modules/JacksonModule.kt
Normal file
17
src/main/kotlin/modules/JacksonModule.kt
Normal 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()) }
|
||||
}
|
||||
26
src/main/kotlin/modules/PersistanceModule.kt
Normal file
26
src/main/kotlin/modules/PersistanceModule.kt
Normal 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) }
|
||||
}
|
||||
12
src/main/kotlin/modules/SecurityModule.kt
Normal file
12
src/main/kotlin/modules/SecurityModule.kt
Normal 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() }
|
||||
}
|
||||
@ -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"))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user