fix(auth-interceptor): Close requests & various improvements

This commit is contained in:
Peter Vacho 2024-12-30 02:35:46 +01:00
parent bd43bc7958
commit 82b3614920
Signed by: school
GPG key ID: 8CFC3837052871B4

View file

@ -52,62 +52,67 @@ class AuthInterceptor(
val path = originalRequest.url().encodedPath()
// Check if the method-path combination is in the bypass list
bypassedUrls[method]?.let { paths ->
if (paths.contains(path)) {
return chain.proceed(originalRequest)
}
if (bypassedUrls[method]?.contains(path) == true) {
return chain.proceed(originalRequest)
}
try {
// No point in continuing if our refresh token expired, make the user to re-login
if (tokenManager.refreshToken == null || tokenManager.isRefreshTokenExpired()) {
handleFail()
// No point in continuing if our refresh token expired, make the user to re-login
if (tokenManager.refreshToken == null || tokenManager.isRefreshTokenExpired()) {
return handleSessionExpired(chain)
}
try {
refreshAccessTokenIfNeeded()
} catch (e: CancellationException) {
return handleSessionExpired(chain)
}
// Override the original request and add the access token, which should be
// available & non-expired after the above checks (though it's still possible
// that it was invalidated through other means, so we can still fail here).
val requestWithToken = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer ${tokenManager.accessToken}")
.build()
var response = chain.proceed(requestWithToken)
// Handle authentication failure, this indicates that our token was invalidated
// on the server side
if (response.code() == 401) {
Log.w("API", "Non-expired stored access token produced 401 (server invalidation?)")
// Close the previous response before retrying
response.close()
// Clear the access token, as it didn't work anyways, we got 401 with it
tokenManager.clearAccessToken()
// This will definitely trigger a refresh now, as we cleared the access token
try {
refreshAccessTokenIfNeeded()
} catch (e: CancellationException) {
return handleSessionExpired(chain)
}
refreshAccessTokenIfNeeded()
// Retry the original request with the refreshed access token
val retryRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer ${tokenManager.accessToken}")
.build()
// Override the original request and add the access token, which should be
// available & non-expired after the above checks (though it's still possible
// that it was invalidated through other means, so we can still fail here).
val requestBuilder = originalRequest.newBuilder()
requestBuilder.addHeader("Authorization", "Bearer $tokenManager.accessToken")
val response = chain.proceed(requestBuilder.build())
response = chain.proceed(retryRequest)
// Handle authentication failure, if it occurs
// If this request also lead to a 401, something is very wrong, as the access token
// was in fact refreshed by now, which means our refresh token does work, but the
// access token it gave us wasn't valid.
if (response.code() == 401) {
// Clear the access token, as it didn't work anyways, we got 401 with it
tokenManager.clearAccessToken()
// This will definitely trigger a refresh now, as we cleared the access token
refreshAccessTokenIfNeeded()
// Retry the original request with the refreshed access token
val newBuilder = originalRequest.newBuilder()
newBuilder.addHeader("Authorization", "Bearer ${tokenManager.accessToken}")
val newResponse = chain.proceed(newBuilder.build())
// If this request also lead to a 401, something is very wrong, as the access token
// was in fact refreshed by now, which means our refresh token does work, but the
// access token it gave us wasn't valid.
if (newResponse.code() == 401) {
Log.e("API", "Got 403 from a freshly refreshed access token: $newResponse")
throw IllegalStateException("Got 403 from a freshly refreshed access token")
}
return newResponse
response.close()
Log.e("API", "Got 401 from a freshly refreshed access token")
return handleSessionExpired(chain)
}
return response
} catch (e: CancellationException) {
// Return an error response to gracefully end the request
return Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(401)
.message("Session expired")
.body(ResponseBody.create(null, "Session expired"))
.build()
}
return response
}
@Synchronized // Avoid simultaneous refresh attempts
@ -120,18 +125,25 @@ class AuthInterceptor(
}
if (!refreshed) {
handleFail()
throw CancellationException()
}
}
}
private fun handleFail(): Nothing {
private fun handleSessionExpired(chain: Interceptor.Chain): Response {
Log.e("API", "Session expired or refresh token is invalid. Redirecting to login.")
tokenManager.clearTokens()
navigateToLoginActivity()
// End the current request chain gracefully
throw CancellationException("Session expired. User redirected to login.") }
return Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(401)
.message("Session expired")
.body(ResponseBody.create(null, "Session expired"))
.build()
}
private fun navigateToLoginActivity() {
val intent = Intent(context, LoginActivity::class.java)