Skip to content

Commit e0b9803

Browse files
Handle expired signature GraphQL errors with ApolloAuthInterceptor
1 parent 6ec2e24 commit e0b9803

2 files changed

Lines changed: 82 additions & 1 deletion

File tree

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.cornellappdev.uplift.data.auth
2+
3+
import com.apollographql.apollo.ApolloClient
4+
import com.apollographql.apollo.api.ApolloRequest
5+
import com.apollographql.apollo.api.ApolloResponse
6+
import com.apollographql.apollo.api.ExecutionContext
7+
import com.apollographql.apollo.api.Operation
8+
import com.apollographql.apollo.interceptor.ApolloInterceptor
9+
import com.apollographql.apollo.interceptor.ApolloInterceptorChain
10+
import com.cornellappdev.uplift.RefreshAccessTokenMutation
11+
import kotlinx.coroutines.flow.Flow
12+
import kotlinx.coroutines.flow.emitAll
13+
import kotlinx.coroutines.flow.first
14+
import kotlinx.coroutines.flow.flow
15+
import javax.inject.Inject
16+
import javax.inject.Named
17+
import javax.inject.Singleton
18+
19+
/**
20+
* Execution context to track retries.
21+
*/
22+
internal class RetryContext(val retryCount: Int) : ExecutionContext.Element {
23+
override val key: ExecutionContext.Key<*> = Key
24+
25+
companion object Key : ExecutionContext.Key<RetryContext>
26+
}
27+
28+
/**
29+
* An Apollo Interceptor that handles token expiration errors that return as 200 OK with
30+
* GraphQL errors (specifically "Signature has expired").
31+
*/
32+
@Singleton
33+
class ApolloAuthInterceptor @Inject constructor(
34+
private val tokenManager: TokenManager,
35+
private val sessionManager: SessionManager,
36+
@Named("refresh") private val refreshClient: ApolloClient
37+
) : ApolloInterceptor {
38+
override fun <D : Operation.Data> intercept(
39+
request: ApolloRequest<D>,
40+
chain: ApolloInterceptorChain
41+
): Flow<ApolloResponse<D>> = flow {
42+
val response = chain.proceed(request).first()
43+
44+
val retryCount = request.executionContext[RetryContext]?.retryCount ?: 0
45+
// TODO: replace string check with explicit error codes if backend implements
46+
if (response.errors?.any { it.message.contains("Signature has expired") } == true && retryCount < 1) {
47+
val refreshToken = tokenManager.getRefreshToken()
48+
if (refreshToken != null) {
49+
try {
50+
val mutationResponse = refreshClient.mutation(RefreshAccessTokenMutation())
51+
.addHttpHeader("Authorization", "Bearer $refreshToken")
52+
.execute()
53+
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
63+
} else {
64+
sessionManager.logout()
65+
}
66+
} catch (e: Exception) {
67+
sessionManager.logout()
68+
}
69+
} else {
70+
sessionManager.logout()
71+
}
72+
}
73+
74+
emit(response)
75+
}
76+
}

app/src/main/java/com/cornellappdev/uplift/di/AppModule.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.cornellappdev.uplift.di
33
import com.apollographql.apollo.ApolloClient
44
import com.apollographql.apollo.network.okHttpClient
55
import com.cornellappdev.uplift.BuildConfig
6+
import com.cornellappdev.uplift.data.auth.ApolloAuthInterceptor
67
import com.cornellappdev.uplift.data.auth.AuthInterceptor
78
import com.cornellappdev.uplift.data.auth.TokenAuthenticator
89
import dagger.Module
@@ -67,10 +68,14 @@ object AppModule {
6768
@Provides
6869
@Singleton
6970
@Named("main")
70-
fun provideApolloClient(@Named("main") okHttpClient: OkHttpClient): ApolloClient {
71+
fun provideApolloClient(
72+
@Named("main") okHttpClient: OkHttpClient,
73+
apolloAuthInterceptor: ApolloAuthInterceptor
74+
): ApolloClient {
7175
return ApolloClient.Builder()
7276
.serverUrl(BuildConfig.BACKEND_URL)
7377
.okHttpClient(okHttpClient)
78+
.addInterceptor(apolloAuthInterceptor)
7479
.build()
7580
}
7681

0 commit comments

Comments
 (0)