Skip to content

Commit 1588a95

Browse files
Address PR feedback: add Mutex for thread-safety and improve logging
1 parent e0b9803 commit 1588a95

1 file changed

Lines changed: 41 additions & 16 deletions

File tree

app/src/main/java/com/cornellappdev/uplift/data/auth/ApolloAuthInterceptor.kt

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.cornellappdev.uplift.data.auth
22

3+
import android.util.Log
34
import com.apollographql.apollo.ApolloClient
45
import com.apollographql.apollo.api.ApolloRequest
56
import com.apollographql.apollo.api.ApolloResponse
@@ -12,6 +13,8 @@ import kotlinx.coroutines.flow.Flow
1213
import kotlinx.coroutines.flow.emitAll
1314
import kotlinx.coroutines.flow.first
1415
import kotlinx.coroutines.flow.flow
16+
import kotlinx.coroutines.sync.Mutex
17+
import kotlinx.coroutines.sync.withLock
1518
import javax.inject.Inject
1619
import javax.inject.Named
1720
import javax.inject.Singleton
@@ -35,38 +38,60 @@ class ApolloAuthInterceptor @Inject constructor(
3538
private val sessionManager: SessionManager,
3639
@Named("refresh") private val refreshClient: ApolloClient
3740
) : ApolloInterceptor {
41+
private val mutex = Mutex()
42+
3843
override fun <D : Operation.Data> intercept(
3944
request: ApolloRequest<D>,
4045
chain: ApolloInterceptorChain
4146
): Flow<ApolloResponse<D>> = flow {
4247
val response = chain.proceed(request).first()
4348

4449
val retryCount = request.executionContext[RetryContext]?.retryCount ?: 0
50+
4551
// TODO: replace string check with explicit error codes if backend implements
4652
if (response.errors?.any { it.message.contains("Signature has expired") } == true && retryCount < 1) {
4753
val refreshToken = tokenManager.getRefreshToken()
4854
if (refreshToken != null) {
49-
try {
50-
val mutationResponse = refreshClient.mutation(RefreshAccessTokenMutation())
51-
.addHttpHeader("Authorization", "Bearer $refreshToken")
52-
.execute()
55+
val newAccessToken = mutex.withLock {
56+
// Check if another request already refreshed the token while we were waiting for the lock
57+
val currentAccessToken = tokenManager.getAccessToken()
58+
val requestToken = request.httpHeaders?.find { it.name == "Authorization" }?.value?.substringAfter("Bearer ")
5359

54-
val newAccessToken = mutationResponse.data?.refreshAccessToken?.newAccessToken
55-
if (newAccessToken != null) {
56-
tokenManager.saveTokens(newAccessToken, refreshToken)
57-
// Retry the request with the new token
58-
val newRequest = request.newBuilder()
59-
.addExecutionContext(RetryContext(retryCount + 1))
60-
.build()
61-
emitAll(chain.proceed(newRequest))
62-
return@flow
60+
if (currentAccessToken != null && currentAccessToken != requestToken) {
61+
currentAccessToken
6362
} else {
64-
sessionManager.logout()
63+
try {
64+
val mutationResponse = refreshClient.mutation(RefreshAccessTokenMutation())
65+
.addHttpHeader("Authorization", "Bearer $refreshToken")
66+
.execute()
67+
68+
val refreshedToken = mutationResponse.data?.refreshAccessToken?.newAccessToken
69+
if (refreshedToken != null) {
70+
tokenManager.saveTokens(refreshedToken, refreshToken)
71+
refreshedToken
72+
} else {
73+
Log.e("ApolloAuthInterceptor", "Refresh token mutation returned null access token")
74+
sessionManager.logout()
75+
null
76+
}
77+
} catch (e: Exception) {
78+
Log.e("ApolloAuthInterceptor", "Token refresh failed with exception", e)
79+
sessionManager.logout()
80+
null
81+
}
6582
}
66-
} catch (e: Exception) {
67-
sessionManager.logout()
83+
}
84+
85+
if (newAccessToken != null) {
86+
// Retry the request with the new token
87+
val newRequest = request.newBuilder()
88+
.addExecutionContext(RetryContext(retryCount + 1))
89+
.build()
90+
emitAll(chain.proceed(newRequest))
91+
return@flow
6892
}
6993
} else {
94+
Log.d("ApolloAuthInterceptor", "No refresh token available, logging out")
7095
sessionManager.logout()
7196
}
7297
}

0 commit comments

Comments
 (0)