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() 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 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)