diff --git a/WebsiteBackend/build.gradle.kts b/WebsiteBackend/build.gradle.kts index 371a4b4d..76a3ca71 100644 --- a/WebsiteBackend/build.gradle.kts +++ b/WebsiteBackend/build.gradle.kts @@ -52,7 +52,7 @@ dependencies { implementation(libs.ktorRequestValidation) implementation(libs.ktorAuth) implementation(libs.ktorAuthJvm) - implementation(libs.ktorAuthLdap) + implementation(libs.ktorAuthSession) implementation(libs.ktorClientCore) implementation(libs.ktorClientJava) implementation(libs.ktorClientContentNegotiation) diff --git a/WebsiteBackend/src/de/steamwar/Application.kt b/WebsiteBackend/src/de/steamwar/Application.kt index adac9a6a..e49b9a9d 100644 --- a/WebsiteBackend/src/de/steamwar/Application.kt +++ b/WebsiteBackend/src/de/steamwar/Application.kt @@ -38,7 +38,7 @@ import java.io.File data class ResponseError(val error: String, val code: String = error) @Serializable -data class Config(val giteaToken: String) +data class Config(val giteaToken: String, val sessionSignSecret: String, val sessionEncryptSecret: String) @OptIn(ExperimentalSerializationApi::class) val config = Json.decodeFromStream(File("config.json").inputStream()) diff --git a/WebsiteBackend/src/de/steamwar/plugins/Auth.kt b/WebsiteBackend/src/de/steamwar/plugins/Auth.kt index e79d9de2..62f8456b 100644 --- a/WebsiteBackend/src/de/steamwar/plugins/Auth.kt +++ b/WebsiteBackend/src/de/steamwar/plugins/Auth.kt @@ -29,10 +29,14 @@ import io.ktor.server.application.hooks.* import io.ktor.server.auth.* import io.ktor.server.request.* import io.ktor.server.response.* +import io.ktor.server.sessions.sessions import io.ktor.util.* +import kotlinx.serialization.Serializable +@Serializable +data class SWUserSession(val userId: Int) -data class SWAuthPrincipal(val token: Token, val user: SteamwarUser) : Principal +data class SWAuthPrincipal(val user: SteamwarUser) : Principal class SWAuthConfig { var permission: UserPerm? = null diff --git a/WebsiteBackend/src/de/steamwar/plugins/Plugins.kt b/WebsiteBackend/src/de/steamwar/plugins/Plugins.kt index 4001412f..93abc473 100644 --- a/WebsiteBackend/src/de/steamwar/plugins/Plugins.kt +++ b/WebsiteBackend/src/de/steamwar/plugins/Plugins.kt @@ -19,6 +19,8 @@ package de.steamwar.plugins +import de.steamwar.config +import de.steamwar.sql.SteamwarUser import de.steamwar.sql.Token import de.steamwar.util.TokenType import de.steamwar.util.isValid @@ -31,7 +33,13 @@ import io.ktor.server.auth.* import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.plugins.cors.routing.* import io.ktor.server.plugins.ratelimit.* +import io.ktor.server.response.respond +import io.ktor.server.sessions.SessionTransportTransformerEncrypt +import io.ktor.server.sessions.Sessions +import io.ktor.server.sessions.cookie +import io.ktor.server.sessions.directorySessionStorage import kotlinx.serialization.json.Json +import java.io.File import kotlin.time.Duration.Companion.seconds fun Application.configurePlugins() { @@ -46,6 +54,7 @@ fun Application.configurePlugins() { allowHeader(HttpHeaders.ContentType) anyHost() allowXHttpMethodOverride() + allowCredentials = true } install(RateLimit) { global { @@ -54,7 +63,7 @@ fun Application.configurePlugins() { it.request.headers["X-Forwarded-For"] ?: it.request.local.remoteHost } requestWeight { applicationCall, _ -> - if(!applicationCall.request.headers.contains("X-Forwarded-For")) { + if (!applicationCall.request.headers.contains("X-Forwarded-For")) { 0 } else { 1 @@ -63,28 +72,50 @@ fun Application.configurePlugins() { } } authentication { - bearer("sw-auth") { - realm = "SteamWar API" - authenticate { call -> - val token = Token.getTokenByCode(call.token) - if (token == null) { - null - } else { - if (!token.isValid) { - token.delete() - return@authenticate null - } - if (token.type == TokenType.REFRESH_TOKEN) { - token.delete() - } + // Disabled, Maybe for API later + //bearer("sw-auth") { + // realm = "SteamWar API" + // authenticate { call -> + // val token = Token.getTokenByCode(call.token) + // if (token == null) { + // null + // } else { + // if (!token.isValid) { + // token.delete() + // return@authenticate null + // } + // if (token.type == TokenType.REFRESH_TOKEN) { + // token.delete() + // } - SWAuthPrincipal(token, token.owner) - } + // SWAuthPrincipal(token.owner) + // } + // } + //} + session("sw-session") { + validate { session -> + val steamwarUser = session.userId.let { SteamwarUser.byId(it) } + return@validate steamwarUser?.let { SWAuthPrincipal(it) } } + challenge { + call.respond(HttpStatusCode.Unauthorized) + } + } + } + install(Sessions) { + cookie("sw-session", directorySessionStorage(File("sessions"))) { + cookie.path = "/" + cookie.maxAgeInSeconds = 60 * 60 * 24 * 7 + cookie.httpOnly = true + cookie.secure = true + transform(SessionTransportTransformerEncrypt( + config.sessionEncryptSecret.toByteArray(), + config.sessionSignSecret.toByteArray() + )) } } install(ContentNegotiation) { json(Json) } install(ErrorLogger) -} +} \ No newline at end of file diff --git a/WebsiteBackend/src/de/steamwar/routes/Auth.kt b/WebsiteBackend/src/de/steamwar/routes/Auth.kt index 1100201d..8a037ad5 100644 --- a/WebsiteBackend/src/de/steamwar/routes/Auth.kt +++ b/WebsiteBackend/src/de/steamwar/routes/Auth.kt @@ -20,44 +20,60 @@ package de.steamwar.routes import de.steamwar.ResponseError -import de.steamwar.plugins.SWAuthPrincipal +import de.steamwar.config +import de.steamwar.plugins.SWUserSession import de.steamwar.sql.SteamwarUser -import de.steamwar.sql.Token -import de.steamwar.util.TokenType -import de.steamwar.util.lifetime -import de.steamwar.util.type +import io.ktor.client.HttpClient +import io.ktor.client.engine.java.Java +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.defaultRequest +import io.ktor.client.request.get +import io.ktor.client.request.headers +import io.ktor.client.statement.bodyAsText import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.json 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 io.ktor.server.sessions.clear +import io.ktor.server.sessions.sessions +import io.ktor.server.sessions.set import kotlinx.serialization.Serializable -import java.time.LocalDateTime -import kotlin.time.Duration -import kotlin.time.toJavaDuration +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive @Serializable data class UsernamePassword(val name: String, val password: String, val keepLoggedIn: Boolean = false) -@Serializable -data class ResponseToken(val token: String, val expires: String) { - constructor(token: String, lifetime: Duration) : this(token, LocalDateTime.now().plus(lifetime.toJavaDuration()).toString()) -} - -@Serializable -data class AuthTokenResponse(val accessToken: ResponseToken, val refreshToken: ResponseToken? = null) - -fun SteamwarUser.createAccessAndRefreshToken(keepLoggedIn: Boolean = false): AuthTokenResponse { - val code = System.currentTimeMillis() % 1000 - val accessToken = Token.createToken("AT-${userName}-${code}", this) - val refreshToken = if (keepLoggedIn) Token.createToken("RT-${userName}-${code}", this) else null - - return AuthTokenResponse(ResponseToken(accessToken, TokenType.ACCESS_TOKEN.lifetime), refreshToken?.let { ResponseToken(it, TokenType.REFRESH_TOKEN.lifetime) }) -} - fun Route.configureAuth() { route("/auth") { + val client = HttpClient(Java) { + install(ContentNegotiation) { + json() + } + } + + post("/discord") { + val token = call.receiveText() + + val res = client.get("https://discord.com/api/v10/oauth2/@me") { + headers { + set("Authorization", "Bearer $token") + } + } + val resJson = Json.parseToJsonElement(res.bodyAsText()).jsonObject + val discordId = resJson["user"]?.jsonObject["id"]?.jsonPrimitive?.content ?: return@post + + SteamwarUser.clear() + val user = SteamwarUser.get(discordId.toLong()) ?: return@post + + + call.sessions.set(SWUserSession(user.getId())) + call.respond(ResponseUser.get(user)) + } + post { val request = call.receive() @@ -70,37 +86,13 @@ fun Route.configureAuth() { return@post } - call.respond(user.createAccessAndRefreshToken(request.keepLoggedIn)) + call.sessions.set(SWUserSession(user.getId())) + call.respond(ResponseUser.get(user)) } - put { - val token = call.principal() - if (token == null || token.token.type != TokenType.REFRESH_TOKEN) { - call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid token type", "invalid")) - return@put - } - - val code = token.token.name.substringAfterLast('-') - - Token.listUser(token.user) - .filter { it.type == TokenType.ACCESS_TOKEN } - .filter { it.name.endsWith(code) } - .forEach { it.delete() } - - call.respond(token.user.createAccessAndRefreshToken(true)) - } delete { - val token = call.principal() - token?.let { t -> - t.token.delete() - val code = t.token.name.substringAfterLast('-') - Token.listUser(token.user) - .filter { it.type == TokenType.REFRESH_TOKEN } - .filter { it.name.endsWith(code) } - .forEach { it.delete() } - } - - call.respond(HttpStatusCode.OK) + call.sessions.clear() + call.respond(HttpStatusCode.NoContent) } } } \ No newline at end of file diff --git a/WebsiteBackend/src/de/steamwar/routes/Routes.kt b/WebsiteBackend/src/de/steamwar/routes/Routes.kt index 6e07d343..eb5444af 100644 --- a/WebsiteBackend/src/de/steamwar/routes/Routes.kt +++ b/WebsiteBackend/src/de/steamwar/routes/Routes.kt @@ -25,7 +25,7 @@ import io.ktor.server.routing.* fun Application.configureRoutes() { routing { - authenticate("sw-auth", optional = true) { + authenticate("sw-session", optional = true) { configureEventsRoute() configureDataRoutes() configureUserPerms() diff --git a/settings.gradle.kts b/settings.gradle.kts index be8e28e4..fc08ed92 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -155,7 +155,7 @@ dependencyResolutionManagement { library("ktorRequestValidation", "io.ktor:ktor-server-request-validation:$ktorVersion") library("ktorAuth", "io.ktor:ktor-server-auth:$ktorVersion") library("ktorAuthJvm", "io.ktor:ktor-server-auth-jvm:$ktorVersion") - library("ktorAuthLdap", "io.ktor:ktor-server-auth-ldap-jvm:$ktorVersion") + library("ktorAuthSession", "io.ktor:ktor-server-sessions:$ktorVersion") library("ktorClientCore", "io.ktor:ktor-client-core-jvm:$ktorVersion") library("ktorClientJava", "io.ktor:ktor-client-java:$ktorVersion") library("ktorClientContentNegotiation", "io.ktor:ktor-client-content-negotiation:$ktorVersion")