Merge branch 'feature/token-renewal'
This commit is contained in:
commit
5799949e00
@ -9,11 +9,27 @@ Content-Type: application/json
|
||||
|
||||
> {%
|
||||
client.global.set("token", response.body.token);
|
||||
client.global.set("refreshToken", response.body.refreshToken);
|
||||
client.test("Request executed successfully", function() {
|
||||
client.assert(response.status === 200, "Response status is not 200");
|
||||
});
|
||||
%}
|
||||
|
||||
### Refresh token
|
||||
POST http://localhost:8081/user/refresh_token
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"refreshToken": "{{refreshToken}}"
|
||||
}
|
||||
|
||||
> {%
|
||||
client.test("Request executed successfully", function() {
|
||||
client.global.set("token", response.body.token);
|
||||
client.assert(response.status === 200, "Response status is not 200");
|
||||
});
|
||||
%}
|
||||
|
||||
### Get notes
|
||||
GET http://localhost:8081/notes
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
@ -9,10 +9,10 @@ import org.kodein.di.generic.instance
|
||||
fun Application.authenticationModule() {
|
||||
install(Authentication) {
|
||||
jwt {
|
||||
val simpleJwt by kodein.instance<SimpleJWT>()
|
||||
val simpleJwt by kodein.instance<SimpleJWT>(tag = "auth")
|
||||
verifier(simpleJwt.verifier)
|
||||
validate {
|
||||
UserIdPrincipal(it.payload.getClaim("name").asString())
|
||||
UserIdPrincipal(it.payload.getClaim("email").asString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,14 +4,15 @@ import com.auth0.jwt.JWT
|
||||
import com.auth0.jwt.JWTVerifier
|
||||
import com.auth0.jwt.algorithms.Algorithm
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class SimpleJWT(secret: String) {
|
||||
private val validityInMs = 36_000_00 * 1
|
||||
class SimpleJWT(secret: String, validity: Long, unit: TimeUnit) {
|
||||
private val validityInMs = TimeUnit.MILLISECONDS.convert(validity, unit)
|
||||
private val algorithm = Algorithm.HMAC256(secret)
|
||||
|
||||
val verifier: JWTVerifier = JWT.require(algorithm).build()
|
||||
fun sign(name: String): String = JWT.create()
|
||||
.withClaim("name", name)
|
||||
fun sign(email: String): String = JWT.create()
|
||||
.withClaim("email", email)
|
||||
.withExpiresAt(getExpiration())
|
||||
.sign(algorithm)
|
||||
|
||||
|
||||
@ -17,16 +17,18 @@ suspend fun ApplicationCall.respondStatus(status: HttpStatusCode) {
|
||||
respond(status, status.description)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the user email for the currently authenticated user
|
||||
*/
|
||||
fun ApplicationCall.userEmail() = principal<UserIdPrincipal>()!!.name
|
||||
|
||||
/**
|
||||
* @return the userId for the currently authenticated user
|
||||
*/
|
||||
fun ApplicationCall.userId(): Int {
|
||||
val email = principal<UserIdPrincipal>()!!.name
|
||||
return userService.getUserId(email)!!
|
||||
}
|
||||
fun ApplicationCall.userId() = userService.getUserId(userEmail())!!
|
||||
|
||||
class NoteCreate(val title: String, val tags: List<String>)
|
||||
|
||||
suspend fun ApplicationCall.receiveNoteCreate(): FullNoteCreateDTO = receive()
|
||||
|
||||
suspend fun ApplicationCall.receiveNotePatch() : FullNotePatchDTO = receive()
|
||||
suspend fun ApplicationCall.receiveNotePatch(): FullNotePatchDTO = receive()
|
||||
@ -7,6 +7,7 @@ import io.ktor.application.*
|
||||
import org.kodein.di.Kodein
|
||||
import org.kodein.di.generic.bind
|
||||
import org.kodein.di.generic.instance
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.sql.DataSource
|
||||
|
||||
/**
|
||||
@ -29,10 +30,13 @@ fun Application.configurationFeature() {
|
||||
HikariDataSource(hikariConfig)
|
||||
}
|
||||
|
||||
val simpleJwt = SimpleJWT(environment.config.property("jwt.secret").getString())
|
||||
val jwtSecret = environment.config.property("jwt.secret").getString()
|
||||
val authSimpleJwt = SimpleJWT(jwtSecret, 1, TimeUnit.HOURS)
|
||||
val refreshSimpleJwt = SimpleJWT(jwtSecret, 7, TimeUnit.DAYS)
|
||||
|
||||
configurationModule = Kodein.Module("Configuration") {
|
||||
bind<DataSource>() with instance(dataSource)
|
||||
bind<SimpleJWT>() with instance(simpleJwt)
|
||||
bind<SimpleJWT>(tag = "auth") with instance(authSimpleJwt)
|
||||
bind<SimpleJWT>(tag = "refresh") with instance(refreshSimpleJwt)
|
||||
}
|
||||
}
|
||||
70
api/src/routing/AuthController.kt
Normal file
70
api/src/routing/AuthController.kt
Normal file
@ -0,0 +1,70 @@
|
||||
package be.vandewalleh.routing
|
||||
|
||||
import be.vandewalleh.auth.SimpleJWT
|
||||
import be.vandewalleh.auth.UsernamePasswordCredential
|
||||
import be.vandewalleh.extensions.respondStatus
|
||||
import be.vandewalleh.services.UserService
|
||||
import com.auth0.jwt.exceptions.JWTVerificationException
|
||||
import io.ktor.application.*
|
||||
import io.ktor.auth.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.request.*
|
||||
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)
|
||||
|
||||
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>()
|
||||
|
||||
post("/user/login") {
|
||||
val credential = call.receive<UsernamePasswordCredential>()
|
||||
|
||||
val (email, password) = userService.getEmailAndPasswordFromUsername(credential.username)
|
||||
?: return@post call.respondStatus(HttpStatusCode.Unauthorized)
|
||||
|
||||
if (!BCrypt.checkpw(credential.password, password)) {
|
||||
return@post call.respondStatus(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
|
||||
val response = DualToken(
|
||||
token = authSimpleJwt.sign(email),
|
||||
refreshToken = refreshSimpleJwt.sign(email)
|
||||
)
|
||||
return@post call.respond(response)
|
||||
}
|
||||
|
||||
post("/user/refresh_token") {
|
||||
val token = call.receive<RefreshToken>().refreshToken
|
||||
|
||||
val email = try {
|
||||
val decodedJWT = refreshSimpleJwt.verifier.verify(token)
|
||||
decodedJWT.getClaim("email").asString()
|
||||
} catch (e: JWTVerificationException) {
|
||||
return@post call.respondStatus(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
|
||||
val response = DualToken(
|
||||
token = authSimpleJwt.sign(email),
|
||||
refreshToken = refreshSimpleJwt.sign(email)
|
||||
)
|
||||
return@post call.respond(response)
|
||||
}
|
||||
|
||||
authenticate {
|
||||
get("/user/me") {
|
||||
// retrieve email from token
|
||||
val email = call.principal<UserIdPrincipal>()!!.name
|
||||
val info = userService.getUserInfo(email)
|
||||
if (info != null) call.respond(mapOf("user" to info))
|
||||
else call.respondStatus(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
package be.vandewalleh.routing
|
||||
|
||||
import be.vandewalleh.auth.SimpleJWT
|
||||
import be.vandewalleh.auth.UsernamePasswordCredential
|
||||
import be.vandewalleh.extensions.respondStatus
|
||||
import be.vandewalleh.services.UserService
|
||||
import io.ktor.application.*
|
||||
import io.ktor.auth.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.request.*
|
||||
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.login(kodein: Kodein) {
|
||||
val simpleJwt by kodein.instance<SimpleJWT>()
|
||||
val userService by kodein.instance<UserService>()
|
||||
|
||||
data class TokenResponse(val token: String)
|
||||
|
||||
post("/user/login") {
|
||||
val credential = call.receive<UsernamePasswordCredential>()
|
||||
|
||||
val (email, password) = userService.getEmailAndPasswordFromUsername(credential.username)
|
||||
?: return@post call.respond(HttpStatusCode.Unauthorized)
|
||||
|
||||
if (!BCrypt.checkpw(credential.password, password)) {
|
||||
return@post call.respond(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
|
||||
return@post call.respond(TokenResponse(simpleJwt.sign(email)))
|
||||
}
|
||||
|
||||
authenticate {
|
||||
get("/user/me") {
|
||||
// retrieve email from token
|
||||
val email = call.principal<UserIdPrincipal>()!!.name
|
||||
val info = userService.getUserInfo(email)
|
||||
if (info != null) call.respond(mapOf("user" to info))
|
||||
else call.respondStatus(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -5,7 +5,7 @@ import org.kodein.di.Kodein
|
||||
|
||||
fun Routing.registerRoutes(kodein: Kodein) {
|
||||
user(kodein)
|
||||
login(kodein)
|
||||
auth(kodein)
|
||||
notes(kodein)
|
||||
title(kodein)
|
||||
tags(kodein)
|
||||
|
||||
@ -34,7 +34,6 @@ fun Routing.user(kodein: Kodein) {
|
||||
}
|
||||
|
||||
authenticate {
|
||||
|
||||
put {
|
||||
val user = call.receive<UserDto>()
|
||||
|
||||
|
||||
@ -44,7 +44,13 @@ class UserService(override val kodein: Kodein) : KodeinAware {
|
||||
return db.from(Users)
|
||||
.select(Users.id)
|
||||
.where { (Users.username eq username) or (Users.email eq email) }
|
||||
.limit(0, 1)
|
||||
.firstOrNull() != null
|
||||
}
|
||||
|
||||
fun userExists(userId: Int): Boolean {
|
||||
return db.from(Users)
|
||||
.select(Users.id)
|
||||
.where { Users.id eq userId }
|
||||
.firstOrNull() != null
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user