refactor: update Dockerfile paths, network module auth flow, and Keycloak config
Updated Dockerfiles to fix frontend path references after refactoring. Refactored `networkModule` to replace the `Auth` plugin with manual auth header injection for enhanced logout support. Adjusted Keycloak realm configuration to set default credentials as non-temporary. Improved error handling in `AuthApiClient` with detailed response messages.
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
# ===================================================================
|
# ===================================================================
|
||||||
# Multi-stage Dockerfile for Meldestelle API Gateway
|
# Multi-stage Dockerfile for Meldestelle API Gateway
|
||||||
# Features: Security hardening, monitoring support, optimal caching, BuildKit cache mounts
|
# Features: Security hardening, monitoring support, optimal caching, BuildKit cache mounts
|
||||||
# Version: 2.2.1 - Optimized for Monorepo (Fixed missing frontend dirs)
|
# Version: 2.2.2 - Optimized for Monorepo (Fixed frontend paths after refactoring)
|
||||||
# ===================================================================
|
# ===================================================================
|
||||||
|
|
||||||
# === CENTRALIZED BUILD ARGUMENTS ===
|
# === CENTRALIZED BUILD ARGUMENTS ===
|
||||||
@@ -55,13 +55,13 @@ COPY contracts/ contracts/
|
|||||||
# Create dummy frontend directories to satisfy settings.gradle.kts include paths
|
# Create dummy frontend directories to satisfy settings.gradle.kts include paths
|
||||||
# This prevents Gradle from failing configuration phase without copying actual frontend code
|
# This prevents Gradle from failing configuration phase without copying actual frontend code
|
||||||
RUN mkdir -p \
|
RUN mkdir -p \
|
||||||
|
frontend/core/auth \
|
||||||
frontend/core/domain \
|
frontend/core/domain \
|
||||||
frontend/core/design-system \
|
frontend/core/design-system \
|
||||||
frontend/core/navigation \
|
frontend/core/navigation \
|
||||||
frontend/core/network \
|
frontend/core/network \
|
||||||
frontend/core/local-db \
|
frontend/core/local-db \
|
||||||
frontend/core/sync \
|
frontend/core/sync \
|
||||||
frontend/features/auth-feature \
|
|
||||||
frontend/features/ping-feature \
|
frontend/features/ping-feature \
|
||||||
frontend/shared \
|
frontend/shared \
|
||||||
frontend/shells/meldestelle-portal \
|
frontend/shells/meldestelle-portal \
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# ===================================================================
|
# ===================================================================
|
||||||
# Multi-stage Dockerfile for Meldestelle Ping Service
|
# Multi-stage Dockerfile for Meldestelle Ping Service
|
||||||
# Features: Security hardening, monitoring support, optimal caching, BuildKit cache mounts
|
# Features: Security hardening, monitoring support, optimal caching, BuildKit cache mounts
|
||||||
# Version: 2.2.0 - Optimized for Monorepo (Fixed missing frontend dirs)
|
# Version: 2.2.1 - Optimized for Monorepo (Fixed frontend paths after refactoring)
|
||||||
# ===================================================================
|
# ===================================================================
|
||||||
|
|
||||||
# === CENTRALIZED BUILD ARGUMENTS ===
|
# === CENTRALIZED BUILD ARGUMENTS ===
|
||||||
@@ -53,13 +53,13 @@ COPY contracts/ contracts/
|
|||||||
|
|
||||||
# Create dummy frontend directories to satisfy settings.gradle.kts include paths
|
# Create dummy frontend directories to satisfy settings.gradle.kts include paths
|
||||||
RUN mkdir -p \
|
RUN mkdir -p \
|
||||||
|
frontend/core/auth \
|
||||||
frontend/core/domain \
|
frontend/core/domain \
|
||||||
frontend/core/design-system \
|
frontend/core/design-system \
|
||||||
frontend/core/navigation \
|
frontend/core/navigation \
|
||||||
frontend/core/network \
|
frontend/core/network \
|
||||||
frontend/core/local-db \
|
frontend/core/local-db \
|
||||||
frontend/core/sync \
|
frontend/core/sync \
|
||||||
frontend/features/auth-feature \
|
|
||||||
frontend/features/ping-feature \
|
frontend/features/ping-feature \
|
||||||
frontend/shared \
|
frontend/shared \
|
||||||
frontend/shells/meldestelle-portal \
|
frontend/shells/meldestelle-portal \
|
||||||
|
|||||||
@@ -238,7 +238,7 @@
|
|||||||
{
|
{
|
||||||
"type": "password",
|
"type": "password",
|
||||||
"value": "Change_Me_In_Production!",
|
"value": "Change_Me_In_Production!",
|
||||||
"temporary": true
|
"temporary": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"realmRoles": [
|
"realmRoles": [
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@
|
|||||||
Dieses Modul enthält projektübergreifende Kern-Logik und Utility-Klassen, die sowohl vom Backend als auch vom Frontend genutzt werden können.
|
Dieses Modul enthält projektübergreifende Kern-Logik und Utility-Klassen, die sowohl vom Backend als auch vom Frontend genutzt werden können.
|
||||||
|
|
||||||
**Die vollständige Dokumentation befindet sich hier:**
|
**Die vollständige Dokumentation befindet sich hier:**
|
||||||
[**-> docs/03_Domain/01_Core_Model/README.md**](../docs/03_Domain/01_Core_Model/README.md)
|
[**→ docs/03_Domain/01_Core_Model/README.md**](../docs/03_Domain/01_Core_Model/README.md)
|
||||||
|
|||||||
+5
-2
@@ -4,6 +4,7 @@ import at.mocode.shared.core.AppConstants
|
|||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
import io.ktor.client.request.forms.*
|
import io.ktor.client.request.forms.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@@ -63,9 +64,10 @@ class AuthApiClient(
|
|||||||
username = username
|
username = username
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
val errorBody = response.bodyAsText()
|
||||||
LoginResponse(
|
LoginResponse(
|
||||||
success = false,
|
success = false,
|
||||||
message = "Login fehlgeschlagen: HTTP ${response.status.value}"
|
message = "Login fehlgeschlagen: HTTP ${response.status.value} - $errorBody"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -104,9 +106,10 @@ class AuthApiClient(
|
|||||||
message = null
|
message = null
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
val errorBody = response.bodyAsText()
|
||||||
LoginResponse(
|
LoginResponse(
|
||||||
success = false,
|
success = false,
|
||||||
message = "Token refresh fehlgeschlagen: HTTP ${response.status.value}"
|
message = "Token refresh fehlgeschlagen: HTTP ${response.status.value} - $errorBody"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
+3
-4
@@ -15,10 +15,10 @@ val authModule = module {
|
|||||||
// Single in-memory token manager
|
// Single in-memory token manager
|
||||||
single { AuthTokenManager() }
|
single { AuthTokenManager() }
|
||||||
|
|
||||||
// AuthApiClient with injected apiClient and DEV client secret
|
// AuthApiClient with injected baseHttpClient (NOT apiClient)
|
||||||
single {
|
single {
|
||||||
AuthApiClient(
|
AuthApiClient(
|
||||||
httpClient = get(named("apiClient")),
|
httpClient = get(named("baseHttpClient")),
|
||||||
clientSecret = AppConstants.KEYCLOAK_CLIENT_SECRET
|
clientSecret = AppConstants.KEYCLOAK_CLIENT_SECRET
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -30,8 +30,7 @@ val authModule = module {
|
|||||||
single<TokenProvider> {
|
single<TokenProvider> {
|
||||||
object : TokenProvider {
|
object : TokenProvider {
|
||||||
override fun getAccessToken(): String? {
|
override fun getAccessToken(): String? {
|
||||||
val token = get<AuthTokenManager>().getToken()
|
return get<AuthTokenManager>().getToken()
|
||||||
return token
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-23
@@ -2,10 +2,9 @@ package at.mocode.frontend.core.network
|
|||||||
|
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.plugins.*
|
import io.ktor.client.plugins.*
|
||||||
import io.ktor.client.plugins.auth.*
|
|
||||||
import io.ktor.client.plugins.auth.providers.*
|
|
||||||
import io.ktor.client.plugins.contentnegotiation.*
|
import io.ktor.client.plugins.contentnegotiation.*
|
||||||
import io.ktor.client.plugins.logging.*
|
import io.ktor.client.plugins.logging.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.koin.core.qualifier.named
|
import org.koin.core.qualifier.named
|
||||||
@@ -19,10 +18,28 @@ interface TokenProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Koin module that provides a preconfigured Ktor HttpClient under the named qualifier "apiClient".
|
* Koin module providing HttpClients.
|
||||||
* The client uses the environment-aware base URL from NetworkConfig.
|
|
||||||
*/
|
*/
|
||||||
val networkModule = module {
|
val networkModule = module {
|
||||||
|
|
||||||
|
// 1. Base Client (Raw, for Auth/Keycloak)
|
||||||
|
single(named("baseHttpClient")) {
|
||||||
|
HttpClient {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(Json { ignoreUnknownKeys = true; isLenient = true; encodeDefaults = true })
|
||||||
|
}
|
||||||
|
install(Logging) {
|
||||||
|
logger = object : Logger {
|
||||||
|
override fun log(message: String) {
|
||||||
|
println("[baseClient] $message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
level = LogLevel.INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. API Client (Configured for Gateway & Auth Header)
|
||||||
single(named("apiClient")) {
|
single(named("apiClient")) {
|
||||||
val tokenProvider: TokenProvider? = try { get<TokenProvider>() } catch (_: Throwable) { null }
|
val tokenProvider: TokenProvider? = try { get<TokenProvider>() } catch (_: Throwable) { null }
|
||||||
HttpClient {
|
HttpClient {
|
||||||
@@ -54,19 +71,15 @@ val networkModule = module {
|
|||||||
exponentialDelay()
|
exponentialDelay()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authentication plugin (Bearer)
|
// Manual Auth Header Injection (instead of Auth plugin) to support immediate logout
|
||||||
install(Auth) {
|
install(DefaultRequest) {
|
||||||
bearer {
|
val base = NetworkConfig.baseUrl.trimEnd('/')
|
||||||
loadTokens {
|
url(base) // Set base URL
|
||||||
val token = tokenProvider?.getAccessToken()
|
|
||||||
token?.let { BearerTokens(it, refreshToken = "") }
|
// Inject Authorization header if token is present
|
||||||
}
|
val token = tokenProvider?.getAccessToken()
|
||||||
// Only send token to our API base URL
|
if (token != null) {
|
||||||
sendWithoutRequest { request ->
|
header("Authorization", "Bearer $token")
|
||||||
val base = NetworkConfig.baseUrl.trimEnd('/')
|
|
||||||
val url = request.url.toString()
|
|
||||||
url.startsWith(base)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,12 +92,6 @@ val networkModule = module {
|
|||||||
}
|
}
|
||||||
level = LogLevel.INFO
|
level = LogLevel.INFO
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set base URL
|
|
||||||
defaultRequest {
|
|
||||||
// Set only the base URL; endpoints will append paths
|
|
||||||
url(NetworkConfig.baseUrl)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user