package be.vandewalleh.controllers.api import be.vandewalleh.auth.SimpleJWT import be.vandewalleh.auth.UsernamePasswordCredential import be.vandewalleh.extensions.authenticatedUser import be.vandewalleh.features.PasswordHash import be.vandewalleh.repositories.UserRepository import be.vandewalleh.validation.receiveValidated import be.vandewalleh.validation.registerValidator import com.auth0.jwt.exceptions.JWTVerificationException import io.ktor.application.* import io.ktor.http.* import io.ktor.request.* import io.ktor.response.* class ApiUserController( private val authJWT: SimpleJWT, private val refreshJWT: SimpleJWT, private val userRepository: UserRepository, private val passwordHash: PasswordHash ) { suspend fun create(call: ApplicationCall) { val user = call.receiveValidated(registerValidator) if (userRepository.exists(user.username)) return call.response.status(HttpStatusCode.Conflict) userRepository.create(user.username, user.password) ?: return call.response.status(HttpStatusCode.Conflict) call.response.status(HttpStatusCode.Created) } suspend fun login(call: ApplicationCall) { val credential = call.receive() val user = userRepository.find(credential.username) ?: return call.response.status(HttpStatusCode.Unauthorized) if (!passwordHash.verify(credential.password, user.password)) { return call.response.status(HttpStatusCode.Unauthorized) } val response = DualToken( token = authJWT.sign(user.id, user.username), refreshToken = refreshJWT.sign(user.id, user.username) ) return call.respond(response) } suspend fun refreshToken(call: ApplicationCall) { val token = call.receive().refreshToken val id = try { val decodedJWT = refreshJWT.verifier.verify(token) decodedJWT.getClaim("id").asInt() } catch (e: JWTVerificationException) { return call.response.status(HttpStatusCode.Unauthorized) } val user = userRepository.find(id) ?: return call.response.status(HttpStatusCode.Unauthorized) val response = DualToken( token = authJWT.sign(user.id, user.username), refreshToken = refreshJWT.sign(user.id, user.username) ) return call.respond(response) } suspend fun delete(call: ApplicationCall) { val userId = call.authenticatedUser().id val success = userRepository.delete(userId) if (success) call.response.status(HttpStatusCode.OK) else call.response.status(HttpStatusCode.NotFound) } suspend fun info(call: ApplicationCall) { val id = call.authenticatedUser().id val info = userRepository.find(id) if (info != null) call.respond(mapOf("user" to info)) else call.response.status(HttpStatusCode.Unauthorized) } } private data class RefreshToken(val refreshToken: String) private data class DualToken(val token: String, val refreshToken: String)