Implement token invalidation
This commit is contained in:
@@ -13,4 +13,6 @@ interface AuthRepo {
|
||||
suspend fun requireAuthHeader(): String
|
||||
|
||||
suspend fun logout()
|
||||
|
||||
fun invalidateAuthHeader(header: String)
|
||||
}
|
||||
@@ -37,12 +37,14 @@ class AuthRepoImpl @Inject constructor(
|
||||
}.getOrThrow() // Throw error to show it to user
|
||||
}
|
||||
|
||||
override suspend fun getAuthHeader(): String? {
|
||||
override suspend fun getAuthHeader(): String? = runCatchingExceptCancel {
|
||||
Timber.v("getAuthHeader() called")
|
||||
return currentAccount()
|
||||
currentAccount()
|
||||
?.let { getAuthToken(it) }
|
||||
?.let { AUTH_HEADER_FORMAT.format(it) }
|
||||
}
|
||||
}.onFailure {
|
||||
Timber.e(it, "getAuthHeader: can't request auth header")
|
||||
}.getOrNull()
|
||||
|
||||
private suspend fun getAuthToken(account: Account?): String? {
|
||||
return account?.let { accountManagerInteractor.getAuthToken(it) }
|
||||
@@ -67,6 +69,16 @@ class AuthRepoImpl @Inject constructor(
|
||||
accountManagerInteractor.removeAccount(account)
|
||||
}
|
||||
|
||||
override fun invalidateAuthHeader(header: String) {
|
||||
Timber.v("invalidateAuthHeader() called with: header = $header")
|
||||
val token = header.substringAfter("Bearer ")
|
||||
if (token == header) {
|
||||
Timber.w("invalidateAuthHeader: can't find token in $header")
|
||||
} else {
|
||||
accountManagerInteractor.invalidateAuthToken(token)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val AUTH_HEADER_FORMAT = "Bearer %s"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package gq.kirmanak.mealient.data.network
|
||||
|
||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AuthenticationInterceptor @Inject constructor(
|
||||
private val authRepo: AuthRepo,
|
||||
) : Interceptor {
|
||||
|
||||
private val authHeader: String?
|
||||
get() = runBlocking { authRepo.getAuthHeader() }
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val currentHeader = authHeader ?: return chain.proceed(chain.request())
|
||||
val response = proceedWithAuthHeader(chain, currentHeader)
|
||||
if (listOf(401, 403).contains(response.code)) {
|
||||
authRepo.invalidateAuthHeader(currentHeader)
|
||||
}
|
||||
val newHeader = authHeader ?: return response
|
||||
return proceedWithAuthHeader(chain, newHeader)
|
||||
}
|
||||
|
||||
private fun proceedWithAuthHeader(
|
||||
chain: Interceptor.Chain,
|
||||
authHeader: String,
|
||||
) = chain.proceed(
|
||||
chain.request()
|
||||
.newBuilder()
|
||||
.header(HEADER_NAME, authHeader)
|
||||
.build()
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val HEADER_NAME = "Authorization"
|
||||
}
|
||||
}
|
||||
@@ -5,17 +5,17 @@ import okhttp3.OkHttpClient
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class OkHttpBuilder
|
||||
@Inject
|
||||
constructor(
|
||||
class OkHttpBuilder @Inject constructor(
|
||||
// Use @JvmSuppressWildcards because otherwise dagger can't inject it (https://stackoverflow.com/a/43149382)
|
||||
private val interceptors: Set<@JvmSuppressWildcards Interceptor>
|
||||
private val authenticationInterceptor: AuthenticationInterceptor,
|
||||
private val interceptors: Set<@JvmSuppressWildcards Interceptor>,
|
||||
) {
|
||||
|
||||
fun buildOkHttp(): OkHttpClient {
|
||||
Timber.v("buildOkHttp() called")
|
||||
return OkHttpClient.Builder()
|
||||
.apply { for (interceptor in interceptors) addNetworkInterceptor(interceptor) }
|
||||
.build()
|
||||
return OkHttpClient.Builder().apply {
|
||||
addInterceptor(authenticationInterceptor)
|
||||
for (interceptor in interceptors) addNetworkInterceptor(interceptor)
|
||||
}.build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package gq.kirmanak.mealient.data.recipes.network
|
||||
|
||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||
import gq.kirmanak.mealient.data.network.ServiceFactory
|
||||
import gq.kirmanak.mealient.data.recipes.network.response.GetRecipeResponse
|
||||
import gq.kirmanak.mealient.data.recipes.network.response.GetRecipeSummaryResponse
|
||||
@@ -10,20 +9,19 @@ import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class RecipeDataSourceImpl @Inject constructor(
|
||||
private val authRepo: AuthRepo,
|
||||
private val recipeServiceFactory: ServiceFactory<RecipeService>,
|
||||
) : RecipeDataSource {
|
||||
|
||||
override suspend fun requestRecipes(start: Int, limit: Int): List<GetRecipeSummaryResponse> {
|
||||
Timber.v("requestRecipes() called with: start = $start, limit = $limit")
|
||||
val recipeSummary = getRecipeService().getRecipeSummary(start, limit, getToken())
|
||||
val recipeSummary = getRecipeService().getRecipeSummary(start, limit)
|
||||
Timber.v("requestRecipes() returned: $recipeSummary")
|
||||
return recipeSummary
|
||||
}
|
||||
|
||||
override suspend fun requestRecipeInfo(slug: String): GetRecipeResponse {
|
||||
Timber.v("requestRecipeInfo() called with: slug = $slug")
|
||||
val recipeInfo = getRecipeService().getRecipe(slug, getToken())
|
||||
val recipeInfo = getRecipeService().getRecipe(slug)
|
||||
Timber.v("requestRecipeInfo() returned: $recipeInfo")
|
||||
return recipeInfo
|
||||
}
|
||||
@@ -32,6 +30,4 @@ class RecipeDataSourceImpl @Inject constructor(
|
||||
Timber.v("getRecipeService() called")
|
||||
return recipeServiceFactory.provideService()
|
||||
}
|
||||
|
||||
private suspend fun getToken(): String? = authRepo.getAuthHeader()
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package gq.kirmanak.mealient.data.recipes.network
|
||||
import gq.kirmanak.mealient.data.recipes.network.response.GetRecipeResponse
|
||||
import gq.kirmanak.mealient.data.recipes.network.response.GetRecipeSummaryResponse
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
@@ -12,12 +11,10 @@ interface RecipeService {
|
||||
suspend fun getRecipeSummary(
|
||||
@Query("start") start: Int,
|
||||
@Query("limit") limit: Int,
|
||||
@Header("Authorization") authHeader: String?,
|
||||
): List<GetRecipeSummaryResponse>
|
||||
|
||||
@GET("/api/recipes/{recipe_slug}")
|
||||
suspend fun getRecipe(
|
||||
@Path("recipe_slug") recipeSlug: String,
|
||||
@Header("Authorization") authHeader: String?,
|
||||
): GetRecipeResponse
|
||||
}
|
||||
Reference in New Issue
Block a user