From 64f40686b6d7c7391032c82fd513b50ff6271e71 Mon Sep 17 00:00:00 2001 From: M57 <41645758+Hy4ri@users.noreply.github.com> Date: Tue, 23 Jun 2026 12:10:00 +0300 Subject: [PATCH] Fix #266: Clear profile-slot token on 401 error (#326) This commit modifies the connect flow in `ConnectViewModel.kt` to clear the profile-specific token alongside the global token when an HTTP 401 (Unauthorized) error is encountered. Previously, the global token was cleared, but the bad token persisted in the selected profile's slot, causing potential security and authentication handling issues. Tests have been added to `ConnectViewModelTest.kt` to verify that both the global and profile tokens are correctly nullified under a 401 scenario. --- .../ui/connect/ConnectViewModel.kt | 3 ++ .../ui/connect/ConnectViewModelTest.kt | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/app/src/main/java/com/m57/hermescontrol/ui/connect/ConnectViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/connect/ConnectViewModel.kt index 63d68967..055ed8e6 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/connect/ConnectViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/connect/ConnectViewModel.kt @@ -188,6 +188,9 @@ class ConnectViewModel(private val app: Application) : ViewModel() { when (err.code) { 401 -> { AuthManager.setToken(null) + if (AuthManager.getSelectedProfileId() != null) { + AuthManager.setProfileToken(AuthManager.getSelectedProfileId()!!, null) + } app.getString(R.string.connect_error_401) } diff --git a/app/src/test/java/com/m57/hermescontrol/ui/connect/ConnectViewModelTest.kt b/app/src/test/java/com/m57/hermescontrol/ui/connect/ConnectViewModelTest.kt index 1c626649..8883e87c 100644 --- a/app/src/test/java/com/m57/hermescontrol/ui/connect/ConnectViewModelTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/ui/connect/ConnectViewModelTest.kt @@ -219,6 +219,35 @@ class ConnectViewModelTest { viewModel.connect() advanceUntilIdle() + verify { AuthManager.setToken(null) } + + val state = viewModel.uiState.value + assertFalse(state.isConnecting) + assertFalse(state.connectionSuccess) + assertEquals("Invalid token (401 Unauthorized)", state.errorMessage) + } + + @Test + fun testConnect_failure_unauthorized_clearsProfileToken() = + runTest { + every { AuthManager.getSelectedProfileId() } returns "prof-1" + + val viewModel = ConnectViewModel(mockApp) + viewModel.onTokenChange("invalid-token") + viewModel.onHostChange("127.0.0.1") + viewModel.onPortChange("9119") + + val mockResponse = mockk>() + every { mockResponse.isSuccessful } returns false + every { mockResponse.code() } returns 401 + coEvery { mockApiService.getStatus() } returns mockResponse + + viewModel.connect() + advanceUntilIdle() + + verify { AuthManager.setToken(null) } + verify { AuthManager.setProfileToken("prof-1", null) } + val state = viewModel.uiState.value assertFalse(state.isConnecting) assertFalse(state.connectionSuccess)