diff --git a/backend/src/main/kotlin/plugins/Errors.kt b/backend/src/main/kotlin/plugins/Errors.kt new file mode 100644 index 0000000..e79960f --- /dev/null +++ b/backend/src/main/kotlin/plugins/Errors.kt @@ -0,0 +1,38 @@ +package dev.svitan.plugins + +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.Application +import io.ktor.server.application.install +import io.ktor.server.plugins.NotFoundException +import io.ktor.server.plugins.statuspages.StatusPages +import io.ktor.server.response.respond + +class AuthorizationException(message: String) : Exception(message) + +fun Application.configureErrors() { + install(StatusPages) { + exception { call, cause -> + when (cause) { + is IllegalArgumentException -> call.respond( + status = HttpStatusCode.BadRequest, + message = cause.message!! + ) + + is NotFoundException -> call.respond( + status = HttpStatusCode.NotFound, + message = cause.message ?: "" + ) + + is AuthorizationException -> call.respond( + status = HttpStatusCode.Unauthorized, + message = cause.message ?: "" + ) + + else -> call.respond( + status = HttpStatusCode.InternalServerError, + message = cause.message ?: "" + ) + } + } + } +} diff --git a/backend/src/main/kotlin/plugins/Routing.kt b/backend/src/main/kotlin/plugins/Routing.kt index a31e282..6105fec 100644 --- a/backend/src/main/kotlin/plugins/Routing.kt +++ b/backend/src/main/kotlin/plugins/Routing.kt @@ -1,14 +1,11 @@ package dev.svitan.plugins +import dev.svitan.routes.routeAction import dev.svitan.routes.routeAuth -import io.ktor.http.* import io.ktor.server.application.* -import io.ktor.server.plugins.NotFoundException import io.ktor.server.plugins.requestvalidation.* -import io.ktor.server.plugins.statuspages.* import io.ktor.server.response.* -import io.ktor.server.routing.get -import io.ktor.server.routing.routing +import io.ktor.server.routing.* fun Application.configureRouting() { install(RequestValidation) { @@ -19,27 +16,6 @@ fun Application.configureRouting() { } } - install(StatusPages) { - exception { call, cause -> - when (cause) { - is IllegalArgumentException -> call.respond( - status = HttpStatusCode.BadRequest, - message = cause.message!! - ) - - is NotFoundException -> call.respond( - status = HttpStatusCode.NotFound, - message = cause.message ?: "" - ) - - else -> call.respond( - status = HttpStatusCode.InternalServerError, - message = cause.message ?: "" - ) - } - } - } - routing { get("/") { call.respond("Hello World!") @@ -47,4 +23,5 @@ fun Application.configureRouting() { } routeAuth() + routeAction() } diff --git a/backend/src/main/kotlin/routes/Action.kt b/backend/src/main/kotlin/routes/Action.kt new file mode 100644 index 0000000..5ff4e77 --- /dev/null +++ b/backend/src/main/kotlin/routes/Action.kt @@ -0,0 +1,67 @@ +package dev.svitan.routes + +import dev.svitan.plugins.AuthorizationException +import dev.svitan.schemas.ActionService +import dev.svitan.schemas.AuthService +import dev.svitan.schemas.NewActionDTO +import dev.svitan.schemas.RunActionDTO +import dev.svitan.schemas.sha256 +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.plugins.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import java.util.* + +fun Application.routeAction() { + routing { + authentication { + get("/action") { + call.respond(ActionService.readAll()) + } + + post("/action") { + val action = call.receive() + call.respond(HttpStatusCode.Created, ActionService.create(action)) + } + + get("/action/{id}") { + val idRaw = call.parameters["id"] ?: throw BadRequestException("Invalid id") + val id = UUID.fromString(idRaw) + val action = ActionService.read(id) ?: throw NotFoundException() + call.respond(action) + } + + post("/action/{id}") { + val idRaw = call.parameters["id"] ?: throw BadRequestException("Invalid id") + val id = UUID.fromString(idRaw) + } + + put("/action/{id}") { + val idRaw = call.parameters["id"] ?: throw BadRequestException("Invalid id") + val id = UUID.fromString(idRaw) + + val action = ActionService.read(id) ?: throw NotFoundException() + val run = call.receive() + val auth = AuthService.validate( + UUID.fromString(action.authId), + run.pin, + run.key + ) ?: throw AuthorizationException("Invalid pin or key") + + // TODO: run the action + + call.respond(HttpStatusCode.NoContent) + } + + delete("/action/{id}") { + val idRaw = call.parameters["id"] ?: throw BadRequestException("Invalid id") + val id = UUID.fromString(idRaw) + ActionService.delete(id) + call.respond(HttpStatusCode.NoContent) + } + } + } +} diff --git a/backend/src/main/kotlin/routes/Auth.kt b/backend/src/main/kotlin/routes/Auth.kt index 7546ec1..77fe346 100644 --- a/backend/src/main/kotlin/routes/Auth.kt +++ b/backend/src/main/kotlin/routes/Auth.kt @@ -19,7 +19,7 @@ fun Application.routeAuth() { call.respond(AuthService.readAll()) } - put("/auth") { + post("/auth") { val auth = call.receive() call.respond(HttpStatusCode.Created, AuthService.create(auth)) } diff --git a/backend/src/main/kotlin/schemas/Action.kt b/backend/src/main/kotlin/schemas/Action.kt index d59ea7e..929e6b1 100644 --- a/backend/src/main/kotlin/schemas/Action.kt +++ b/backend/src/main/kotlin/schemas/Action.kt @@ -40,7 +40,16 @@ class ActionDTO( @Serializable class NewActionDTO( - val name: String, val kind: String, val source: String, val authId: String + val name: String, + val kind: String, + val source: String, + val authId: String +) + +@Serializable +class RunActionDTO( + val pin: String, + val key: String ) class ActionService { diff --git a/backend/src/main/kotlin/schemas/Auth.kt b/backend/src/main/kotlin/schemas/Auth.kt index 35b6e0d..be9fec6 100644 --- a/backend/src/main/kotlin/schemas/Auth.kt +++ b/backend/src/main/kotlin/schemas/Auth.kt @@ -73,6 +73,19 @@ class AuthService { } } + fun validate(id: UUID, pin: String, key: String): AuthDTO? = transaction { + Auths.selectAll() + .where { (Auths.id eq id) and (Auths.pin eq sha256(pin)) and (Auths.key eq sha256(key)) } + .map { + AuthDTO( + it[Auths.id].toString(), + it[Auths.name], + it[Auths.createdAt] + ) + } + .singleOrNull() + } + fun delete(id: UUID) { transaction { Auths.deleteWhere { Auths.id eq id }