diff --git a/books/buildnumber.properties b/books/buildnumber.properties index f903dd5..54ce895 100644 --- a/books/buildnumber.properties +++ b/books/buildnumber.properties @@ -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 diff --git a/books/src/commonMain/kotlin/net/keinekekse/app/App.kt b/books/src/commonMain/kotlin/net/keinekekse/app/App.kt index f436853..d1585cd 100644 --- a/books/src/commonMain/kotlin/net/keinekekse/app/App.kt +++ b/books/src/commonMain/kotlin/net/keinekekse/app/App.kt @@ -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 = 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" } diff --git a/books/src/commonMain/kotlin/net/keinekekse/app/books/Version.kt b/books/src/commonMain/kotlin/net/keinekekse/app/books/Version.kt index bd3f900..ef03af0 100644 --- a/books/src/commonMain/kotlin/net/keinekekse/app/books/Version.kt +++ b/books/src/commonMain/kotlin/net/keinekekse/app/books/Version.kt @@ -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" diff --git a/books/src/jvmMain/kotlin/net/keinekekse/app/auth/AuthModule.kt b/books/src/jvmMain/kotlin/net/keinekekse/app/auth/AuthModule.kt index 1ce6a52..3fd9866 100644 --- a/books/src/jvmMain/kotlin/net/keinekekse/app/auth/AuthModule.kt +++ b/books/src/jvmMain/kotlin/net/keinekekse/app/auth/AuthModule.kt @@ -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 diff --git a/books/src/jvmMain/kotlin/net/keinekekse/app/auth/Authenticator.kt b/books/src/jvmMain/kotlin/net/keinekekse/app/auth/Authenticator.kt index ad2dfc1..aa64e63 100644 --- a/books/src/jvmMain/kotlin/net/keinekekse/app/auth/Authenticator.kt +++ b/books/src/jvmMain/kotlin/net/keinekekse/app/auth/Authenticator.kt @@ -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 = - "

Schwerer Fehler.

${htmlEscape(authResult.message)}

${ - htmlEscape(authResult.details ?: "") - }

" - 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() 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." diff --git a/books/src/jvmMain/kotlin/net/keinekekse/app/service/HttpClient.kt b/books/src/jvmMain/kotlin/net/keinekekse/app/service/HttpClient.kt index 1944c56..64b545a 100644 --- a/books/src/jvmMain/kotlin/net/keinekekse/app/service/HttpClient.kt +++ b/books/src/jvmMain/kotlin/net/keinekekse/app/service/HttpClient.kt @@ -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) {