Add forced logout flow when the proxy is no longer available (#3458)
* Add `MatrixClient.isSlidingSyncProxySupported` function * Update localazy strings * Modify `ErrorDialog` to have an `onSubmit` call, which will be used for the submit action. Also make the title text optional and dismissing the dialog by tapping outside/going back configurable. * Check if a forced migration to SSS is needed because the proxy is no longer available. In that case, display the non-dismissable dialog and force the user to log out after enabling SSS. * Enable native/simplified sliding sync by default. * Refactor the login to make sure we: 1. Always try native/simplified sliding sync login first, if available. 2. Then, if it wasn't available or failed with an sliding sync not supported error, try with the proxy instead (either discovered proxy or forced custom one). * Move logic to `LoggedInPresenter` and the UI to `LoggedInView` * Update screenshots --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
da3f5e00dc
commit
663362ac7f
79 changed files with 315 additions and 231 deletions
|
|
@ -534,6 +534,10 @@ class RustMatrixClient(
|
|||
return client.availableSlidingSyncVersions().contains(SlidingSyncVersion.Native)
|
||||
}
|
||||
|
||||
override suspend fun isSlidingSyncProxySupported(): Boolean {
|
||||
return client.availableSlidingSyncVersions().any { it is SlidingSyncVersion.Proxy }
|
||||
}
|
||||
|
||||
override fun isUsingNativeSlidingSync(): Boolean {
|
||||
return client.session().slidingSyncVersion == SlidingSyncVersion.Native
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl
|
||||
|
||||
import io.element.android.appconfig.AuthenticationConfig
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.di.CacheDirectory
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
|
|
@ -19,12 +18,10 @@ import io.element.android.libraries.matrix.impl.paths.getSessionPaths
|
|||
import io.element.android.libraries.matrix.impl.proxy.ProxyProvider
|
||||
import io.element.android.libraries.matrix.impl.util.anonymizedTokens
|
||||
import io.element.android.libraries.network.useragent.UserAgentProvider
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.sessionstorage.api.SessionData
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.ClientBuilder
|
||||
import org.matrix.rustcomponents.sdk.Session
|
||||
|
|
@ -47,7 +44,6 @@ class RustMatrixClientFactory @Inject constructor(
|
|||
private val proxyProvider: ProxyProvider,
|
||||
private val clock: SystemClock,
|
||||
private val utdTracker: UtdTracker,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
) {
|
||||
suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) {
|
||||
|
|
@ -55,7 +51,7 @@ class RustMatrixClientFactory @Inject constructor(
|
|||
val client = getBaseClientBuilder(
|
||||
sessionPaths = sessionData.getSessionPaths(),
|
||||
passphrase = sessionData.passphrase,
|
||||
restore = true,
|
||||
slidingSyncType = ClientBuilderSlidingSync.Restored,
|
||||
)
|
||||
.homeserverUrl(sessionData.homeserverUrl)
|
||||
.username(sessionData.userId)
|
||||
|
|
@ -88,16 +84,8 @@ class RustMatrixClientFactory @Inject constructor(
|
|||
internal suspend fun getBaseClientBuilder(
|
||||
sessionPaths: SessionPaths,
|
||||
passphrase: String?,
|
||||
restore: Boolean,
|
||||
slidingSyncType: ClientBuilderSlidingSync,
|
||||
): ClientBuilder {
|
||||
val slidingSync = when {
|
||||
// Always check restore first, since otherwise other values could accidentally override the already persisted config
|
||||
restore -> ClientBuilderSlidingSync.Restored
|
||||
AuthenticationConfig.SLIDING_SYNC_PROXY_URL != null -> ClientBuilderSlidingSync.CustomProxy(AuthenticationConfig.SLIDING_SYNC_PROXY_URL!!)
|
||||
appPreferencesStore.isSimplifiedSlidingSyncEnabledFlow().first() -> ClientBuilderSlidingSync.Simplified
|
||||
else -> ClientBuilderSlidingSync.Discovered
|
||||
}
|
||||
|
||||
return ClientBuilder()
|
||||
.sessionPaths(
|
||||
dataPath = sessionPaths.fileDirectory.absolutePath,
|
||||
|
|
@ -117,9 +105,9 @@ class RustMatrixClientFactory @Inject constructor(
|
|||
)
|
||||
.run {
|
||||
// Apply sliding sync version settings
|
||||
when (slidingSync) {
|
||||
when (slidingSyncType) {
|
||||
ClientBuilderSlidingSync.Restored -> this
|
||||
is ClientBuilderSlidingSync.CustomProxy -> slidingSyncVersionBuilder(SlidingSyncVersionBuilder.Proxy(slidingSync.url))
|
||||
is ClientBuilderSlidingSync.CustomProxy -> slidingSyncVersionBuilder(SlidingSyncVersionBuilder.Proxy(slidingSyncType.url))
|
||||
ClientBuilderSlidingSync.Discovered -> slidingSyncVersionBuilder(SlidingSyncVersionBuilder.DiscoverProxy)
|
||||
ClientBuilderSlidingSync.Simplified -> slidingSyncVersionBuilder(SlidingSyncVersionBuilder.DiscoverNative)
|
||||
ClientBuilderSlidingSync.ForcedSimplified -> slidingSyncVersionBuilder(SlidingSyncVersionBuilder.Native)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package io.element.android.libraries.matrix.impl.auth
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.appconfig.AuthenticationConfig
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.extensions.mapFailure
|
||||
import io.element.android.libraries.di.AppScope
|
||||
|
|
@ -19,6 +20,7 @@ import io.element.android.libraries.matrix.api.auth.OidcDetails
|
|||
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
|
||||
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.impl.ClientBuilderSlidingSync
|
||||
import io.element.android.libraries.matrix.impl.RustMatrixClientFactory
|
||||
import io.element.android.libraries.matrix.impl.auth.qrlogin.QrErrorMapper
|
||||
import io.element.android.libraries.matrix.impl.auth.qrlogin.SdkQrCodeLoginData
|
||||
|
|
@ -28,6 +30,7 @@ import io.element.android.libraries.matrix.impl.keys.PassphraseGenerator
|
|||
import io.element.android.libraries.matrix.impl.mapper.toSessionData
|
||||
import io.element.android.libraries.matrix.impl.paths.SessionPaths
|
||||
import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.sessionstorage.api.LoggedInState
|
||||
import io.element.android.libraries.sessionstorage.api.LoginType
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
|
|
@ -35,9 +38,13 @@ import kotlinx.coroutines.CancellationException
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.Client
|
||||
import org.matrix.rustcomponents.sdk.ClientBuilder
|
||||
import org.matrix.rustcomponents.sdk.HumanQrLoginException
|
||||
import org.matrix.rustcomponents.sdk.OidcConfiguration
|
||||
import org.matrix.rustcomponents.sdk.QrCodeData
|
||||
import org.matrix.rustcomponents.sdk.QrCodeDecodeException
|
||||
import org.matrix.rustcomponents.sdk.QrLoginProgress
|
||||
import org.matrix.rustcomponents.sdk.QrLoginProgressListener
|
||||
|
|
@ -55,6 +62,7 @@ class RustMatrixAuthenticationService @Inject constructor(
|
|||
private val rustMatrixClientFactory: RustMatrixClientFactory,
|
||||
private val passphraseGenerator: PassphraseGenerator,
|
||||
private val oidcConfigurationProvider: OidcConfigurationProvider,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : MatrixAuthenticationService {
|
||||
// Passphrase which will be used for new sessions. Existing sessions will use the passphrase
|
||||
// stored in the SessionData.
|
||||
|
|
@ -117,9 +125,10 @@ class RustMatrixAuthenticationService @Inject constructor(
|
|||
withContext(coroutineDispatchers.io) {
|
||||
val emptySessionPath = rotateSessionPath()
|
||||
runCatching {
|
||||
val client = getBaseClientBuilder(emptySessionPath)
|
||||
.serverNameOrHomeserverUrl(homeserver)
|
||||
.build()
|
||||
val client = makeClient(sessionPaths = emptySessionPath) {
|
||||
serverNameOrHomeserverUrl(homeserver)
|
||||
}
|
||||
|
||||
currentClient = client
|
||||
val homeServerDetails = client.homeserverLoginDetails().map()
|
||||
currentHomeserver.value = homeServerDetails.copy(url = homeserver)
|
||||
|
|
@ -207,23 +216,24 @@ class RustMatrixAuthenticationService @Inject constructor(
|
|||
|
||||
override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) =
|
||||
withContext(coroutineDispatchers.io) {
|
||||
val sdkQrCodeLoginData = (qrCodeData as SdkQrCodeLoginData).rustQrCodeData
|
||||
val emptySessionPaths = rotateSessionPath()
|
||||
val oidcConfiguration = oidcConfigurationProvider.get()
|
||||
val progressListener = object : QrLoginProgressListener {
|
||||
override fun onUpdate(state: QrLoginProgress) {
|
||||
Timber.d("QR Code login progress: $state")
|
||||
progress(state.toStep())
|
||||
}
|
||||
}
|
||||
runCatching {
|
||||
val client = rustMatrixClientFactory.getBaseClientBuilder(
|
||||
val client = makeQrCodeLoginClient(
|
||||
sessionPaths = emptySessionPaths,
|
||||
passphrase = pendingPassphrase,
|
||||
restore = false,
|
||||
qrCodeData = sdkQrCodeLoginData,
|
||||
oidcConfiguration = oidcConfiguration,
|
||||
progressListener = progressListener,
|
||||
)
|
||||
.buildWithQrCode(
|
||||
qrCodeData = (qrCodeData as SdkQrCodeLoginData).rustQrCodeData,
|
||||
oidcConfiguration = oidcConfigurationProvider.get(),
|
||||
progressListener = object : QrLoginProgressListener {
|
||||
override fun onUpdate(state: QrLoginProgress) {
|
||||
Timber.d("QR Code login progress: $state")
|
||||
progress(state.toStep())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
client.use { rustClient ->
|
||||
val sessionData = rustClient.session()
|
||||
.toSessionData(
|
||||
|
|
@ -249,14 +259,80 @@ class RustMatrixAuthenticationService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun getBaseClientBuilder(
|
||||
private suspend fun makeClient(
|
||||
sessionPaths: SessionPaths,
|
||||
) = rustMatrixClientFactory
|
||||
.getBaseClientBuilder(
|
||||
sessionPaths = sessionPaths,
|
||||
passphrase = pendingPassphrase,
|
||||
restore = false,
|
||||
)
|
||||
config: suspend ClientBuilder.() -> ClientBuilder,
|
||||
): Client {
|
||||
val slidingSyncType = getSlidingSyncType()
|
||||
if (slidingSyncType is ClientBuilderSlidingSync.Simplified) {
|
||||
Timber.d("Creating client with simplified sliding sync")
|
||||
try {
|
||||
return rustMatrixClientFactory
|
||||
.getBaseClientBuilder(
|
||||
sessionPaths = sessionPaths,
|
||||
passphrase = pendingPassphrase,
|
||||
slidingSyncType = slidingSyncType,
|
||||
)
|
||||
.run { config() }
|
||||
.build()
|
||||
} catch (e: HumanQrLoginException.SlidingSyncNotAvailable) {
|
||||
Timber.e(e, "Failed to create client with simplified sliding sync, trying with Proxy now")
|
||||
}
|
||||
}
|
||||
Timber.d("Creating client with Proxy sliding sync")
|
||||
return rustMatrixClientFactory
|
||||
.getBaseClientBuilder(
|
||||
sessionPaths = sessionPaths,
|
||||
passphrase = pendingPassphrase,
|
||||
slidingSyncType = getSlidingSyncProxy(),
|
||||
)
|
||||
.run { config() }
|
||||
.build()
|
||||
}
|
||||
|
||||
private suspend fun makeQrCodeLoginClient(
|
||||
sessionPaths: SessionPaths,
|
||||
passphrase: String?,
|
||||
qrCodeData: QrCodeData,
|
||||
oidcConfiguration: OidcConfiguration,
|
||||
progressListener: QrLoginProgressListener,
|
||||
): Client {
|
||||
val slidingSyncType = getSlidingSyncType()
|
||||
if (slidingSyncType is ClientBuilderSlidingSync.Simplified) {
|
||||
Timber.d("Creating client for QR Code login with simplified sliding sync")
|
||||
try {
|
||||
return rustMatrixClientFactory
|
||||
.getBaseClientBuilder(
|
||||
sessionPaths = sessionPaths,
|
||||
passphrase = pendingPassphrase,
|
||||
slidingSyncType = slidingSyncType,
|
||||
)
|
||||
.passphrase(passphrase)
|
||||
.buildWithQrCode(qrCodeData, oidcConfiguration, progressListener)
|
||||
} catch (e: HumanQrLoginException.SlidingSyncNotAvailable) {
|
||||
Timber.e(e, "Failed to create client with simplified sliding sync, trying with Proxy now")
|
||||
}
|
||||
}
|
||||
Timber.d("Creating client for QR Code login with Proxy sliding sync")
|
||||
return rustMatrixClientFactory
|
||||
.getBaseClientBuilder(
|
||||
sessionPaths = sessionPaths,
|
||||
passphrase = pendingPassphrase,
|
||||
slidingSyncType = getSlidingSyncProxy(),
|
||||
)
|
||||
.passphrase(passphrase)
|
||||
.buildWithQrCode(qrCodeData, oidcConfiguration, progressListener)
|
||||
}
|
||||
|
||||
private suspend fun getSlidingSyncType(nativeSlidingSyncFailed: Boolean = false) = when {
|
||||
appPreferencesStore.isSimplifiedSlidingSyncEnabledFlow().first() && !nativeSlidingSyncFailed -> ClientBuilderSlidingSync.Simplified
|
||||
else -> getSlidingSyncProxy()
|
||||
}
|
||||
|
||||
private fun getSlidingSyncProxy() = when {
|
||||
AuthenticationConfig.SLIDING_SYNC_PROXY_URL != null -> ClientBuilderSlidingSync.CustomProxy(AuthenticationConfig.SLIDING_SYNC_PROXY_URL!!)
|
||||
else -> ClientBuilderSlidingSync.Discovered
|
||||
}
|
||||
|
||||
private fun clear() {
|
||||
currentClient?.close()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue