Ensure authentication token is always sent when it exists (#193)

* Ensure auth token is sent if it is present

* Allow using login token for other requests while API token is created

* Update version code
This commit is contained in:
Kirill Kamakin
2023-12-28 11:05:20 +01:00
committed by GitHub
parent ffdac4c616
commit 94f12820bc
12 changed files with 56 additions and 42 deletions

View File

@@ -2,7 +2,7 @@
import com.android.build.api.dsl.ManagedVirtualDevice
import java.io.FileInputStream
import java.util.*
import java.util.Properties
plugins {
id("gq.kirmanak.mealient.application")
@@ -17,8 +17,8 @@ plugins {
android {
defaultConfig {
applicationId = "gq.kirmanak.mealient"
versionCode = 33
versionName = "0.4.4"
versionCode = 34
versionName = "0.4.5"
testInstrumentationRunner = "gq.kirmanak.mealient.MealientTestRunner"
testInstrumentationRunnerArguments += mapOf("clearPackageData" to "true")
resourceConfigurations += listOf("en", "es", "ru", "fr", "nl", "pt", "de")

View File

@@ -4,7 +4,6 @@ import gq.kirmanak.mealient.data.auth.AuthDataSource
import gq.kirmanak.mealient.data.auth.AuthRepo
import gq.kirmanak.mealient.data.auth.AuthStorage
import gq.kirmanak.mealient.datasource.AuthenticationProvider
import gq.kirmanak.mealient.datasource.SignOutHandler
import gq.kirmanak.mealient.logging.Logger
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@@ -14,7 +13,6 @@ class AuthRepoImpl @Inject constructor(
private val authStorage: AuthStorage,
private val authDataSource: AuthDataSource,
private val logger: Logger,
private val signOutHandler: SignOutHandler,
private val credentialsLogRedactor: CredentialsLogRedactor,
) : AuthRepo, AuthenticationProvider {
@@ -23,12 +21,14 @@ class AuthRepoImpl @Inject constructor(
override suspend fun authenticate(email: String, password: String) {
logger.v { "authenticate() called" }
credentialsLogRedactor.set(email, password)
val token = authDataSource.authenticate(email, password)
credentialsLogRedactor.clear()
authStorage.setAuthToken(token)
val apiToken = authDataSource.createApiToken(API_TOKEN_NAME)
authStorage.setAuthToken(apiToken)
credentialsLogRedactor.clear()
}
override suspend fun getAuthToken(): String? = authStorage.getAuthToken()
@@ -36,7 +36,6 @@ class AuthRepoImpl @Inject constructor(
override suspend fun logout() {
logger.v { "logout() called" }
authStorage.setAuthToken(null)
signOutHandler.signOut()
}
companion object {

View File

@@ -4,6 +4,7 @@ import android.content.SharedPreferences
import androidx.annotation.VisibleForTesting
import androidx.core.content.edit
import gq.kirmanak.mealient.data.auth.AuthStorage
import gq.kirmanak.mealient.datasource.TokenChangeListener
import gq.kirmanak.mealient.datastore.DataStoreModule.Companion.ENCRYPTED
import gq.kirmanak.mealient.extensions.prefsChangeFlow
import gq.kirmanak.mealient.logging.Logger
@@ -19,6 +20,7 @@ import javax.inject.Singleton
@Singleton
class AuthStorageImpl @Inject constructor(
@Named(ENCRYPTED) private val sharedPreferences: SharedPreferences,
private val tokenChangeListener: TokenChangeListener,
private val logger: Logger,
) : AuthStorage {
@@ -28,7 +30,11 @@ class AuthStorageImpl @Inject constructor(
.distinctUntilChanged()
private val singleThreadDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
override suspend fun setAuthToken(authToken: String?) = putString(AUTH_TOKEN_KEY, authToken)
override suspend fun setAuthToken(authToken: String?) {
logger.v { "setAuthToken() called with: authToken = $authToken" }
tokenChangeListener.onTokenChange()
putString(AUTH_TOKEN_KEY, authToken)
}
override suspend fun getAuthToken(): String? = getString(AUTH_TOKEN_KEY)

View File

@@ -26,6 +26,7 @@ class AuthenticationViewModel @Inject constructor(
_uiState.value = OperationUiState.Progress()
viewModelScope.launch {
val result = runCatchingExceptCancel { authRepo.authenticate(email, password) }
logger.d { "Authentication result = $result" }
_uiState.value = OperationUiState.fromResult(result)
}
}

View File

@@ -4,7 +4,6 @@ import com.google.common.truth.Truth.assertThat
import gq.kirmanak.mealient.data.auth.AuthDataSource
import gq.kirmanak.mealient.data.auth.AuthRepo
import gq.kirmanak.mealient.data.auth.AuthStorage
import gq.kirmanak.mealient.datasource.SignOutHandler
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_API_TOKEN
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_PASSWORD
@@ -31,9 +30,6 @@ class AuthRepoImplTest : BaseUnitTest() {
@MockK(relaxUnitFun = true)
lateinit var storage: AuthStorage
@MockK(relaxUnitFun = true)
lateinit var signOutHandler: SignOutHandler
@RelaxedMockK
lateinit var credentialsLogRedactor: CredentialsLogRedactor
@@ -46,7 +42,6 @@ class AuthRepoImplTest : BaseUnitTest() {
authStorage = storage,
authDataSource = dataSource,
logger = logger,
signOutHandler = signOutHandler,
credentialsLogRedactor = credentialsLogRedactor,
)
}

View File

@@ -8,9 +8,11 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidTest
import gq.kirmanak.mealient.data.auth.AuthStorage
import gq.kirmanak.mealient.data.auth.impl.AuthStorageImpl.Companion.AUTH_TOKEN_KEY
import gq.kirmanak.mealient.datasource.TokenChangeListener
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN
import gq.kirmanak.mealient.test.HiltRobolectricTest
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -28,11 +30,14 @@ class AuthStorageImplTest : HiltRobolectricTest() {
lateinit var sharedPreferences: SharedPreferences
@MockK
lateinit var tokenChangeListener: TokenChangeListener
@Before
fun setUp() {
MockKAnnotations.init(this)
sharedPreferences = context.getSharedPreferences("test", Context.MODE_PRIVATE)
subject = AuthStorageImpl(sharedPreferences, logger)
subject = AuthStorageImpl(sharedPreferences, tokenChangeListener, logger)
}
@Test

View File

@@ -1,6 +0,0 @@
package gq.kirmanak.mealient.datasource
interface SignOutHandler {
fun signOut()
}

View File

@@ -0,0 +1,6 @@
package gq.kirmanak.mealient.datasource
fun interface TokenChangeListener {
fun onTokenChange()
}

View File

@@ -6,7 +6,7 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.IntoSet
import gq.kirmanak.mealient.datasource.SignOutHandler
import gq.kirmanak.mealient.datasource.TokenChangeListener
import io.ktor.client.HttpClient
import javax.inject.Singleton
@@ -37,5 +37,5 @@ internal interface KtorModule {
fun bindKtorClientBuilder(impl: KtorClientBuilderImpl) : KtorClientBuilder
@Binds
fun bindSignOutHandler(impl: SignOutHandlerKtor) : SignOutHandler
fun bindSignOutHandler(impl: TokenChangeListenerKtor): TokenChangeListener
}

View File

@@ -1,20 +0,0 @@
package gq.kirmanak.mealient.datasource.ktor
import gq.kirmanak.mealient.datasource.SignOutHandler
import io.ktor.client.HttpClient
import io.ktor.client.plugins.auth.Auth
import io.ktor.client.plugins.auth.providers.BearerAuthProvider
import io.ktor.client.plugins.plugin
import javax.inject.Inject
internal class SignOutHandlerKtor @Inject constructor(
private val httpClient: HttpClient,
) : SignOutHandler {
override fun signOut() {
httpClient.plugin(Auth)
.providers
.filterIsInstance<BearerAuthProvider>()
.forEach { it.clearToken() }
}
}

View File

@@ -0,0 +1,26 @@
package gq.kirmanak.mealient.datasource.ktor
import gq.kirmanak.mealient.datasource.TokenChangeListener
import gq.kirmanak.mealient.logging.Logger
import io.ktor.client.HttpClient
import io.ktor.client.plugins.auth.Auth
import io.ktor.client.plugins.auth.providers.BearerAuthProvider
import io.ktor.client.plugins.plugin
import javax.inject.Inject
internal class TokenChangeListenerKtor @Inject constructor(
private val httpClient: HttpClient,
private val logger: Logger,
) : TokenChangeListener {
override fun onTokenChange() {
logger.v { "onTokenChange() called" }
httpClient.plugin(Auth)
.providers
.filterIsInstance<BearerAuthProvider>()
.forEach {
logger.d { "onTokenChange(): removing the token" }
it.clearToken()
}
}
}

View File

@@ -0,0 +1,2 @@
Fix authentication issues with some Mealie instances.
Allow sending logs to the developer.