diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/LogInFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/LogInFragment.kt index 659d26610cc3eaadfff26f2bef8c0b65ceb091ef..a6f945892fa598ad8c03e06774421ef86e344e52 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/LogInFragment.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/log_in/LogInFragment.kt @@ -98,7 +98,7 @@ class LogInFragment : Fragment(R.layout.fragment_log_in), HasLoadingState { NetworkObserver.observe(this) { setEnabledViews(it) } viewModel.loginResultLiveData.observeResponse(this, success = { - findNavController().navigateSafe(LogInFragmentDirections.toLoginStagesFragment()) + findNavController().navigateSafe(LogInFragmentDirections.toUiaFragment()) }, error = { showError(getString(R.string.username_not_found)) diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/LoginDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/LoginDataSource.kt index e404b11f9ecb02467923096b01e59f1bde3172e8..88d4aff1fbd4be7e2bef2d5e78a287d291f85dde 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/LoginDataSource.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/log_in/LoginDataSource.kt @@ -24,7 +24,7 @@ class LoginDataSource @Inject constructor( private val uiaFactory: UIADataSource.Factory ) { - private val loginStagesDataSource by lazy { uiaFactory.create(UIAFlowType.Login) } + private val uiaDataSource by lazy { uiaFactory.create(UIAFlowType.Login) } private val authService by lazy { MatrixInstanceProvider.matrix.authenticationService() } suspend fun startLogin( @@ -34,7 +34,7 @@ class LoginDataSource @Inject constructor( ) = createResult { authService.cancelPendingLoginOrRegistration() val stages = prepareLoginStages(userName, domain, isForgotPassword) - loginStagesDataSource.startUIAStages(stages, domain, userName) + uiaDataSource.startUIAStages(stages, domain, userName) } private suspend fun prepareLoginStages( diff --git a/auth/src/main/java/org/futo/circles/auth/feature/reauth/AuthConfirmationProvider.kt b/auth/src/main/java/org/futo/circles/auth/feature/reauth/AuthConfirmationProvider.kt index 4e83420c6c358204739b5a791f22e6d2b6d87817..61c77565df6c41376950e1bc208846cca7047bd5 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/reauth/AuthConfirmationProvider.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/reauth/AuthConfirmationProvider.kt @@ -1,5 +1,10 @@ package org.futo.circles.auth.feature.reauth +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import org.futo.circles.auth.feature.uia.UIADataSource +import org.futo.circles.auth.feature.uia.UIAFlowType import org.futo.circles.core.base.SingleEventLiveData import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor @@ -10,10 +15,11 @@ import kotlin.coroutines.Continuation import kotlin.coroutines.resumeWithException class AuthConfirmationProvider @Inject constructor( - private val reAuthStagesDataSource: ReAuthStagesDataSource + private val uiaFactory: UIADataSource.Factory ) : UserInteractiveAuthInterceptor { val startReAuthEventLiveData = SingleEventLiveData<Unit>() + private val uiaDataSource by lazy { uiaFactory.create(UIAFlowType.ReAuth) } override fun performStage( flowResponse: RegistrationFlowResponse, @@ -24,9 +30,11 @@ class AuthConfirmationProvider @Inject constructor( if (flowResponse.completedStages.isNullOrEmpty()) { val stages = flowResponse.toFlowsWithStages().firstOrNull() ?: emptyList() startReAuthEventLiveData.postValue(Unit) - reAuthStagesDataSource.startReAuthStages(flowResponse.session ?: "", stages, promise) + MainScope().launch(Dispatchers.IO) { + uiaDataSource.startUIAStages(stages, flowResponse.session ?: "", promise) + } } else { - reAuthStagesDataSource.onStageResult(promise, flowResponse, errCode) + uiaDataSource.onStageResult(promise, flowResponse, errCode) } } } \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/sign_up_type/SelectSignUpTypeDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/sign_up_type/SelectSignUpTypeDataSource.kt index 6a2ebdc000c35aabb12f3f3b4ebbe49673e79645..9d6a1596e66668a2acbce69bbe192d1afccc8538 100644 --- a/auth/src/main/java/org/futo/circles/auth/feature/sign_up/sign_up_type/SelectSignUpTypeDataSource.kt +++ b/auth/src/main/java/org/futo/circles/auth/feature/sign_up/sign_up_type/SelectSignUpTypeDataSource.kt @@ -3,9 +3,11 @@ package org.futo.circles.auth.feature.sign_up.sign_up_type import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import org.futo.circles.auth.R +import org.futo.circles.auth.feature.uia.UIADataSource import org.futo.circles.auth.feature.uia.UIADataSource.Companion.REGISTRATION_FREE_TYPE import org.futo.circles.auth.feature.uia.UIADataSource.Companion.REGISTRATION_SUBSCRIPTION_TYPE import org.futo.circles.auth.feature.uia.UIADataSourceProvider +import org.futo.circles.auth.feature.uia.UIAFlowType import org.futo.circles.core.base.CirclesAppConfig import org.futo.circles.core.extensions.createResult import org.futo.circles.core.provider.MatrixInstanceProvider @@ -14,12 +16,13 @@ import org.matrix.android.sdk.api.auth.registration.Stage import javax.inject.Inject class SelectSignUpTypeDataSource @Inject constructor( - @ApplicationContext private val context: Context + @ApplicationContext private val context: Context, + private val uiaFactory: UIADataSource.Factory ) { private var registrationFlowsForDomain: Pair<String, List<List<Stage>>>? = null - private val uiaDataSource = UIADataSourceProvider.getDataSourceOrThrow() + private val uiaDataSource by lazy { uiaFactory.create(UIAFlowType.Signup) } suspend fun getAuthFlowsFor(domain: String) = createResult { registrationFlowsForDomain = null diff --git a/auth/src/main/java/org/futo/circles/auth/feature/uia/flow/LoginStagesDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/uia/flow/LoginStagesDataSource.kt new file mode 100644 index 0000000000000000000000000000000000000000..ba31c984328b5b43ef24e91d27214a0f27b09d8c --- /dev/null +++ b/auth/src/main/java/org/futo/circles/auth/feature/uia/flow/LoginStagesDataSource.kt @@ -0,0 +1,144 @@ +package org.futo.circles.auth.feature.uia.flow + +import android.content.Context +import android.net.Uri +import dagger.hilt.android.qualifiers.ApplicationContext +import org.futo.circles.auth.R +import org.futo.circles.auth.bsspeke.BSSpekeClientProvider +import org.futo.circles.auth.feature.pass_phrase.EncryptionAlgorithmHelper +import org.futo.circles.auth.feature.pass_phrase.create.CreatePassPhraseDataSource +import org.futo.circles.auth.feature.pass_phrase.restore.RestoreBackupDataSource +import org.futo.circles.auth.feature.token.RefreshTokenManager +import org.futo.circles.auth.feature.uia.UIADataSource +import org.futo.circles.core.base.SingleEventLiveData +import org.futo.circles.core.extensions.Response +import org.futo.circles.core.extensions.createResult +import org.futo.circles.core.model.LoadingData +import org.futo.circles.core.provider.MatrixInstanceProvider +import org.futo.circles.core.provider.MatrixSessionProvider +import org.matrix.android.sdk.api.auth.registration.RegistrationResult +import org.matrix.android.sdk.api.auth.registration.Stage +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.util.JsonDict +import javax.inject.Inject +import javax.inject.Singleton + +enum class LoginNavigationEvent { Main, PassPhrase } + +@Singleton +class LoginStagesDataSource @Inject constructor( + @ApplicationContext private val context: Context, + private val restoreBackupDataSource: RestoreBackupDataSource, + private val encryptionAlgorithmHelper: EncryptionAlgorithmHelper, + private val createPassPhraseDataSource: CreatePassPhraseDataSource, + private val refreshTokenManager: RefreshTokenManager +) : UIADataSource(context) { + + val loginNavigationLiveData = SingleEventLiveData<LoginNavigationEvent>() + val passPhraseLoadingLiveData = restoreBackupDataSource.loadingLiveData + + private var userPassword: String = "" + + override suspend fun startUIAStages( + stages: List<Stage>, + serverDomain: String, + name: String? + ) { + userPassword = "" + name ?: throw IllegalArgumentException("Username is required for login") + super.startUIAStages(stages, serverDomain, name) + } + + override suspend fun performUIAStage( + authParams: JsonDict, + name: String?, + password: String? + ): Response<RegistrationResult> { + val wizard = MatrixInstanceProvider.matrix.authenticationService().getLoginWizard() + val result = createResult { + wizard.loginStageCustom( + authParams, + getIdentifier(), + context.getString(R.string.initial_device_name), + true + ) + } + (result as? Response.Success)?.let { + password?.let { userPassword = it } + stageCompleted(result.data) + } + return result + } + + + override suspend fun finishStages(session: Session) { + passPhraseLoadingLiveData.postValue( + LoadingData(messageId = R.string.initial_sync, isLoading = true) + ) + MatrixSessionProvider.awaitForSessionSync(session) + passPhraseLoadingLiveData.postValue(LoadingData(isLoading = false)) + refreshTokenManager.scheduleTokenRefreshIfNeeded(session) + handleKeysBackup() + BSSpekeClientProvider.clear() + } + + private fun getIdentifier() = mapOf( + USER_PARAM_KEY to "@$userName:$domain", + TYPE_PARAM_KEY to LOGIN_PASSWORD_USER_ID_TYPE + ) + + private suspend fun handleKeysBackup() { + if (encryptionAlgorithmHelper.isBcryptAlgorithm()) restoreAndMigrateBCrypt(userPassword) + else { + if (encryptionAlgorithmHelper.isBsSpekePassPhrase()) restoreBsSpekeBackup() + else loginNavigationLiveData.postValue(LoginNavigationEvent.PassPhrase) + } + } + + private suspend fun restoreBsSpekeBackup(): Response<Unit> { + val restoreResult = createResult { restoreBackupDataSource.restoreWithBsSpekeKey() } + return handleRestoreResult(restoreResult) + } + + private suspend fun restoreAndMigrateBCrypt(passphrase: String): Response<Unit> { + val restoreResult = createResult { + restoreBackupDataSource.restoreBcryptWithPassPhase(passphrase) + createPassPhraseDataSource.replaceToNewKeyBackup() + } + return handleRestoreResult(restoreResult) + } + + + suspend fun restoreBackupWithPassphrase(password: String): Response<Unit> { + val restoreResult = createResult { + restoreBackupDataSource.restoreKeysWithPassPhase(password) + } + return handleRestoreResult(restoreResult) + } + + suspend fun restoreBackupWithRawKey(rawKey: String): Response<Unit> { + val restoreResult = createResult { + restoreBackupDataSource.restoreKeysWithRawKey(rawKey) + } + return handleRestoreResult(restoreResult) + } + + suspend fun restoreBackup(uri: Uri): Response<Unit> { + val restoreResult = createResult { + restoreBackupDataSource.restoreKeysWithRecoveryKey(uri) + } + return handleRestoreResult(restoreResult) + } + + private fun handleRestoreResult(restoreResult: Response<Unit>): Response<Unit> { + when (restoreResult) { + is Response.Error -> loginNavigationLiveData.postValue(LoginNavigationEvent.PassPhrase) + is Response.Success -> navigateToMain() + } + return restoreResult + } + + fun navigateToMain() { + loginNavigationLiveData.postValue(LoginNavigationEvent.Main) + } +} \ No newline at end of file diff --git a/auth/src/main/java/org/futo/circles/auth/model/CustomUIAuth.kt b/auth/src/main/java/org/futo/circles/auth/model/CustomUIAuth.kt index fd30b78f677f962f6c7127ff60a7eeb70b5d374a..098c3cafac5bed5f82b24707d344687f73417ffb 100644 --- a/auth/src/main/java/org/futo/circles/auth/model/CustomUIAuth.kt +++ b/auth/src/main/java/org/futo/circles/auth/model/CustomUIAuth.kt @@ -1,5 +1,6 @@ package org.futo.circles.auth.model +import org.futo.circles.auth.feature.uia.UIADataSource.Companion.USER_PARAM_KEY import org.futo.circles.core.provider.MatrixSessionProvider import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.util.JsonDict @@ -14,8 +15,7 @@ data class CustomUIAuth( override fun asMap(): Map<String, *> = auth.toMutableMap().apply { this["session"] = session - this[BaseLoginStagesDataSource.USER_PARAM_KEY] = - MatrixSessionProvider.currentSession?.myUserId ?: "" + this[USER_PARAM_KEY] = MatrixSessionProvider.currentSession?.myUserId ?: "" } }