/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package de.steamwar.routes
import de.steamwar.ResponseError
import de.steamwar.plugins.SWAuthPrincipal
import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.Token
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
import java.time.format.DateTimeFormatter
import java.time.LocalDateTime
@Serializable
data class AuthLoginRequest(val username: String, val password: String)
@Serializable
data class AuthTokenResponse(val token: String)
@Serializable
data class ResponseToken(val id: Int, val name: String, val created: String) {
constructor(token: Token) : this(token.id, token.name, token.created.toLocalDateTime().toString())
}
@Serializable
data class CreateTokenRequest(val name: String, val password: String)
fun Route.configureAuthRoutes() {
route("/auth") {
post("/login") {
if (call.principal() != null) {
call.respond(HttpStatusCode.Forbidden, ResponseError("Already logged in", "already_logged_in"))
return@post
}
val request = call.receive()
val user = SteamwarUser.get(request.username)
if (user == null) {
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid username or password", "invalid"))
return@post
}
if (!user.verifyPassword(request.password)) {
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid username or password", "invalid"))
return@post
}
val code = Token.createToken("Website: ${DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now())}", user)
call.respond(AuthTokenResponse(code))
}
route("/tokens") {
install(SWPermissionCheck) {
mustAuth = true
}
get {
val auth = call.principal()
if(auth == null) {
call.respond(HttpStatusCode.InternalServerError)
return@get
}
call.respond(Token.listUser(auth.user).map { ResponseToken(it) })
}
post {
val auth = call.principal()
if(auth == null) {
call.respond(HttpStatusCode.InternalServerError)
return@post
}
val request = call.receive()
if(request.name.length > 32) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Name too long", "name_too_long"))
return@post
}
if(request.name.length < 3) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Name too short", "name_too_short"))
return@post
}
if(!auth.user.verifyPassword(request.password)) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid password", "invalid_password"))
return@post
}
val token = Token.createToken(request.name, auth.user)
call.respond(AuthTokenResponse(token))
}
route("/{id}") {
delete {
val auth = call.principal()
if(auth == null) {
call.respond(HttpStatusCode.InternalServerError)
return@delete
}
val id = call.parameters["id"]?.toIntOrNull()
if(id == null) {
call.respond(HttpStatusCode.BadRequest)
return@delete
}
val token = Token.get(id)
if(token == null) {
call.respond(HttpStatusCode.NotFound)
return@delete
}
if(token.owner != auth.user) {
call.respond(HttpStatusCode.Forbidden)
return@delete
}
token.delete()
call.respond(HttpStatusCode.OK)
}
}
post("/logout") {
val auth = call.principal()
if(auth == null) {
call.respond(HttpStatusCode.InternalServerError)
return@post
}
auth.token.delete()
call.respond(HttpStatusCode.OK)
}
}
}
}