fix: Ensure logging out accounts completes (#515)

The account logout process could fail due to API exceptions; network
errors for example, or if the user had already revoked the app's token
for that account. This would prevent the rest of the logout process
(cleaning database, etc) from completing.

Fix this by ignoring network errors during the logout process, and
always cleaning up account content in the database.

Fix a related issue where a deleted account might be recreated in a
partial state if the account's visible position was saved after it was
deleted. The recreated account couldn't do anything as it had no tokens,
but is very confusing.
This commit is contained in:
Nik Clayton 2024-03-10 12:25:12 +01:00 committed by GitHub
parent 0105a8179c
commit 0445e187df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 28 additions and 8 deletions

View File

@ -14,6 +14,7 @@ import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.util.removeShortcut import app.pachli.util.removeShortcut
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject import javax.inject.Inject
import timber.log.Timber
class LogoutUsecase @Inject constructor( class LogoutUsecase @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
@ -37,11 +38,15 @@ class LogoutUsecase @Inject constructor(
val clientId = activeAccount.clientId val clientId = activeAccount.clientId
val clientSecret = activeAccount.clientSecret val clientSecret = activeAccount.clientSecret
if (clientId != null && clientSecret != null) { if (clientId != null && clientSecret != null) {
try {
api.revokeOAuthToken( api.revokeOAuthToken(
clientId = clientId, clientId = clientId,
clientSecret = clientSecret, clientSecret = clientSecret,
token = activeAccount.accessToken, token = activeAccount.accessToken,
) )
} catch (e: Exception) {
Timber.e(e, "Could not revoke OAuth token, continuing")
}
} }
// disable push notifications // disable push notifications

View File

@ -130,11 +130,26 @@ class AccountManager @Inject constructor(
* @param account the account to save * @param account the account to save
*/ */
fun saveAccount(account: AccountEntity) { fun saveAccount(account: AccountEntity) {
if (account.id != 0L) { if (account.id == 0L) {
Timber.e("Trying to save account with ID = 0, ignoring")
return
}
// Work around saveAccount() being called after account deletion
// For example:
// - Have two accounts, A and B, signed in with A, looking at home timeline for A
// - Log out of A. This triggers deletion of account A from the database
// - Shortly afterwards the timeline activity/fragment ends, and it tries to save
// the visible ID back to the database, which creates the AccountEntity record
// that was just deleted, but in a partial state.
if (accounts.find { it.id == account.id } == null) {
Timber.e("Trying to save account with ID = %d which does not exist, ignoring", account.id)
return
}
Timber.d("saveAccount: saving account with id %d", account.id) Timber.d("saveAccount: saving account with id %d", account.id)
accountDao.insertOrReplace(account) accountDao.insertOrReplace(account)
} }
}
/** /**
* Logs the current account out by deleting all data of the account. * Logs the current account out by deleting all data of the account.