From ea17789a6015c3e5ea388c620ccb01e3453ec28d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Moli=C5=84ski?= <47773413+damian-molinski@users.noreply.github.com> Date: Wed, 6 Nov 2024 15:17:28 +0100 Subject: [PATCH] feat(cat-voices): recover different keychain (#1147) --- .../seed_phrase/account_details_panel.dart | 39 +++++++------------ .../registration/cubits/recover_cubit.dart | 17 ++++++++ .../lib/src/session/session_cubit.dart | 2 +- .../test/session/session_cubit_test.dart | 6 +-- .../catalyst_voices_localizations.dart | 6 +++ .../catalyst_voices_localizations_en.dart | 3 ++ .../catalyst_voices_localizations_es.dart | 3 ++ .../lib/l10n/intl_en.arb | 1 + .../lib/src/user/user_service.dart | 30 +++++++++++--- .../test/src/user/user_service_test.dart | 4 +- 10 files changed, 75 insertions(+), 36 deletions(-) diff --git a/catalyst_voices/apps/voices/lib/pages/registration/recover/seed_phrase/account_details_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/recover/seed_phrase/account_details_panel.dart index f8b2ed8495..f9f3ac815d 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/recover/seed_phrase/account_details_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/recover/seed_phrase/account_details_panel.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:catalyst_voices/pages/registration/recover/bloc_recover_builder.dart'; import 'package:catalyst_voices/pages/registration/widgets/wallet_connection_status.dart'; import 'package:catalyst_voices/pages/registration/widgets/wallet_summary.dart'; @@ -33,11 +31,9 @@ class AccountDetailsPanel extends StatelessWidget { style: theme.textTheme.titleMedium?.copyWith(color: textColor), ), const SizedBox(height: 24), - Expanded( + const Expanded( child: SingleChildScrollView( - child: _BlocAccountSummery( - onRetry: () => unawaited(_retryAccountRestore(context)), - ), + child: _BlocAccountSummery(), ), ), const SizedBox(height: 24), @@ -45,19 +41,10 @@ class AccountDetailsPanel extends StatelessWidget { ], ); } - - Future _retryAccountRestore(BuildContext context) async { - final recover = RegistrationCubit.of(context).recover; - await recover.recoverAccount(); - } } class _BlocAccountSummery extends StatelessWidget { - final VoidCallback? onRetry; - - const _BlocAccountSummery({ - this.onRetry, - }); + const _BlocAccountSummery(); @override Widget build(BuildContext context) { @@ -71,10 +58,7 @@ class _BlocAccountSummery extends StatelessWidget { walletSummary: value.walletSummary, ), Failure(:final value) => - _RecoverAccountFailure( - exception: value, - onRetry: onRetry, - ), + _RecoverAccountFailure(exception: value), _ => const Center(child: VoicesCircularProgressIndicator()), }; }, @@ -117,18 +101,19 @@ class _RecoveredAccountSummary extends StatelessWidget { class _RecoverAccountFailure extends StatelessWidget { final LocalizedException exception; - final VoidCallback? onRetry; const _RecoverAccountFailure({ required this.exception, - this.onRetry, }); @override Widget build(BuildContext context) { return VoicesErrorIndicator( message: exception.message(context), - onRetry: onRetry, + onRetry: () async { + final recover = RegistrationCubit.of(context).recover; + await recover.recoverAccount(); + }, ); } } @@ -185,8 +170,12 @@ class _Navigation extends StatelessWidget { ), const SizedBox(height: 10), VoicesTextButton( - onTap: () => RegistrationCubit.of(context).previousStep(), - child: Text(context.l10n.back), + onTap: () async { + final cubit = RegistrationCubit.of(context); + await cubit.recover.reset(); + cubit.previousStep(); + }, + child: Text(context.l10n.recoverDifferentKeychain), ), ], ); diff --git a/catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/registration/cubits/recover_cubit.dart b/catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/registration/cubits/recover_cubit.dart index 5105628edd..abe64ca8c6 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/registration/cubits/recover_cubit.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/registration/cubits/recover_cubit.dart @@ -18,6 +18,8 @@ abstract interface class RecoverManager implements UnlockPasswordManager { void setSeedPhraseWords(List words); Future recoverAccount(); + + Future reset(); } final class RecoverCubit extends Cubit @@ -27,6 +29,7 @@ final class RecoverCubit extends Cubit final RegistrationService _registrationService; SeedPhrase? _seedPhrase; + Account? _recoveredAccount; RecoverCubit({ required UserService userService, @@ -84,6 +87,8 @@ final class RecoverCubit extends Cubit lockFactor: lockFactor, ); + _recoveredAccount = account; + await _userService.useAccount(account); final walletInfo = account.walletInfo; @@ -119,6 +124,18 @@ final class RecoverCubit extends Cubit void onUnlockPasswordStateChanged(UnlockPasswordState data) { emit(state.copyWith(unlockPasswordState: data)); } + + @override + Future reset() async { + final recoveredAccount = _recoveredAccount; + if (recoveredAccount != null) { + await _userService.removeKeychain(recoveredAccount.keychainId); + } + + _recoveredAccount = null; + + setSeedPhraseWords([]); + } } const _testWords = [ diff --git a/catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/session/session_cubit.dart b/catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/session/session_cubit.dart index e47537d544..86adb2dbd5 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/session/session_cubit.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/session/session_cubit.dart @@ -61,7 +61,7 @@ final class SessionCubit extends Cubit } Future removeKeychain() { - return _userService.removeCurrentAccount(); + return _userService.removeCurrentKeychain(); } Future switchToDummyAccount() async { diff --git a/catalyst_voices/packages/internal/catalyst_voices_blocs/test/session/session_cubit_test.dart b/catalyst_voices/packages/internal/catalyst_voices_blocs/test/session/session_cubit_test.dart index 0a058f7c00..ae3569baa4 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_blocs/test/session/session_cubit_test.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_blocs/test/session/session_cubit_test.dart @@ -43,7 +43,7 @@ void main() { // Given // When - await userService.removeCurrentAccount(); + await userService.removeCurrentKeychain(); // Then expect(userService.keychain, isNull); @@ -54,7 +54,7 @@ void main() { // Given // When - await userService.removeCurrentAccount(); + await userService.removeCurrentKeychain(); // Gives time for stream to emit. await Future.delayed(const Duration(milliseconds: 100)); @@ -80,7 +80,7 @@ void main() { // When notifier.value = RegistrationProgress(keychainProgress: keychainProgress); - await userService.removeCurrentAccount(); + await userService.removeCurrentKeychain(); // Gives time for stream to emit. await Future.delayed(const Duration(milliseconds: 100)); diff --git a/catalyst_voices/packages/internal/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart b/catalyst_voices/packages/internal/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart index 356a846fc5..e25a788ede 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart @@ -1738,6 +1738,12 @@ abstract class VoicesLocalizations { /// **'With over 300 trillion possible combinations, your 12 word seed phrase is great for keeping your account safe. 

But it can be a bit tedious to enter every single time you want to use the app. 

In this next step, you\'ll set your Unlock Password for your current device. It\'s like a shortcut for proving ownership of your Keychain. 

Whenever you recover your account for the first time on a new device, you\'ll need to use your Catalyst Keychain to get started. Every time after that, you can use your Unlock Password to quickly regain access.'** String get recoveryUnlockPasswordInstructionsSubtitle; + /// No description provided for @recoverDifferentKeychain. + /// + /// In en, this message translates to: + /// **'Restore a different keychain'** + String get recoverDifferentKeychain; + /// The header label in unlock dialog. /// /// In en, this message translates to: diff --git a/catalyst_voices/packages/internal/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart b/catalyst_voices/packages/internal/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart index 4d0879c593..5cd6739f26 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart @@ -914,6 +914,9 @@ class VoicesLocalizationsEn extends VoicesLocalizations { @override String get recoveryUnlockPasswordInstructionsSubtitle => 'With over 300 trillion possible combinations, your 12 word seed phrase is great for keeping your account safe. 

But it can be a bit tedious to enter every single time you want to use the app. 

In this next step, you\'ll set your Unlock Password for your current device. It\'s like a shortcut for proving ownership of your Keychain. 

Whenever you recover your account for the first time on a new device, you\'ll need to use your Catalyst Keychain to get started. Every time after that, you can use your Unlock Password to quickly regain access.'; + @override + String get recoverDifferentKeychain => 'Restore a different keychain'; + @override String get unlockDialogHeader => 'Unlock Catalyst'; diff --git a/catalyst_voices/packages/internal/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart b/catalyst_voices/packages/internal/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart index c63315deed..4384ef7847 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart @@ -914,6 +914,9 @@ class VoicesLocalizationsEs extends VoicesLocalizations { @override String get recoveryUnlockPasswordInstructionsSubtitle => 'With over 300 trillion possible combinations, your 12 word seed phrase is great for keeping your account safe. 

But it can be a bit tedious to enter every single time you want to use the app. 

In this next step, you\'ll set your Unlock Password for your current device. It\'s like a shortcut for proving ownership of your Keychain. 

Whenever you recover your account for the first time on a new device, you\'ll need to use your Catalyst Keychain to get started. Every time after that, you can use your Unlock Password to quickly regain access.'; + @override + String get recoverDifferentKeychain => 'Restore a different keychain'; + @override String get unlockDialogHeader => 'Unlock Catalyst'; diff --git a/catalyst_voices/packages/internal/catalyst_voices_localization/lib/l10n/intl_en.arb b/catalyst_voices/packages/internal/catalyst_voices_localization/lib/l10n/intl_en.arb index 9358da69a7..cee75b5d56 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_localization/lib/l10n/intl_en.arb +++ b/catalyst_voices/packages/internal/catalyst_voices_localization/lib/l10n/intl_en.arb @@ -900,6 +900,7 @@ "recoveryAccountDetailsAction": "Set unlock password for this device", "recoveryUnlockPasswordInstructionsTitle": "Set your Catalyst unlock password f\u2028or this device", "recoveryUnlockPasswordInstructionsSubtitle": "With over 300 trillion possible combinations, your 12 word seed phrase is great for keeping your account safe. \u2028\u2028But it can be a bit tedious to enter every single time you want to use the app. \u2028\u2028In this next step, you'll set your Unlock Password for your current device. It's like a shortcut for proving ownership of your Keychain. \u2028\u2028Whenever you recover your account for the first time on a new device, you'll need to use your Catalyst Keychain to get started. Every time after that, you can use your Unlock Password to quickly regain access.", + "recoverDifferentKeychain": "Restore a different keychain", "unlockDialogHeader": "Unlock Catalyst", "@unlockDialogHeader": { "description": "The header label in unlock dialog." diff --git a/catalyst_voices/packages/internal/catalyst_voices_services/lib/src/user/user_service.dart b/catalyst_voices/packages/internal/catalyst_voices_services/lib/src/user/user_service.dart index 53030b69fc..5ad6755c89 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_services/lib/src/user/user_service.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_services/lib/src/user/user_service.dart @@ -32,7 +32,9 @@ abstract interface class UserService { Future useKeychain(String id); - Future removeCurrentAccount(); + Future removeCurrentKeychain(); + + Future removeKeychain(String id); Future dispose(); } @@ -110,16 +112,34 @@ final class UserServiceImpl implements UserService { } @override - Future removeCurrentAccount() async { + Future removeCurrentKeychain() async { final keychain = _keychain; if (keychain == null) { - _logger.warning('Called remove account but no active keychain found'); + _logger.warning('Called remove keychain but no active found'); + return; + } + + await removeKeychain(keychain.id); + } + + @override + Future removeKeychain(String id) async { + if (!await _keychainProvider.exists(id)) { + _logger.warning( + 'Called remove keychain[$id] but no such keychain was found', + ); return; } + final isCurrentKeychain = id == _keychain?.id; + + final keychain = await _keychainProvider.get(id); await keychain.clear(); - await _clearUser(); - await _useKeychain(null); + + if (isCurrentKeychain) { + await _clearUser(); + await _useKeychain(null); + } } @override diff --git a/catalyst_voices/packages/internal/catalyst_voices_services/test/src/user/user_service_test.dart b/catalyst_voices/packages/internal/catalyst_voices_services/test/src/user/user_service_test.dart index eac0f3d7d3..045abedef1 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_services/test/src/user/user_service_test.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_services/test/src/user/user_service_test.dart @@ -56,7 +56,7 @@ void main() { await service.useKeychain(keychainOne.id); await service.useKeychain(keychainTwo.id); - await service.removeCurrentAccount(); + await service.removeCurrentKeychain(); await service.dispose(); }); @@ -120,7 +120,7 @@ void main() { // Then expect(service.keychain, isNotNull); - await service.removeCurrentAccount(); + await service.removeCurrentKeychain(); expect(service.keychain, isNull); expect(await currentKeychain.isEmpty, isTrue);