diff --git a/changelog.d/988.feature b/changelog.d/988.feature new file mode 100644 index 0000000000..438387c2b3 --- /dev/null +++ b/changelog.d/988.feature @@ -0,0 +1 @@ +Forcer la génération du code de récupération et la sauvegarde automatique. \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-fr/strings.xml b/library/ui-strings/src/main/res/values-fr/strings.xml index 340611eda6..7c88b18119 100644 --- a/library/ui-strings/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -1292,7 +1292,7 @@ Nom d’utilisateur Outils de développement Données du compte - Utiliser le Code de Récupération + Vérifier avec le Code de Récupération Si vous n’avez pas accès à un appareil existant Impossible de trouver les secrets dans le stockage Supprimer… @@ -1521,7 +1521,7 @@ EN SAVOIR PLUS COMPRIS L’apparence et l’organisation de votre application évoluent.\nDe nouveaux changements seront introduits progressivement pour faciliter et enrichir votre expérience. - Bienvenue dans la nouvelle version de Tchap ! + Bienvenue dans la nouvelle version de ${app_name} ! Attente de l’historique du chiffrement Impossible d’accéder à ce message car l’envoyeur n’a intentionnellement pas envoyé les clés Vous ne pouvez pas accéder à ce message car l’envoyeur n’a pas confiance en votre appareil @@ -1542,9 +1542,9 @@ Définir le rôle Vous redémarrerez sans aucun historique, message, appareil ou utilisateurs connus Si vous réinitialisez tout - Faites uniquement ceci si vous n\'avez aucun autre appareil pouvant vérifier celui-ci. + Uniquement si vous avez perdu votre Code et n\'avez aucun autre appareil connecté à ${app_name}. Réinitialiser tout - Vous avez perdu votre Code de Récupération ? Générez-en un nouveau. + Générer un nouveau Code de Récupération Impossible d’enregistrer le fichier multimédia Ce compte a été désactivé. Si vous annulez maintenant, vous pourrez perdre les messages et données chiffrés si vous perdez accès à vos identifiants. @@ -2940,12 +2940,12 @@ Message Message de %s Chiffré par un appareil supprimé - Veuillez ne continuer que si vous êtes certain d’avoir perdu tous vos autres appareils et votre clé de sécurité. - La réinitialisation de vos clés de vérification ne peut pas être annulé. Après la réinitialisation, vous n’aurez plus accès à vos anciens messages chiffrés, et tous les amis que vous aviez précédemment vérifiés verront des avertissement de sécurité jusqu\'à ce vous les vérifiiez à nouveau. + Veuillez ne continuer que si vous êtes certain d’avoir perdu tous vos autres appareils et votre Code de Récupération. + La réinitialisation ne peut pas être annulée. Vous n’aurez plus accès à vos anciens messages chiffrés. La demande de vérification n’a pas été trouvée. Elle a peut-être été annulée, ou prise en charge dans une autre session. - Une demande de vérification a été envoyée. Ouvrez l’une de vos autres sessions pour accepter et commencer la vérification. + Une demande de vérification a été envoyée. Ouvrez ${app_name} sur l’un de vos autres appareils pour accepter et commencer la vérification. Reprendre - Vérifiez votre identité pour accéder aux messages chiffrés et prouver votre identité aux autres. + Vérifiez cet appareil pour accéder à vos messages. Vérifier avec un autre appareil Vérification depuis la clé ou phrase de sécurité… Politique d’utilisation acceptable diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index 54c7de9b7a..f982223665 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -50,6 +50,8 @@ class DebugVectorFeatures( override fun tchapIsLabsVisible(domain: String) = vectorFeatures.tchapIsLabsVisible(domain) + override fun tchapIsSecureBackupRequired() = vectorFeatures.tchapIsSecureBackupRequired() + override fun onboardingVariant(): OnboardingVariant { return readPreferences().getEnum() ?: vectorFeatures.onboardingVariant() } diff --git a/vector-config/src/btchap/res/values/config-features.xml b/vector-config/src/btchap/res/values/config-features.xml index e30415e59d..683c904075 100755 --- a/vector-config/src/btchap/res/values/config-features.xml +++ b/vector-config/src/btchap/res/values/config-features.xml @@ -3,6 +3,7 @@ true true false + true diff --git a/vector-config/src/devTchap/res/values/config-features.xml b/vector-config/src/devTchap/res/values/config-features.xml index e30415e59d..683c904075 100755 --- a/vector-config/src/devTchap/res/values/config-features.xml +++ b/vector-config/src/devTchap/res/values/config-features.xml @@ -3,6 +3,7 @@ true true false + true diff --git a/vector-config/src/tchap/res/values/config-features.xml b/vector-config/src/tchap/res/values/config-features.xml index e30415e59d..683c904075 100755 --- a/vector-config/src/tchap/res/values/config-features.xml +++ b/vector-config/src/tchap/res/values/config-features.xml @@ -3,6 +3,7 @@ true true false + true diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt index 70e9593172..2315005aec 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -32,6 +32,7 @@ interface VectorFeatures { fun tchapIsKeyBackupEnabled(): Boolean fun tchapIsThreadEnabled(): Boolean fun tchapIsLabsVisible(domain: String): Boolean + fun tchapIsSecureBackupRequired(): Boolean fun onboardingVariant(): OnboardingVariant fun isOnboardingAlreadyHaveAccountSplashEnabled(): Boolean fun isOnboardingSplashCarouselEnabled(): Boolean @@ -71,6 +72,7 @@ class DefaultVectorFeatures @Inject constructor( override fun tchapIsThreadEnabled() = booleanProvider.getBoolean(R.bool.tchap_is_thread_enabled) override fun tchapIsLabsVisible(domain: String) = booleanProvider.getBoolean(R.bool.settings_root_labs_visible) || domain == appNameProvider.getAppName() + override fun tchapIsSecureBackupRequired() = booleanProvider.getBoolean(R.bool.tchap_is_secure_backup_required) override fun onboardingVariant() = Config.ONBOARDING_VARIANT override fun isOnboardingAlreadyHaveAccountSplashEnabled() = true override fun isOnboardingSplashCarouselEnabled() = false // TCHAP no carousel diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt index 4ab11a218c..206a93aa67 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -32,12 +32,16 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.resources.StringProvider +import im.vector.app.features.VectorFeatures +import im.vector.app.features.raw.wellknown.getElementWellknown +import im.vector.app.features.raw.wellknown.isSecureBackupRequired import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.listeners.ProgressListener +import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.securestorage.IntegrityResult import org.matrix.android.sdk.api.session.securestorage.KeyInfo @@ -88,7 +92,9 @@ data class SharedSecureStorageViewState( class SharedSecureStorageViewModel @AssistedInject constructor( @Assisted private val initialState: SharedSecureStorageViewState, private val stringProvider: StringProvider, + private val vectorFeatures: VectorFeatures, private val session: Session, + private val rawService: RawService, private val matrix: Matrix, ) : VectorViewModel(initialState) { @@ -102,16 +108,25 @@ class SharedSecureStorageViewModel @AssistedInject constructor( setState { copy(userId = session.myUserId) } - if (initialState.requestType is RequestType.ReadSecrets) { - val integrityResult = - session.sharedSecretStorageService().checkShouldBeAbleToAccessSecrets(initialState.requestType.secretsName, initialState.keyId) - if (integrityResult !is IntegrityResult.Success) { - _viewEvents.post( - SharedSecureStorageViewEvent.Error( - stringProvider.getString(R.string.enter_secret_storage_invalid), - true - ) - ) + + // TCHAP force to configure secure backup even if well-known is null + // Do not check integrity if the session is not verified + viewModelScope.launch(Dispatchers.IO) { + val elementWellKnown = rawService.getElementWellknown(session.sessionParams) + val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() ?: vectorFeatures.tchapIsSecureBackupRequired() + val isThisSessionVerified = session.cryptoService().crossSigningService().isCrossSigningVerified() + + if ((isThisSessionVerified || !isSecureBackupRequired) && initialState.requestType is RequestType.ReadSecrets) { + val integrityResult = + session.sharedSecretStorageService().checkShouldBeAbleToAccessSecrets(initialState.requestType.secretsName, initialState.keyId) + if (integrityResult !is IntegrityResult.Success) { + _viewEvents.post( + SharedSecureStorageViewEvent.Error( + stringProvider.getString(R.string.enter_secret_storage_invalid), + true + ) + ) + } } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt index 5ec1c9858c..08e929b192 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt @@ -44,18 +44,17 @@ class BootstrapSetupRecoveryKeyFragment : // TCHAP we directly send user to Security Key // Actions when a key backup exist - // views.bootstrapSetupSecureSubmit.views.bottomSheetActionClickableZone.debouncedClicks { - // sharedViewModel.handle(BootstrapActions.StartKeyBackupMigration) - // } - - // Actions when there is no key backup - // views.bootstrapSetupSecureUseSecurityKey.views.bottomSheetActionClickableZone.debouncedClicks { - // sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = false)) - // } - // views.bootstrapSetupSecureUseSecurityPassphrase.views.bottomSheetActionClickableZone.debouncedClicks { - // sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = true)) - // } - +// views.bootstrapSetupSecureSubmit.views.bottomSheetActionClickableZone.debouncedClicks { +// sharedViewModel.handle(BootstrapActions.StartKeyBackupMigration) +// } +// +// // Actions when there is no key backup +// views.bootstrapSetupSecureUseSecurityKey.views.bottomSheetActionClickableZone.debouncedClicks { +// sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = false)) +// } +// views.bootstrapSetupSecureUseSecurityPassphrase.views.bottomSheetActionClickableZone.debouncedClicks { +// sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = true)) +// } sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = false)) } @@ -95,7 +94,7 @@ class BootstrapSetupRecoveryKeyFragment : private fun renderBackupMethodActions(method: SecureBackupMethod) = with(views) { bootstrapSetupSecureUseSecurityKey.isVisible = method.isKeyAvailable // TCHAP Hide Security Passphrase -// views.bootstrapSetupSecureUseSecurityPassphrase.isVisible = method.isPassphraseAvailable -// views.bootstrapSetupSecureUseSecurityPassphraseSeparator.isVisible = method.isPassphraseAvailable +// bootstrapSetupSecureUseSecurityPassphrase.isVisible = method.isPassphraseAvailable +// bootstrapSetupSecureUseSecurityPassphraseSeparator.isVisible = method.isPassphraseAvailable } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt index c9c2c5ce9a..d13da10ee7 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt @@ -32,6 +32,7 @@ import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.resources.StringProvider +import im.vector.app.features.VectorFeatures import im.vector.app.features.auth.PendingAuthHandler import im.vector.app.features.raw.wellknown.SecureBackupMethod import im.vector.app.features.raw.wellknown.getElementWellknown @@ -45,7 +46,6 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage -import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.raw.RawService @@ -63,6 +63,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( @Assisted initialState: BootstrapViewState, private val stringProvider: StringProvider, private val errorFormatter: ErrorFormatter, + private val vectorFeatures: VectorFeatures, private val session: Session, private val rawService: RawService, private val bootstrapTask: BootstrapCrossSigningTask, @@ -92,8 +93,9 @@ class BootstrapSharedViewModel @AssistedInject constructor( val wellKnown = rawService.getElementWellknown(session.sessionParams) setState { copy( - isSecureBackupRequired = wellKnown?.isSecureBackupRequired().orFalse(), - secureBackupMethod = wellKnown?.secureBackupMethod() ?: SecureBackupMethod.KEY_OR_PASSPHRASE, + // TCHAP force to configure secure backup key even if well-known is null + isSecureBackupRequired = wellKnown?.isSecureBackupRequired() ?: vectorFeatures.tchapIsSecureBackupRequired(), + secureBackupMethod = wellKnown?.secureBackupMethod() ?: SecureBackupMethod.KEY, ) } } @@ -212,7 +214,10 @@ class BootstrapSharedViewModel @AssistedInject constructor( } is BootstrapActions.DoInitialize -> { if (state.passphrase == state.passphraseRepeat) { - startInitializeFlow(state) + // TCHAP do not ask user password multiple times + if (state.step !is BootstrapStep.AccountReAuth) { + startInitializeFlow(state) + } } else { setState { copy( @@ -222,7 +227,10 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } is BootstrapActions.DoInitializeGeneratedKey -> { - startInitializeFlow(state) + // TCHAP do not ask user password multiple times + if (state.step !is BootstrapStep.AccountReAuth) { + startInitializeFlow(state) + } } BootstrapActions.RecoveryKeySaved -> { _viewEvents.post(BootstrapViewEvents.RecoveryKeySaved) @@ -603,4 +611,4 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } -private val BootstrapViewState.canLeave: Boolean get() = !isSecureBackupRequired || isRecoverySetup +private val BootstrapViewState.canLeave: Boolean get() = !isSecureBackupRequired || isRecoverySetup // TCHAP add a reminder instead ? diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationController.kt index 9871d21601..8548aa3d04 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationController.kt @@ -296,7 +296,8 @@ class SelfVerificationController @Inject constructor( id("passphrase") title(host.stringProvider.getString(R.string.verification_cannot_access_other_session)) titleColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary)) - subTitle(host.stringProvider.getString(R.string.verification_use_passphrase)) + // TCHAP use recovery key with no restriction +// subTitle(host.stringProvider.getString(R.string.verification_use_passphrase)) iconRes(R.drawable.ic_arrow_right) iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary)) listener { host.selfVerificationListener?.onClickRecoverFromPassphrase() } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationViewModel.kt index 029fa2ca15..99d3165fcb 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationViewModel.kt @@ -37,8 +37,6 @@ import im.vector.app.features.crypto.verification.VerificationAction import im.vector.app.features.crypto.verification.VerificationBottomSheetViewEvents import im.vector.app.features.crypto.verification.user.VerificationTransactionData import im.vector.app.features.crypto.verification.user.toDataClass -import im.vector.app.features.raw.wellknown.getElementWellknown -import im.vector.app.features.raw.wellknown.isSecureBackupRequired import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.filter @@ -46,7 +44,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.Matrix -import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session @@ -140,9 +137,9 @@ class SelfVerificationViewModel @AssistedInject constructor( // This is async, but at this point should be in cache // so it's ok to not wait until result viewModelScope.launch(Dispatchers.IO) { - val wellKnown = rawService.getElementWellknown(session.sessionParams) + // TCHAP force verification when recovery is setup setState { - copy(isVerificationRequired = wellKnown?.isSecureBackupRequired().orFalse()) + copy(isVerificationRequired = session.sharedSecretStorageService().isRecoverySetup()) } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index bc2bbad6bc..dc88747c25 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -47,6 +47,7 @@ import im.vector.app.features.raw.wellknown.isSecureBackupRequired import im.vector.app.features.raw.wellknown.withElementWellKnown import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase import im.vector.app.features.voicebroadcast.recording.usecase.StopOngoingVoiceBroadcastUseCase import im.vector.lib.core.utils.compat.getParcelableExtraCompat import kotlinx.coroutines.Dispatchers @@ -97,6 +98,7 @@ class HomeActivityViewModel @AssistedInject constructor( private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase, private val ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase, private val ensureSessionSyncingUseCase: EnsureSessionSyncingUseCase, + private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase, private val coroutineDispatchers: CoroutineDispatchers, ) : VectorViewModel(initialState) { @@ -391,7 +393,8 @@ class HomeActivityViewModel @AssistedInject constructor( private fun sessionHasBeenUnverified(elementWellKnown: ElementWellKnown?) { val session = activeSessionHolder.getSafeActiveSession() ?: return - val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() ?: false + val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() + ?: vectorFeatures.tchapIsSecureBackupRequired() // TCHAP force to configure secure backup even if well-known is null if (isSecureBackupRequired) { // If 4S is forced, force verification // for stability cancel all pending verifications? @@ -425,7 +428,8 @@ class HomeActivityViewModel @AssistedInject constructor( } val elementWellKnown = rawService.getElementWellknown(session.sessionParams) - val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() ?: false + val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() + ?: vectorFeatures.tchapIsSecureBackupRequired() // TCHAP force to configure secure backup even if well-known is null // In case of account creation, it is already done before if (initialState.authenticationDescription is AuthenticationDescription.Register) { @@ -464,15 +468,24 @@ class HomeActivityViewModel @AssistedInject constructor( // Is there already cross signing keys here? val mxCrossSigningInfo = session.cryptoService().crossSigningService().getMyCrossSigningKeys() if (mxCrossSigningInfo != null) { - if (isSecureBackupRequired && !session.sharedSecretStorageService().isRecoverySetup()) { + // TCHAP Setup 4S and cross-signing if needed + if (isSecureBackupRequired && !session.sharedSecretStorageService().isRecoverySetup() && mxCrossSigningInfo.isTrusted()) { // If 4S is forced, start the full interactive setup flow _viewEvents.post(HomeActivityViewEvents.StartRecoverySetupFlow) } else { // Cross-signing is already set up for this user, is it trusted? if (!mxCrossSigningInfo.isTrusted()) { if (isSecureBackupRequired) { - // If 4S is forced, force verification - _viewEvents.post(HomeActivityViewEvents.ForceVerification(true)) + // TCHAP Setup 4S and cross-signing if needed + viewModelScope.launch { + val currentSessionCanBeVerified = checkIfCurrentSessionCanBeVerifiedUseCase.execute() + if (currentSessionCanBeVerified) { + // If 4S is forced, force verification + _viewEvents.post(HomeActivityViewEvents.ForceVerification(true)) + } else { + _viewEvents.post(HomeActivityViewEvents.StartRecoverySetupFlow) + } + } } else { // we wan't to check if there is a way to actually verify this session, // that means that there is another session to verify against, or @@ -592,11 +605,11 @@ class HomeActivityViewModel @AssistedInject constructor( private suspend fun CrossSigningService.awaitCrossSigninInitialization( block: Continuation.(response: RegistrationFlowResponse, errCode: String?) -> Unit ) { - initializeCrossSigning( - object : UserInteractiveAuthInterceptor { - override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { - promise.block(flowResponse, errCode) - } + initializeCrossSigning( + object : UserInteractiveAuthInterceptor { + override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { + promise.block(flowResponse, errCode) } - ) + } + ) }