fix(#688): route expired auth sessions to sign-in (#689)

* fix(#688): route expired auth sessions to sign-in

Adopt global 401 latch, WS disconnect, and Back/profile guards.

* chore: clean up formatting noise

* fix(ktlint): reorder imports to place java and kotlinx at end

* fix(ktlint): place java.io.IOException at end of imports

* test: fix NavBackStack type parameter in AuthenticationRoutingTest

* test: add AuthSessionState tests to AuthLoginViewModelTest

* fix(ktlint): fix import order in AuthLoginViewModelTest
This commit is contained in:
M57 authored and GitHub committed 2026-07-23 00:44:17 +03:00
1 parent def122ab58
commit a5baf41545
10 files changed
+217 -4

No files matched your search

@@ -8,6 +8,7 @@ import androidx.lifecycle.viewModelScope
import com.m57.hermescontrol.R
import com.m57.hermescontrol.data.config.ConnectionProfile
import com.m57.hermescontrol.data.local.AuthManager
import com.m57.hermescontrol.data.local.AuthSessionState
import com.m57.hermescontrol.data.remote.ApiClient
import com.m57.hermescontrol.data.remote.AuthPayloads
import com.m57.hermescontrol.data.remote.CleartextPolicy
@@ -80,9 +81,20 @@ class AuthLoginViewModel(
init {
loadLoggedInProfiles()
viewModelScope.launch {
AuthSessionState.signInRequired.collect { required ->
if (required) {
_uiState.update { it.copy(loggedInProfiles = emptyList()) }
}
}
}
}
fun loadLoggedInProfiles() {
if (AuthSessionState.signInRequired.value) {
_uiState.update { it.copy(loggedInProfiles = emptyList()) }
return
}
val allProfiles = AuthManager.getConnectionProfiles()
val loggedIn =
allProfiles.filter { profile ->
@@ -95,6 +107,7 @@ class AuthLoginViewModel(
fun useExistingProfile(profileId: String) {
AuthManager.setSelectedProfileId(profileId)
ApiClient.rebuild()
AuthSessionState.markAuthenticated()
HermesWsClient.connect()
_uiState.update { it.copy(connectionSuccess = true) }
}
@@ -345,6 +358,7 @@ class AuthLoginViewModel(
AuthManager.setWsAuthParam("ticket")
}
ApiClient.rebuild()
AuthSessionState.markAuthenticated()
HermesWsClient.connect()
_uiState.update { it.copy(isLoading = false, connectionSuccess = true) }
}
@@ -366,7 +380,7 @@ class AuthLoginViewModel(
}
val tempApi = ApiClient.createTempService(endpoint.baseUrl.toString(), token)
val result = safeApiCall { tempApi.getSessions() }
val result = safeApiCall(reportAuthExpiry = false) { tempApi.getSessions() }
return when (result) {
is com.m57.hermescontrol.data.remote.NetworkResult.Success -> {
@@ -8,6 +8,7 @@ import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.m57.hermescontrol.data.local.AuthManager
import com.m57.hermescontrol.data.local.AuthSessionState
import com.m57.hermescontrol.data.local.HermesDatabase
import com.m57.hermescontrol.data.model.Attachment
import com.m57.hermescontrol.data.model.ModelProvider
@@ -1854,6 +1855,7 @@ class ChatViewModel(
}
fun reconnect() {
AuthSessionState.markAuthenticated()
_uiState.update {
it.copy(
isLoading = true,
@@ -1951,6 +1953,7 @@ class ChatViewModel(
AuthManager.setWsAuthParam("ticket")
AuthManager.setToken(ticket)
AuthSessionState.markAuthenticated()
withContext(Dispatchers.Main) {
onResult(true, null)
@@ -8,6 +8,7 @@ import com.m57.hermescontrol.R
import com.m57.hermescontrol.data.config.ConnectionProfile
import com.m57.hermescontrol.data.config.resolveBaseUrl
import com.m57.hermescontrol.data.local.AuthManager
import com.m57.hermescontrol.data.local.AuthSessionState
import com.m57.hermescontrol.data.remote.ApiClient
import com.m57.hermescontrol.data.remote.CleartextPolicy
import com.m57.hermescontrol.data.remote.NetworkError
@@ -115,7 +116,7 @@ class ConnectViewModel(
val result =
withContext(Dispatchers.IO) {
val tempApi = ApiClient.createTempService(endpoint.baseUrl.toString(), state.token)
safeApiCall { tempApi.getStatus() }
safeApiCall(reportAuthExpiry = false) { tempApi.getStatus() }
}
when (result) {
is NetworkResult.Success -> {
@@ -123,6 +124,7 @@ class ConnectViewModel(
AuthManager.setToken(state.token)
AuthManager.setBaseUrl(state.baseUrl)
ApiClient.rebuild()
AuthSessionState.markAuthenticated()
if (state.saveProfile) {
val currentProfiles = AuthManager.getConnectionProfiles()