PM-38998: Feat: Apply optional auth token to config endpoint (#7055)

This commit is contained in:
David Perez
2026-06-12 15:40:56 -05:00
committed by GitHub
parent cc9b7d3edf
commit 2432665327
5 changed files with 33 additions and 17 deletions

View File

@@ -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.

View File

@@ -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>(AuthState.Uninitialized)
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
private val mutableEnvironmentStateFlow = MutableStateFlow<Environment>(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)

View File

@@ -121,7 +121,7 @@ internal class BitwardenServiceClientImpl(
override val configService: ConfigService by lazy {
ConfigServiceImpl(
configApi = retrofits.unauthenticatedApiRetrofit.create(),
configApi = retrofits.authenticatedApiRetrofit.create(),
)
}

View File

@@ -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()

View File

@@ -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<String> = 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"))
}
}
}