Add JwtFilter + separate modules
This commit is contained in:
parent
a50bd89e18
commit
b8e6c4b7ca
@ -1,76 +1,13 @@
|
|||||||
package be.simplenotes
|
package be.simplenotes
|
||||||
|
|
||||||
import be.simplenotes.extensions.addShutdownHook
|
import be.simplenotes.extensions.addShutdownHook
|
||||||
import be.simplenotes.extensions.hikariDataSource
|
import be.simplenotes.modules.httpModule
|
||||||
import be.simplenotes.extensions.registerSerializers
|
import be.simplenotes.modules.jacksonModule
|
||||||
import be.simplenotes.repositories.UserRepository
|
import be.simplenotes.modules.persistanceModule
|
||||||
import be.simplenotes.routes.RouteSupplier
|
import be.simplenotes.modules.securityModule
|
||||||
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 org.koin.core.context.startKoin
|
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() {
|
fun main() = startKoin {
|
||||||
val jacksonModule = module {
|
|
||||||
single { UserIdSerializer() } bind StdSerializer::class
|
|
||||||
single { UserSerializer() } bind StdSerializer::class
|
|
||||||
single { Jackson.mapper }
|
|
||||||
single(createdAtStart = true) {
|
|
||||||
get<ObjectMapper>().registerSerializers(getAll())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
modules(httpModule, securityModule, jacksonModule, persistanceModule)
|
||||||
}.koin.addShutdownHook()
|
}.addShutdownHook()
|
||||||
}
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
package be.simplenotes.extensions
|
package be.simplenotes.extensions
|
||||||
|
|
||||||
import org.koin.core.Koin
|
import org.koin.core.KoinApplication
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
fun Koin.addShutdownHook() {
|
fun KoinApplication.addShutdownHook() {
|
||||||
Runtime.getRuntime().addShutdownHook(
|
Runtime.getRuntime().addShutdownHook(
|
||||||
thread(start = false) {
|
thread(start = false) {
|
||||||
close()
|
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.extensions.ok
|
||||||
import be.simplenotes.repositories.UserRepository
|
import be.simplenotes.repositories.UserRepository
|
||||||
import be.simplenotes.security.SimpleJwt
|
import be.simplenotes.security.SimpleJwt
|
||||||
import org.http4k.core.Body
|
import org.http4k.core.*
|
||||||
import org.http4k.core.Method.*
|
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.cookie.cookie
|
import org.http4k.core.cookie.cookie
|
||||||
import org.http4k.core.with
|
|
||||||
import org.http4k.format.Jackson.auto
|
import org.http4k.format.Jackson.auto
|
||||||
import org.http4k.lens.ContentNegotiation
|
import org.http4k.lens.ContentNegotiation
|
||||||
import org.http4k.lens.Cookies
|
|
||||||
import org.http4k.lens.Path
|
import org.http4k.lens.Path
|
||||||
|
import org.http4k.lens.RequestContextKey
|
||||||
import org.http4k.lens.long
|
import org.http4k.lens.long
|
||||||
import org.http4k.routing.bind
|
import org.http4k.routing.bind
|
||||||
import org.http4k.routing.routes
|
import org.http4k.routing.routes
|
||||||
@ -23,12 +21,17 @@ import org.http4k.routing.routes
|
|||||||
data class Message(val msg: String)
|
data class Message(val msg: String)
|
||||||
data class Login(val username: String, val password: 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 userLens = Body.auto<User>("user", ContentNegotiation.StrictNoDirective).toLens()
|
||||||
private val msgLens = Body.auto<Message>().toLens()
|
private val msgLens = Body.auto<Message>().toLens()
|
||||||
private val loginLens = Body.auto<Login>().toLens()
|
private val loginLens = Body.auto<Login>().toLens()
|
||||||
private val idLens = Path.long().map { UserId(it) }.of("id")
|
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"))
|
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.ok().with(userLens of user.copy(id = null)).cookie(cookie)
|
||||||
} ?: Response.notFound()
|
} ?: Response.notFound()
|
||||||
},
|
},
|
||||||
"/whoami" bind GET to {
|
"/private" bind GET to jwtFilter.then {
|
||||||
jwtLens(it)?.value
|
val user = userRepository.find(userIdLens(it))!!
|
||||||
?.let { token -> simpleJwt.extract(token) }
|
Response.ok().with(msgLens of Message("Welcome $user"))
|
||||||
?.let { id -> Response.ok().with(userLens of userRepository.find(id)!!) }
|
|
||||||
?: Response.notFound()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user