MP-24 Epic 4: Fertigstellung von MP-24: Authentication DI Refactoring & Cleanup
Das Refactoring der Authentifizierungs-Komponenten auf Dependency Injection (Koin) wurde verifiziert und abgeschlossen. Alle manuellen Instanziierungen wurden entfernt und die korrekte Initialisierung in allen Entry-Points sichergestellt.
This commit is contained in:
+4
-5
@@ -6,7 +6,6 @@ import io.ktor.client.request.forms.*
|
||||
import io.ktor.http.*
|
||||
import kotlinx.serialization.Serializable
|
||||
import io.ktor.client.HttpClient
|
||||
import org.koin.core.context.GlobalContext
|
||||
import org.koin.core.qualifier.named
|
||||
|
||||
/**
|
||||
@@ -31,6 +30,7 @@ data class LoginResponse(
|
||||
* HTTP client for authentication API calls
|
||||
*/
|
||||
class AuthApiClient(
|
||||
private val httpClient: HttpClient,
|
||||
// Keycloak Basis-URL (z. B. http://localhost:8180)
|
||||
private val keycloakBaseUrl: String = AppConstants.KEYCLOAK_URL,
|
||||
// Realm-Name in Keycloak
|
||||
@@ -40,7 +40,6 @@ class AuthApiClient(
|
||||
// Optional: Client-Secret (nur bei vertraulichen Clients erforderlich)
|
||||
private val clientSecret: String? = null
|
||||
) {
|
||||
private val client: HttpClient by lazy { GlobalContext.get().koin.get<HttpClient>(named("apiClient")) }
|
||||
|
||||
/**
|
||||
* Authenticate user with username and password
|
||||
@@ -48,7 +47,7 @@ class AuthApiClient(
|
||||
suspend fun login(username: String, password: String): LoginResponse {
|
||||
val tokenEndpoint = "$keycloakBaseUrl/realms/$realm/protocol/openid-connect/token"
|
||||
return try {
|
||||
val response = client.submitForm(
|
||||
val response = httpClient.submitForm(
|
||||
url = tokenEndpoint,
|
||||
formParameters = Parameters.build {
|
||||
append("grant_type", "password")
|
||||
@@ -93,7 +92,7 @@ class AuthApiClient(
|
||||
suspend fun exchangeAuthorizationCode(code: String, codeVerifier: String, redirectUri: String): LoginResponse {
|
||||
val tokenEndpoint = "$keycloakBaseUrl/realms/$realm/protocol/openid-connect/token"
|
||||
return try {
|
||||
val response = client.submitForm(
|
||||
val response = httpClient.submitForm(
|
||||
url = tokenEndpoint,
|
||||
formParameters = Parameters.build {
|
||||
append("grant_type", "authorization_code")
|
||||
@@ -136,7 +135,7 @@ class AuthApiClient(
|
||||
suspend fun refreshToken(refreshToken: String): LoginResponse {
|
||||
val tokenEndpoint = "$keycloakBaseUrl/realms/$realm/protocol/openid-connect/token"
|
||||
return try {
|
||||
val response = client.submitForm(
|
||||
val response = httpClient.submitForm(
|
||||
url = tokenEndpoint,
|
||||
formParameters = Parameters.build {
|
||||
append("grant_type", "refresh_token")
|
||||
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package at.mocode.clients.authfeature
|
||||
|
||||
import at.mocode.shared.core.AppConstants
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
/**
|
||||
* Singleton object for managing authenticated HTTP client configuration.
|
||||
* Provides methods to create HTTP clients and add authentication headers manually.
|
||||
*/
|
||||
object AuthenticatedHttpClient {
|
||||
|
||||
private val authTokenManager = AuthTokenManager()
|
||||
|
||||
/**
|
||||
* Create a basic HTTP client with JSON support
|
||||
*/
|
||||
fun create(baseUrl: String = AppConstants.GATEWAY_URL): HttpClient {
|
||||
return HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an authentication header to an HTTP request builder if a token is available
|
||||
*/
|
||||
fun HttpRequestBuilder.addAuthHeader() {
|
||||
authTokenManager.getBearerToken()?.let { bearerToken ->
|
||||
header(HttpHeaders.Authorization, bearerToken)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shared AuthTokenManager instance
|
||||
*/
|
||||
fun getAuthTokenManager(): AuthTokenManager = authTokenManager
|
||||
|
||||
/**
|
||||
* Create an HTTP client without authentication (for login/public endpoints)
|
||||
*/
|
||||
fun createUnauthenticated(): HttpClient {
|
||||
return HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -14,13 +14,11 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LoginScreen(
|
||||
authTokenManager: AuthTokenManager,
|
||||
viewModel: LoginViewModel = viewModel { LoginViewModel(authTokenManager) },
|
||||
viewModel: LoginViewModel,
|
||||
onLoginSuccess: () -> Unit = {}
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
|
||||
+3
-6
@@ -3,8 +3,6 @@ package at.mocode.clients.authfeature
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.ktor.client.request.post
|
||||
import org.koin.core.context.GlobalContext
|
||||
import org.koin.core.qualifier.named
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -31,15 +29,14 @@ data class LoginUiState(
|
||||
* ViewModel for handling login authentication logic
|
||||
*/
|
||||
class LoginViewModel(
|
||||
private val authTokenManager: AuthTokenManager
|
||||
private val authTokenManager: AuthTokenManager,
|
||||
private val authApiClient: AuthApiClient,
|
||||
private val apiClient: HttpClient
|
||||
) : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(LoginUiState())
|
||||
val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()
|
||||
|
||||
private val authApiClient = AuthApiClient()
|
||||
private val apiClient: HttpClient by lazy { GlobalContext.get().koin.get<HttpClient>(named("apiClient")) }
|
||||
|
||||
fun updateUsername(username: String) {
|
||||
_uiState.value = _uiState.value.copy(
|
||||
username = username,
|
||||
|
||||
+9
@@ -1,7 +1,10 @@
|
||||
package at.mocode.clients.authfeature.di
|
||||
|
||||
import at.mocode.clients.authfeature.AuthApiClient
|
||||
import at.mocode.clients.authfeature.AuthTokenManager
|
||||
import at.mocode.clients.authfeature.LoginViewModel
|
||||
import at.mocode.frontend.core.network.TokenProvider
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
|
||||
/**
|
||||
@@ -11,6 +14,12 @@ val authFeatureModule = module {
|
||||
// Single in-memory token manager
|
||||
single { AuthTokenManager() }
|
||||
|
||||
// AuthApiClient with injected apiClient
|
||||
single { AuthApiClient(get(named("apiClient"))) }
|
||||
|
||||
// LoginViewModel
|
||||
factory { LoginViewModel(get(), get(), get(named("apiClient"))) }
|
||||
|
||||
// Bridge to core network TokenProvider without adding a hard dependency there
|
||||
single<TokenProvider> {
|
||||
object : TokenProvider {
|
||||
|
||||
Reference in New Issue
Block a user