fix(auth-interceptor): Close requests & various improvements
This commit is contained in:
parent
bd43bc7958
commit
82b3614920
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue