SimpleNotes/api/src/routing/UserRoutes.kt

124 lines
3.7 KiB
Kotlin

package be.vandewalleh.routing
import be.vandewalleh.auth.SimpleJWT
import be.vandewalleh.auth.UsernamePasswordCredential
import be.vandewalleh.extensions.RoutingBuilder
import be.vandewalleh.extensions.authenticatedUserId
import be.vandewalleh.extensions.respondStatus
import be.vandewalleh.features.PasswordHash
import be.vandewalleh.services.UserService
import be.vandewalleh.validation.receiveValidated
import be.vandewalleh.validation.registerValidator
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.*
class UserRoutes(
authJWT: SimpleJWT,
refreshJWT: SimpleJWT,
userService: UserService,
passwordHash: PasswordHash
) : RoutingBuilder({
route("/user") {
createUser(userService)
route("/login") {
login(userService, passwordHash, authJWT, refreshJWT)
}
route("/refresh_token") {
refreshToken(userService, authJWT, refreshJWT)
}
authenticate {
deleteUser(userService)
route("/me") {
userInfo(userService)
}
}
}
})
private fun Route.userInfo(userService: UserService) {
get {
val id = call.authenticatedUserId()
val info = userService.find(id)
if (info != null) call.respond(mapOf("user" to info))
else call.respondStatus(HttpStatusCode.Unauthorized)
}
}
private fun Route.deleteUser(userService: UserService) {
delete {
val userId = call.authenticatedUserId()
call.respondStatus(
if (userService.delete(userId)) HttpStatusCode.OK
else HttpStatusCode.NotFound
)
}
}
private fun Route.createUser(userService: UserService) {
post {
val user = call.receiveValidated(registerValidator)
if (userService.exists(user.username))
return@post call.respondStatus(HttpStatusCode.Conflict)
val newUser = userService.create(user.username, user.password)
?: return@post call.respondStatus(HttpStatusCode.Conflict)
call.respond(HttpStatusCode.Created, newUser)
}
}
private fun Route.login(
userService: UserService,
passwordHash: PasswordHash,
authJWT: SimpleJWT,
refreshJWT: SimpleJWT
) {
post {
val credential = call.receive<UsernamePasswordCredential>()
val user = userService.find(credential.username)
?: return@post call.respondStatus(HttpStatusCode.Unauthorized)
if (!passwordHash.verify(credential.password, user.password)) {
return@post call.respondStatus(HttpStatusCode.Unauthorized)
}
val response = DualToken(
token = authJWT.sign(user.id),
refreshToken = refreshJWT.sign(user.id)
)
return@post call.respond(response)
}
}
private fun Route.refreshToken(userService: UserService, authJWT: SimpleJWT, refreshJWT: SimpleJWT) {
post {
val token = call.receive<RefreshToken>().refreshToken
val id = try {
val decodedJWT = refreshJWT.verifier.verify(token)
decodedJWT.getClaim("id").asInt()
} catch (e: JWTVerificationException) {
return@post call.respondStatus(HttpStatusCode.Unauthorized)
}
if (!userService.exists(id))
return@post call.respondStatus(HttpStatusCode.Unauthorized)
val response = DualToken(
token = authJWT.sign(id),
refreshToken = refreshJWT.sign(id)
)
return@post call.respond(response)
}
}
private data class RefreshToken(val refreshToken: String)
private data class DualToken(val token: String, val refreshToken: String)