From b3c9b3f75c7e94263b17afbb2247d22749d19154 Mon Sep 17 00:00:00 2001 From: Shreyash Borde <116138842+Shreyash16b@users.noreply.github.com> Date: Tue, 20 May 2025 20:34:34 +0530 Subject: [PATCH] Logout on Unauthorized access (#1864) --- .../mifospay/core/common/GlobalAuthManager.kt | 24 +++++++++++++ .../designsystem/component/AlertDialog.kt | 8 +++-- .../core/network/utils/KtorInterceptor.kt | 17 ++++++++++ .../kotlin/org/mifospay/shared/MifosPayApp.kt | 34 +++++++++++++++++++ 4 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 core/common/src/commonMain/kotlin/org/mifospay/core/common/GlobalAuthManager.kt diff --git a/core/common/src/commonMain/kotlin/org/mifospay/core/common/GlobalAuthManager.kt b/core/common/src/commonMain/kotlin/org/mifospay/core/common/GlobalAuthManager.kt new file mode 100644 index 00000000..fb104174 --- /dev/null +++ b/core/common/src/commonMain/kotlin/org/mifospay/core/common/GlobalAuthManager.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifospay.core.common + +import kotlinx.coroutines.flow.MutableStateFlow + +object GlobalAuthManager { + val isUnauthorized = MutableStateFlow(false) + + fun markUnauthorized() { + isUnauthorized.value = true + } + + fun reset() { + isUnauthorized.value = false + } +} diff --git a/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/AlertDialog.kt b/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/AlertDialog.kt index 8a0115a0..76437c97 100644 --- a/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/AlertDialog.kt +++ b/core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/AlertDialog.kt @@ -22,7 +22,7 @@ fun MifosDialogBox( title: String, showDialogState: Boolean, confirmButtonText: String, - dismissButtonText: String, + dismissButtonText: String? = null, onConfirm: () -> Unit, onDismiss: () -> Unit, modifier: Modifier = Modifier, @@ -48,8 +48,10 @@ fun MifosDialogBox( } }, dismissButton = { - TextButton(onClick = onDismiss) { - Text(text = dismissButtonText) + if (!dismissButtonText.isNullOrBlank()) { + TextButton(onClick = onDismiss) { + Text(text = dismissButtonText) + } } }, ) diff --git a/core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/KtorInterceptor.kt b/core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/KtorInterceptor.kt index 060f025a..80125e7b 100644 --- a/core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/KtorInterceptor.kt +++ b/core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/KtorInterceptor.kt @@ -13,7 +13,10 @@ import io.ktor.client.HttpClient import io.ktor.client.plugins.HttpClientPlugin import io.ktor.client.request.HttpRequestPipeline import io.ktor.client.request.header +import io.ktor.client.statement.HttpResponsePipeline +import io.ktor.http.HttpStatusCode import io.ktor.util.AttributeKey +import org.mifospay.core.common.GlobalAuthManager import org.mifospay.core.datastore.UserPreferencesRepository class KtorInterceptor( @@ -38,6 +41,13 @@ class KtorInterceptor( } } } + + scope.responsePipeline.intercept(HttpResponsePipeline.After) { + if (context.response.status == HttpStatusCode.Unauthorized) { + GlobalAuthManager.markUnauthorized() + } + proceedWith(subject) + } } override fun prepare(block: Config.() -> Unit): KtorInterceptor { @@ -75,6 +85,13 @@ class KtorInterceptorRe( } } } + + scope.responsePipeline.intercept(HttpResponsePipeline.After) { + if (context.response.status == HttpStatusCode.Unauthorized) { + GlobalAuthManager.markUnauthorized() + } + proceedWith(subject) + } } override fun prepare(block: ConfigRe.() -> Unit): KtorInterceptorRe { diff --git a/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/MifosPayApp.kt b/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/MifosPayApp.kt index e58580e0..39ca953e 100644 --- a/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/MifosPayApp.kt +++ b/mifospay-shared/src/commonMain/kotlin/org/mifospay/shared/MifosPayApp.kt @@ -10,14 +10,19 @@ package org.mifospay.shared import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.compose.rememberNavController import org.koin.compose.koinInject import org.koin.compose.viewmodel.koinViewModel +import org.mifospay.core.common.GlobalAuthManager import org.mifospay.core.data.util.NetworkMonitor import org.mifospay.core.data.util.TimeZoneMonitor +import org.mifospay.core.designsystem.component.MifosDialogBox import org.mifospay.core.designsystem.theme.MifosTheme import org.mifospay.shared.MainUiState.Success import org.mifospay.shared.navigation.MifosNavGraph.LOGIN_GRAPH @@ -43,6 +48,35 @@ private fun MifosPayApp( val uiState by viewModel.uiState.collectAsStateWithLifecycle() val navController = rememberNavController() + var showErrorDialog = remember { mutableStateOf(false) } + val isUnauthorized by GlobalAuthManager.isUnauthorized.collectAsStateWithLifecycle() + + LaunchedEffect(isUnauthorized) { + if (isUnauthorized) { + showErrorDialog.value = true + } + } + + if (showErrorDialog.value) { + MifosDialogBox( + title = "Unauthorized User", + showDialogState = showErrorDialog.value, + confirmButtonText = "Ok", + onConfirm = { + showErrorDialog.value = false + viewModel.logOut() + navController.navigate(LOGIN_GRAPH) { + popUpTo(navController.graph.id) { + inclusive = true + } + } + GlobalAuthManager.reset() + }, + onDismiss = {}, + message = "Please login again to continue", + ) + } + val navDestination = when (uiState) { is MainUiState.Loading -> LOGIN_GRAPH is Success -> if ((uiState as Success).userData.authenticated) {