refactor: enhance platform configuration, database schema handling, and Keycloak setup

Improved `PlatformConfig` API base URL resolution with enhanced logging and fallback logic. Revised database initialization with version checks, schema migration, and error handling. Updated Keycloak configuration to enable `Direct Access Grants` and refine CORS/redirect settings. Adjusted Webpack proxy settings for correct API routing.
This commit is contained in:
2026-01-27 15:12:58 +01:00
parent bddeba81be
commit 637d610a5b
12 changed files with 250 additions and 38 deletions
@@ -43,9 +43,19 @@ class AuthApiClient(
formParameters = Parameters.build {
append("grant_type", "password")
append("client_id", clientId)
if (!clientSecret.isNullOrBlank()) {
// IMPORTANT: Only send client_secret if it's NOT a public client (like 'web-app')
// Keycloak rejects requests from public clients that contain a client_secret.
// We check if the client ID suggests a public client or if secret is explicitly provided.
// For now, we rely on the fact that 'web-app' is public and should NOT have a secret sent.
// Logic: If clientId is 'web-app', we force ignore the secret, or we rely on caller to pass null.
// Since AppConstants might still have the secret for 'postman-client', we need to be careful.
if (!clientSecret.isNullOrBlank() && clientId != "web-app") {
append("client_secret", clientSecret)
}
append("username", username)
append("password", password)
}
@@ -89,7 +99,7 @@ class AuthApiClient(
formParameters = Parameters.build {
append("grant_type", "refresh_token")
append("client_id", clientId)
if (!clientSecret.isNullOrBlank()) {
if (!clientSecret.isNullOrBlank() && clientId != "web-app") {
append("client_secret", clientSecret)
}
append("refresh_token", refreshToken)
@@ -28,9 +28,11 @@ val authModule = module {
// Bridge to core network TokenProvider without adding a hard dependency there
single<TokenProvider> {
// We need to capture the AuthTokenManager instance to avoid issues with 'this' context in JS
val tokenManager = get<AuthTokenManager>()
object : TokenProvider {
override fun getAccessToken(): String? {
return get<AuthTokenManager>().getToken()
return tokenManager.getToken()
}
}
}
@@ -5,6 +5,8 @@ import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@@ -14,6 +16,7 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@@ -26,6 +29,7 @@ fun LoginScreen(
) {
val uiState by viewModel.uiState.collectAsState()
val passwordFocusRequester = remember { FocusRequester() }
var passwordVisible by remember { mutableStateOf(false) }
Scaffold(
topBar = {
@@ -75,7 +79,19 @@ fun LoginScreen(
enabled = !uiState.isLoading,
isError = uiState.passwordError != null,
supportingText = uiState.passwordError?.let { { Text(it) } },
visualTransformation = PasswordVisualTransformation(),
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
val image = if (passwordVisible)
Icons.Filled.Visibility
else
Icons.Filled.VisibilityOff
val description = if (passwordVisible) "Passwort verbergen" else "Passwort anzeigen"
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(imageVector = image, description)
}
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done
@@ -114,7 +114,10 @@ class LoginViewModel(
viewModelScope.launch {
try {
// Fire-and-forget sync call; Bearer token added by Ktor Auth plugin
apiClient.post("/api/members/sync")
// IMPORTANT: Use relative path (no leading slash) so Ktor appends it to baseUrl
// baseUrl is http://localhost:8080/api (JS) or http://localhost:8081 (JVM)
// Result: http://localhost:8080/api/members/sync -> Proxy -> http://localhost:8081/api/members/sync
apiClient.post("members/sync")
} catch (_: Exception) {
// Non-fatal: Wir zeigen Sync-Fehler im Login nicht an
}