diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/network/NetworkConfigManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/network/NetworkConfigManagerImpl.kt index ba1779c39d..0e3e74fa73 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/network/NetworkConfigManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/network/NetworkConfigManagerImpl.kt @@ -6,8 +6,12 @@ import com.bitwarden.network.BitwardenServiceClient import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach private const val ENVIRONMENT_DEBOUNCE_TIMEOUT_MS: Long = 500L @@ -26,9 +30,11 @@ class NetworkConfigManagerImpl( private val collectionScope = CoroutineScope(dispatcherManager.unconfined) init { - @Suppress("OPT_IN_USAGE") - environmentRepository - .environmentStateFlow + @OptIn(FlowPreview::class) + combine( + environmentRepository.environmentStateFlow, + authRepository.userStateFlow.map { it?.activeUserId }.distinctUntilChanged(), + ) { _, _ -> } .debounce(timeoutMillis = ENVIRONMENT_DEBOUNCE_TIMEOUT_MS) .onEach { _ -> // This updates the stored service configuration by performing a network request. diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/network/NetworkConfigManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/network/NetworkConfigManagerTest.kt index bfa93737bd..deec797b39 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/network/NetworkConfigManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/network/NetworkConfigManagerTest.kt @@ -8,6 +8,7 @@ import com.bitwarden.data.repository.model.Environment import com.bitwarden.network.BitwardenServiceClient import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.model.AuthState +import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import io.mockk.coEvery import io.mockk.coVerify @@ -29,10 +30,12 @@ class NetworkConfigManagerTest { unconfined = testDispatcher, ) private val mutableAuthStateFlow = MutableStateFlow(AuthState.Uninitialized) + private val mutableUserStateFlow = MutableStateFlow(null) private val mutableEnvironmentStateFlow = MutableStateFlow(Environment.Us) private val authRepository: AuthRepository = mockk { every { authStateFlow } returns mutableAuthStateFlow + every { userStateFlow } returns mutableUserStateFlow } private val environmentRepository: EnvironmentRepository = mockk { every { environmentStateFlow } returns mutableEnvironmentStateFlow @@ -57,10 +60,13 @@ class NetworkConfigManagerTest { ) } + @Suppress("MaxLineLength") @Test - fun `changes in the Environment should call getServerConfig after debounce period`() { - mutableEnvironmentStateFlow.value = Environment.Us + fun `changes in the Environment or active user ID should call getServerConfig after debounce period`() { mutableEnvironmentStateFlow.value = Environment.Eu + mutableUserStateFlow.value = mockk { every { activeUserId } returns "userId" } + mutableEnvironmentStateFlow.value = Environment.Us + mutableUserStateFlow.value = null testDispatcher.advanceTimeByAndRunCurrent(delayTimeMillis = 500L) coVerify(exactly = 1) { serverConfigRepository.getServerConfig(forceRefresh = true) diff --git a/network/src/main/kotlin/com/bitwarden/network/BitwardenServiceClientImpl.kt b/network/src/main/kotlin/com/bitwarden/network/BitwardenServiceClientImpl.kt index f054cb3e01..a346fbc7b0 100644 --- a/network/src/main/kotlin/com/bitwarden/network/BitwardenServiceClientImpl.kt +++ b/network/src/main/kotlin/com/bitwarden/network/BitwardenServiceClientImpl.kt @@ -121,7 +121,7 @@ internal class BitwardenServiceClientImpl( override val configService: ConfigService by lazy { ConfigServiceImpl( - configApi = retrofits.unauthenticatedApiRetrofit.create(), + configApi = retrofits.authenticatedApiRetrofit.create(), ) } diff --git a/network/src/main/kotlin/com/bitwarden/network/interceptor/AuthTokenManager.kt b/network/src/main/kotlin/com/bitwarden/network/interceptor/AuthTokenManager.kt index 51aa56bfb4..64014b0f6d 100644 --- a/network/src/main/kotlin/com/bitwarden/network/interceptor/AuthTokenManager.kt +++ b/network/src/main/kotlin/com/bitwarden/network/interceptor/AuthTokenManager.kt @@ -19,7 +19,6 @@ import java.time.Clock import java.time.Instant import java.time.temporal.ChronoUnit -private const val MISSING_TOKEN_MESSAGE: String = "Auth token is missing!" private const val MISSING_PROVIDER_MESSAGE: String = "Refresh token provider is missing!" private const val EXPIRATION_OFFSET_MINUTES: Long = 5L @@ -83,8 +82,10 @@ internal class AuthTokenManager( @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { - val token = getAccessToken() - ?: throw IOException(IllegalStateException(MISSING_TOKEN_MESSAGE)) + val token = getAccessToken() ?: run { + Timber.w("Auth token is missing! Proceeding without token.") + return chain.proceed(chain.request()) + } val request = chain .request() .newBuilder() diff --git a/network/src/test/kotlin/com/bitwarden/network/interceptor/AuthTokenManagerTest.kt b/network/src/test/kotlin/com/bitwarden/network/interceptor/AuthTokenManagerTest.kt index 08906a4efd..80f706f2fe 100644 --- a/network/src/test/kotlin/com/bitwarden/network/interceptor/AuthTokenManagerTest.kt +++ b/network/src/test/kotlin/com/bitwarden/network/interceptor/AuthTokenManagerTest.kt @@ -270,16 +270,19 @@ class AuthTokenManagerTest { } @Test - fun `intercept should throw an exception when an auth token data is missing`() { - val throwable = assertThrows(IOException::class.java) { - authTokenManager.intercept( - chain = FakeInterceptorChain(request = request), - ) + fun `intercept should proceed without token when an auth token data is missing`() { + val token = "token" + authTokenManager.refreshTokenProvider = object : RefreshTokenProvider { + override fun refreshAccessTokenSynchronously( + userId: String, + ): Result = token.asSuccess() } - assertEquals( - "Auth token is missing!", - throwable.cause?.message, + every { mockAuthTokenProvider.getAuthTokenDataOrNull() } returns null + + val response = authTokenManager.intercept( + chain = FakeInterceptorChain(request = request), ) + assertNull(response.request.header("Authorization")) } } }