Authentication aufräumen, 1. Schritt

This commit is contained in:
Hartmut Bauer committed 2026-02-11 01:59:54 +01:00
1 parent bf7119f580
commit 677d57fb3c
6 files changed
+59 -69

No files matched your search

+2 -2
View File
@@ -1,3 +1,3 @@
#auto generated
#Mon Feb 09 23:38:51 CET 2026
build.number=226
#Wed Feb 11 01:28:34 CET 2026
build.number=233
@@ -54,8 +54,9 @@ fun App() {
}
Button(onClick = {
CoroutineScope(Dispatchers.Default).launch {
logger.debug { "requesting '/test'" }
test.request("/test")
val testurl = "/books"
logger.debug { "requesting $testurl" }
test.request(testurl)
}
}) {
Text("Test Token Refresh")
@@ -77,13 +78,13 @@ fun App() {
@Single
class TestAuth(val httpClient: HttpClient) {
private val srv = "https://kas.keinekekse.net/"
private val srv = "https://kas.keinekekse.net"
suspend fun request(url: String) {
logger.debug { "connecting" }
// val response = httpClient.get(srv + "testdb/books")
// val books: List<Book> = response.body()
val response = httpClient.get(srv + url + "testdb/books")
val response = httpClient.get(srv + url)
val body = response.bodyAsText()
logger.debug { "call to $url" }
@@ -4,13 +4,13 @@ import net.keinekekse.version.Version
object VERSION: Version {
override val version = "1.0.0"
override val fullVersion = "1.0.0+build.226"
override val fullVersion = "1.0.0+build.233"
override val major = 1
override val minor = 0
override val patch = 0
override val preRelease = ""
override val buildMetadata = "build.226"
override val buildDate = "2026-02-09T22:38:51.084372Z"
override val buildMetadata = "build.233"
override val buildDate = "2026-02-11T00:28:34.345856Z"
override val displayName = "Books App"
override val copyright = "Copyright (c) 2026 Hartmut Bauer"
override val name = "books"
@@ -17,8 +17,8 @@ sealed interface AuthResult {
data class Error(val code: String, val message: String, val details: String? = null) :
AuthResult
data class Fatal(val code: String, val message: String, val details: String? = null) :
AuthResult
// data class Fatal(val code: String, val message: String, val details: String? = null) :
// AuthResult
}
@Single
@@ -1,6 +1,5 @@
package net.keinekekse.app.auth
import com.auth0.jwt.JWT
import io.github.oshai.kotlinlogging.KotlinLogging
import io.ktor.client.*
import io.ktor.client.call.*
@@ -25,7 +24,6 @@ import net.keinekekse.app.service.TokenService
import org.koin.core.annotation.Single
import java.awt.Desktop
import java.net.Socket
import java.util.*
import kotlin.use
private val logger = KotlinLogging.logger {}
@@ -50,40 +48,43 @@ class Authenticator(
override fun close() = httpClient.close()
suspend fun authenticate(): AuthResult {
// check if necessary
val tokens = tokenService.getTokens()
if (tokens != null) {
val access = JWT.decode(tokens.accessToken)
val refresh = JWT.decode(tokens.refreshToken)
val now = Date()
logger.debug { "Now: $now" }
logger.debug { "Token expires at: ${access.expiresAt}" }
logger.debug { "Token is expired: ${access.expiresAt.before(Date())}" }
logger.debug { "Refresh token expires at: ${refresh.expiresAt}" }
logger.debug { "Refresh token is expired: ${refresh.expiresAt.before(Date())}" }
if (access.expiresAt.before(now)) {
// access_token ungültig
if (refresh.expiresAt.before(now)) {
// refresh_token ungültig
return doAuthentication()
} else {
val result = requestRefreshToken(tokens)
return if (result != null) {
AuthResult.Success()
} else {
doAuthentication()
}
}
} else {
return AuthResult.Success()
}
} else {
return doAuthentication()
}
}
// suspend fun authenticate(): AuthResult {
// // check if necessary
// logger.debug { "Authenticator: authenticate()" }
// val tokens = tokenService.getTokens()
// if (tokens != null) {
// val access = JWT.decode(tokens.accessToken)
// val refresh = JWT.decode(tokens.refreshToken)
// val now = Date()
// logger.debug { "Now: $now" }
// logger.debug { "Token expires at: ${access.expiresAt}" }
// logger.debug { "Token is expired: ${access.expiresAt.before(Date())}" }
// logger.debug { "Refresh token expires at: ${refresh.expiresAt}" }
// logger.debug { "Refresh token is expired: ${refresh.expiresAt.before(Date())}" }
// if (access.expiresAt.before(now)) {
// // access_token ungültig
// if (refresh.expiresAt.before(now)) {
// // refresh_token ungültig
// return doAuthentication()
// } else {
// val result = requestRefreshToken(tokens)
// return if (result != null) {
// AuthResult.Success()
// } else {
// doAuthentication()
// }
// }
// } else {
// logger.debug { "Authenticator: Token is still valid." }
// return AuthResult.Success()
// }
// } else {
// return doAuthentication()
// }
// }
private suspend fun doAuthentication(): AuthResult {
private suspend fun authenticate(): AuthResult {
logger.debug { "Authenticator: doAuthentication()" }
val config = AuthRun(keycloakConfig)
if (!testServerConnection(config)) {
return AuthResult.Error("ERR001", "Der Anmeldeserver ist nicht erreichbar.", "")
@@ -94,7 +95,7 @@ class Authenticator(
val port = server.engine.resolvedConnectors().first().port
config.buildRedirectUri(port)
if (!waitForServer(port)) {
return AuthResult.Fatal(
return AuthResult.Error(
"ERR009",
"Schwerer Fehler",
"Der interne Webserver konnte nicht gestartet werden. Keine Anmeldung möglich."
@@ -117,6 +118,7 @@ class Authenticator(
}
suspend fun reAuthenticate(): BearerTokens? {
logger.debug { "Authenticator: reAuthenticate()" }
val authResult = authenticate()
if (authResult is AuthResult.Success) {
return tokenService.getTokens()
@@ -152,12 +154,6 @@ class Authenticator(
var error = ""
var errorDetails = ""
when (val err = result.first) {
is AuthResult.Fatal -> {
logger.debug { err.code }
error = err.message ?: ""
errorDetails = err.details ?: ""
}
is AuthResult.Error -> {
logger.debug { err.code }
error = err.message ?: ""
@@ -272,13 +268,6 @@ class Authenticator(
status = HttpStatusCode.BadRequest
}
is AuthResult.Fatal -> {
text =
"<html><body><h1>Schwerer Fehler.</h1><h2>${htmlEscape(authResult.message)}</h2><h3>${
htmlEscape(authResult.details ?: "")
}</h3></body></html>"
status = HttpStatusCode.InternalServerError
}
}
return Pair(text, status)
}
@@ -305,7 +294,7 @@ class Authenticator(
)
}
if (request.queryParameters["state"] != csrfProtectionState) {
return AuthResult.Fatal(
return AuthResult.Error(
"ERR005",
"Schwerer Fehler",
"CSRF-Fehler in der Kommunikation mit dem Anmeldeserver. Mögliche Ursachen: Netzwerkfehler, Serverfehler oder Malware."
@@ -349,7 +338,7 @@ class Authenticator(
private suspend fun requestTokens(config: AuthRun, authCode: String?): AuthResult {
if (authCode == null) {
return AuthResult.Fatal(
return AuthResult.Error(
"ERR003",
"Schwerer Fehler",
"Der Anmeldeserver lieferte ungültige Daten. Mögliche Ursachen: Netzwerkfehler, Serverfehler oder Malware."
@@ -387,7 +376,7 @@ class Authenticator(
try {
val errorResponse = response.body<TokenErrorResponse>()
return Pair(
AuthResult.Fatal(
AuthResult.Error(
"ERR010",
errorResponse.error,
"${errorResponse.errorDescription} (${response.status})"
@@ -410,7 +399,7 @@ class Authenticator(
} catch (e: Exception) {
// Fehler beim JSON-Konvertieren
return Pair(
AuthResult.Fatal(
AuthResult.Error(
"ERR008",
"Schwerer Fehler",
"Ungültige Daten beim Abholen der Zugriffstoken. Mögliche Ursachen: Netzwerkfehler, Serverfehler oder Malware."
@@ -41,14 +41,14 @@ fun createHttpClient(authenticator: Authenticator): HttpClient {
logger.debug { "Refresh token is expired: ${refresh.expiresAt.before(Date())}" }
if (refresh.expiresAt.before(now)) {
logger.debug { "refresh token expired - HOW TO authenticate" }
logger.debug { "HTTPClient: refresh token expired - reAuthenticate()" }
authenticator.reAuthenticate()
} else {
logger.debug { "refresh token valid - request new tokens" }
logger.debug { "HTTPClient: refresh token valid - requestRefreshToken()" }
authenticator.requestRefreshToken(oldTokens)
}
} else {
logger.debug { "no tokens - authenticate" }
logger.debug { "HTTPClient: no tokens - reAuthenticate()" }
authenticator.reAuthenticate()
}
}
@@ -71,9 +71,9 @@ fun createHttpClient(authenticator: Authenticator): HttpClient {
}
client.plugin(HttpSend).intercept { request ->
logger.debug { "HTTPClient: intercept ${request.url}" }
logger.debug { "HTTPCli: intercept ${request.url}" }
val originalCall = execute(request)
logger.debug { "HTTPClient: response status ${originalCall.response.status}" }
logger.debug { "HTTPCli: response status ${originalCall.response.status}" }
originalCall
// val originalCall = execute(request)
// if (originalCall.response.status.value !in 100..399) {