Create OkHttp authenticator without token invalidation
This commit is contained in:
@@ -4,5 +4,4 @@ interface AuthenticationProvider {
|
||||
|
||||
suspend fun getAuthHeader(): String?
|
||||
|
||||
suspend fun invalidateAuthHeader()
|
||||
}
|
||||
@@ -6,9 +6,8 @@ import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.IntoSet
|
||||
import gq.kirmanak.mealient.datasource.impl.AuthInterceptor
|
||||
import gq.kirmanak.mealient.datasource.impl.CacheBuilderImpl
|
||||
import gq.kirmanak.mealient.datasource.impl.MealieAuthenticator
|
||||
import gq.kirmanak.mealient.datasource.impl.NetworkRequestWrapperImpl
|
||||
import gq.kirmanak.mealient.datasource.impl.OkHttpBuilderImpl
|
||||
import gq.kirmanak.mealient.datasource.impl.RetrofitBuilder
|
||||
@@ -20,7 +19,7 @@ import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1Impl
|
||||
import gq.kirmanak.mealient.datasource.v1.MealieServiceV1
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Authenticator
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Converter
|
||||
@@ -91,6 +90,5 @@ interface DataSourceModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
@IntoSet
|
||||
fun bindAuthInterceptor(authInterceptor: AuthInterceptor): Interceptor
|
||||
fun bindAuthenticator(mealieAuthenticator: MealieAuthenticator): Authenticator
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package gq.kirmanak.mealient.datasource.impl
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import gq.kirmanak.mealient.datasource.AuthenticationProvider
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import retrofit2.HttpException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AuthInterceptor @Inject constructor(
|
||||
private val authenticationProvider: Provider<AuthenticationProvider>,
|
||||
) : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val token = getAuthHeader()
|
||||
return if (token == null) {
|
||||
proceedWithAuthHeader(chain)
|
||||
} else {
|
||||
try {
|
||||
proceedWithAuthHeader(chain, token)
|
||||
} catch (e: HttpException) {
|
||||
if (e.code() in setOf(401, 403)) {
|
||||
invalidateAuthHeader()
|
||||
proceedWithAuthHeader(chain, getAuthHeader())
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithAuthHeader(chain: Interceptor.Chain, token: String? = null): Response {
|
||||
val requestBuilder = chain.request().newBuilder()
|
||||
val request = if (token == null) {
|
||||
requestBuilder.removeHeader(HEADER_NAME).build()
|
||||
} else {
|
||||
requestBuilder.header(HEADER_NAME, token).build()
|
||||
}
|
||||
return chain.proceed(request)
|
||||
}
|
||||
|
||||
private fun getAuthHeader() = runBlocking {
|
||||
authenticationProvider.get().getAuthHeader()
|
||||
}
|
||||
|
||||
private fun invalidateAuthHeader() = runBlocking {
|
||||
authenticationProvider.get().invalidateAuthHeader()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@VisibleForTesting
|
||||
const val HEADER_NAME = "Authorization"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package gq.kirmanak.mealient.datasource.impl
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import gq.kirmanak.mealient.datasource.AuthenticationProvider
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.Authenticator
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.Route
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class MealieAuthenticator @Inject constructor(
|
||||
private val authenticationProvider: Provider<AuthenticationProvider>,
|
||||
) : Authenticator {
|
||||
|
||||
override fun authenticate(route: Route?, response: Response): Request? {
|
||||
val supportsBearer = response.challenges().any { it.scheme == BEARER_SCHEME }
|
||||
val request = response.request
|
||||
return if (supportsBearer && request.header(HEADER_NAME) == null) {
|
||||
getAuthHeader()?.let { request.copyWithHeader(HEADER_NAME, it) }
|
||||
} else {
|
||||
null // Either Bearer is not supported or we've already tried to authenticate
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAuthHeader() = runBlocking { authenticationProvider.get().getAuthHeader() }
|
||||
|
||||
companion object {
|
||||
@VisibleForTesting
|
||||
const val HEADER_NAME = "Authorization"
|
||||
private const val BEARER_SCHEME = "Bearer"
|
||||
}
|
||||
}
|
||||
|
||||
private fun Request.copyWithHeader(name: String, value: String): Request {
|
||||
return newBuilder().header(name, value).build()
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package gq.kirmanak.mealient.datasource.impl
|
||||
|
||||
import gq.kirmanak.mealient.datasource.CacheBuilder
|
||||
import gq.kirmanak.mealient.datasource.OkHttpBuilder
|
||||
import okhttp3.Authenticator
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import javax.inject.Inject
|
||||
@@ -12,10 +13,12 @@ class OkHttpBuilderImpl @Inject constructor(
|
||||
private val cacheBuilder: CacheBuilder,
|
||||
// Use @JvmSuppressWildcards because otherwise dagger can't inject it (https://stackoverflow.com/a/43149382)
|
||||
private val interceptors: Set<@JvmSuppressWildcards Interceptor>,
|
||||
private val authenticator: Authenticator,
|
||||
) : OkHttpBuilder {
|
||||
|
||||
override fun buildOkHttp(): OkHttpClient = OkHttpClient.Builder()
|
||||
.apply { interceptors.forEach(::addNetworkInterceptor) }
|
||||
.cache(cacheBuilder.buildCache())
|
||||
.authenticator(authenticator)
|
||||
.build()
|
||||
}
|
||||
Reference in New Issue
Block a user