Add Login
Dieser Commit ist enthalten in:
Ursprung
d79d9b9e3d
Commit
43f6ad8d3d
@ -1 +1 @@
|
||||
Subproject commit 61985fb80673b318a9a9b6bfa345b670ac0e1da9
|
||||
Subproject commit 6aefb79e78bc49c2cd83a2ae06e442f52169116d
|
@ -26,6 +26,7 @@ import io.ktor.server.application.*
|
||||
import io.ktor.server.engine.*
|
||||
import de.steamwar.routes.configureRoutes
|
||||
import de.steamwar.sql.SchematicType
|
||||
import de.steamwar.sql.SteamwarUser
|
||||
import io.ktor.server.netty.*
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -34,7 +35,7 @@ import kotlinx.serialization.json.decodeFromStream
|
||||
import java.io.File
|
||||
|
||||
@Serializable
|
||||
data class ResponseError(val error: String)
|
||||
data class ResponseError(val error: String, val code: String = error)
|
||||
|
||||
@Serializable
|
||||
data class Config(val giteaToken: String)
|
||||
@ -44,6 +45,7 @@ val config = Json.decodeFromStream<Config>(File("config.json").inputStream())
|
||||
|
||||
fun main() {
|
||||
SchematicType.Normal.name().length
|
||||
SteamwarUser.get(3).setPassword("testtest")
|
||||
Thread {
|
||||
while (true) {
|
||||
Thread.sleep(1000 * 10)
|
||||
|
@ -32,10 +32,10 @@ import io.ktor.server.response.*
|
||||
data class SWAuthPrincipal(val token: Token, val user: SteamwarUser) : Principal
|
||||
|
||||
class SWAuthConfig {
|
||||
public var permission: UserPerm? = null
|
||||
public var allowedMethods = mutableListOf<HttpMethod>()
|
||||
public var userCheck: SWAuthPrincipal.(ApplicationRequest) -> Boolean = { true }
|
||||
public var mustAuth: Boolean = false
|
||||
var permission: UserPerm? = null
|
||||
var allowedMethods = mutableListOf<HttpMethod>()
|
||||
var userCheck: SWAuthPrincipal.(ApplicationRequest) -> Boolean = { true }
|
||||
var mustAuth: Boolean = false
|
||||
|
||||
fun allowMethod(method: HttpMethod) {
|
||||
allowedMethods.add(method)
|
||||
|
@ -31,15 +31,8 @@ import io.ktor.server.plugins.ratelimit.*
|
||||
import io.ktor.server.request.*
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.slf4j.event.*
|
||||
import java.security.MessageDigest
|
||||
import java.util.Base64
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
fun hashToken(token: String): String {
|
||||
val md = MessageDigest.getInstance("SHA-512")
|
||||
return Base64.getEncoder().encodeToString(md.digest(token.toByteArray()))
|
||||
}
|
||||
|
||||
fun Application.configurePlugins() {
|
||||
install(CORS) {
|
||||
allowMethod(HttpMethod.Options)
|
||||
@ -56,8 +49,11 @@ fun Application.configurePlugins() {
|
||||
install(RateLimit) {
|
||||
global {
|
||||
rateLimiter(limit = 60, refillPeriod = 60.seconds)
|
||||
requestKey {
|
||||
it.request.headers["X-Forwarded-Proto"] ?: it.request.local.remoteHost
|
||||
}
|
||||
requestWeight { applicationCall, _ ->
|
||||
if(applicationCall.request.local.remoteAddress == "127.0.0.1") {
|
||||
if(applicationCall.request.headers["X-Forwarded-Proto"] != null) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
@ -69,7 +65,7 @@ fun Application.configurePlugins() {
|
||||
bearer("sw-auth") {
|
||||
realm = "SteamWar API"
|
||||
authenticate { call ->
|
||||
val token = Token.get(hashToken(call.token))
|
||||
val token = Token.getTokenByCode(call.token)
|
||||
if (token == null) {
|
||||
null
|
||||
} else {
|
||||
|
168
src/main/kotlin/de/steamwar/routes/Auth.kt
Normale Datei
168
src/main/kotlin/de/steamwar/routes/Auth.kt
Normale Datei
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<SWAuthPrincipal>() != null) {
|
||||
call.respond(HttpStatusCode.Forbidden, ResponseError("Already logged in", "already_logged_in"))
|
||||
return@post
|
||||
}
|
||||
|
||||
val request = call.receive<AuthLoginRequest>()
|
||||
|
||||
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<SWAuthPrincipal>()
|
||||
|
||||
if(auth == null) {
|
||||
call.respond(HttpStatusCode.InternalServerError)
|
||||
return@get
|
||||
}
|
||||
|
||||
call.respond(Token.listUser(auth.user).map { ResponseToken(it) })
|
||||
}
|
||||
|
||||
post {
|
||||
val auth = call.principal<SWAuthPrincipal>()
|
||||
|
||||
if(auth == null) {
|
||||
call.respond(HttpStatusCode.InternalServerError)
|
||||
return@post
|
||||
}
|
||||
|
||||
val request = call.receive<CreateTokenRequest>()
|
||||
|
||||
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<SWAuthPrincipal>()
|
||||
|
||||
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<SWAuthPrincipal>()
|
||||
|
||||
if(auth == null) {
|
||||
call.respond(HttpStatusCode.InternalServerError)
|
||||
return@post
|
||||
}
|
||||
|
||||
auth.token.delete()
|
||||
call.respond(HttpStatusCode.OK)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -104,7 +104,7 @@ fun Route.configureDataRoutes() {
|
||||
call.respond(SteamwarUser.getAll().map { ResponseUser(it) })
|
||||
}
|
||||
get("/team") {
|
||||
call.respond(SteamwarUser.getAll().filter { !it.hasPerm(UserPerm.PREFIX_NONE) && !it.hasPerm(UserPerm.PREFIX_YOUTUBER) }.sortedBy { it.prefix().ordinal }.reversed().map { ResponseUser(it) })
|
||||
call.respond(SteamwarUser.getAll().filter { it.hasPerm(UserPerm.TEAM) }.sortedBy { it.prefix().ordinal }.reversed().map { ResponseUser(it) })
|
||||
}
|
||||
get("/groups") {
|
||||
call.respond(Groups.getAllGroups())
|
||||
|
@ -15,7 +15,6 @@
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package de.steamwar.routes
|
||||
|
@ -34,6 +34,7 @@ fun Application.configureRoutes() {
|
||||
configureStats()
|
||||
configurePage()
|
||||
configureSchematic()
|
||||
configureAuthRoutes()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.routes
|
||||
|
||||
import de.steamwar.plugins.SWAuthPrincipal
|
||||
@ -96,7 +115,7 @@ fun Route.configureSchematic() {
|
||||
install(SWPermissionCheck)
|
||||
get {
|
||||
val user = call.principal<SWAuthPrincipal>()!!.user
|
||||
call.respond(ResponseSchematicList(SchematicNode.list(user, null).sortedWith { o1, o2 ->
|
||||
call.respond(ResponseSchematicList(SchematicNode.list(user, null).filter { it.name != "//copy" }.sortedWith { o1, o2 ->
|
||||
if (o1.isDir || o2.isDir) {
|
||||
o2.isDir.compareTo(o1.isDir)
|
||||
} else {
|
||||
@ -178,7 +197,7 @@ fun Route.configureSchematic() {
|
||||
return@get
|
||||
}
|
||||
|
||||
call.respond(ResponseSchematicList(SchematicNode.list(user, parent.id).sortedWith { o1, o2 ->
|
||||
call.respond(ResponseSchematicList(SchematicNode.list(user, parent.id).filter { it.name != "//copy" }.sortedWith { o1, o2 ->
|
||||
if (o1.isDir || o2.isDir) {
|
||||
o2.isDir.compareTo(o1.isDir)
|
||||
} else {
|
||||
|
@ -37,7 +37,7 @@ data class UserStats(val eventFightParticipation: Int, val eventParticipation: I
|
||||
getEventParticipation(user) ?: 0,
|
||||
getAcceptedSchematics(user) ?: 0,
|
||||
getFightCount(user) ?: 0,
|
||||
user.onlinetime
|
||||
user.onlinetime / 3600.0
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ fun loadSchematicTypes(tmpTypes: MutableList<SchematicType>?, tmpFromDB: Mutable
|
||||
var checktype: SchematicType? = null
|
||||
val material: String = config.getString("Schematic.Material", "STONE_BUTTON")!!
|
||||
if (!config.getStringList("CheckQuestions").isEmpty()) {
|
||||
checktype = SchematicType("C$type", "C$shortcut", SchematicType.Type.CHECK_TYPE, null, material)
|
||||
checktype = SchematicType("C$type", "C$shortcut", SchematicType.Type.CHECK_TYPE, null, material, false)
|
||||
tmpTypes!!.add(checktype)
|
||||
tmpFromDB[checktype.toDB()] = checktype
|
||||
}
|
||||
@ -50,7 +50,8 @@ fun loadSchematicTypes(tmpTypes: MutableList<SchematicType>?, tmpFromDB: Mutable
|
||||
shortcut,
|
||||
if (config.isConfigurationSection("Server")) SchematicType.Type.FIGHT_TYPE else SchematicType.Type.NORMAL,
|
||||
checktype,
|
||||
material
|
||||
material,
|
||||
false
|
||||
)
|
||||
tmpTypes!!.add(current)
|
||||
tmpFromDB[type.lowercase(Locale.getDefault())] = current
|
||||
|
@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.util
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
@ -1,3 +1,22 @@
|
||||
<!--
|
||||
~ This file is a part of the SteamWar software.
|
||||
~
|
||||
~ Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren