mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 20:39:51 +00:00
feat(ios-storage-issue): Remove 2FA/PIN persistence across app re-installs in iOS
This error occurs due to the keychain persisting auth data on user devices connected to the same iCloud
This commit is contained in:
parent
65bb917bfb
commit
da56ed8f52
3 changed files with 141 additions and 4 deletions
125
lib/core/reset_service.dart
Normal file
125
lib/core/reset_service.dart
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
import 'package:cake_wallet/core/secure_storage.dart';
|
||||||
|
import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||||
|
import 'package:cake_wallet/store/authentication_store.dart';
|
||||||
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:cw_core/utils/print_verbose.dart';
|
||||||
|
|
||||||
|
class ResetService {
|
||||||
|
ResetService({
|
||||||
|
required this.secureStorage,
|
||||||
|
required this.authenticationStore,
|
||||||
|
required this.settingsStore,
|
||||||
|
});
|
||||||
|
|
||||||
|
final SecureStorage secureStorage;
|
||||||
|
final AuthenticationStore authenticationStore;
|
||||||
|
final SettingsStore settingsStore;
|
||||||
|
|
||||||
|
static const List<String> _authKeys = [
|
||||||
|
SecureKey.allowBiometricalAuthenticationKey,
|
||||||
|
SecureKey.useTOTP2FA,
|
||||||
|
SecureKey.shouldRequireTOTP2FAForAccessingWallet,
|
||||||
|
SecureKey.shouldRequireTOTP2FAForSendsToContact,
|
||||||
|
SecureKey.shouldRequireTOTP2FAForSendsToNonContact,
|
||||||
|
SecureKey.shouldRequireTOTP2FAForSendsToInternalWallets,
|
||||||
|
SecureKey.shouldRequireTOTP2FAForExchangesToInternalWallets,
|
||||||
|
SecureKey.shouldRequireTOTP2FAForExchangesToExternalWallets,
|
||||||
|
SecureKey.shouldRequireTOTP2FAForAddingContacts,
|
||||||
|
SecureKey.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
|
SecureKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
|
SecureKey.selectedCake2FAPreset,
|
||||||
|
SecureKey.totpSecretKey,
|
||||||
|
SecureKey.pinTimeOutDuration,
|
||||||
|
SecureKey.lastAuthTimeMilliseconds,
|
||||||
|
'PIN_CODE_PASSWORD',
|
||||||
|
];
|
||||||
|
|
||||||
|
bool _isAuthKey(String key) => _authKeys.contains(key);
|
||||||
|
|
||||||
|
/// Checks if this is a new install and clears any existing auth data from Keychain
|
||||||
|
Future<void> resetAuthDataOnNewInstall(SharedPreferences sharedPreferences) async {
|
||||||
|
try {
|
||||||
|
final isNewInstall = sharedPreferences.getBool(PreferencesKey.isNewInstall) ?? false;
|
||||||
|
|
||||||
|
if (isNewInstall) {
|
||||||
|
await _clearExistingAuthDataOnNewInstall();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
printV('Error during new install auth reset: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if there's existing auth data that should be cleared on new install
|
||||||
|
Future<void> _clearExistingAuthDataOnNewInstall() async {
|
||||||
|
final allKeys = await secureStorage.readAll();
|
||||||
|
final authKeysFound = <String>[];
|
||||||
|
|
||||||
|
for (final key in allKeys.keys) {
|
||||||
|
if (_isAuthKey(key)) {
|
||||||
|
authKeysFound.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authKeysFound.isNotEmpty) {
|
||||||
|
printV(
|
||||||
|
'Found ${authKeysFound.length} existing auth keys in storage: ${authKeysFound.join(', ')}',
|
||||||
|
);
|
||||||
|
|
||||||
|
await resetAuthenticationData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resets authentication data from both secure storage and settings store
|
||||||
|
Future<void> resetAuthenticationData() async {
|
||||||
|
try {
|
||||||
|
await Future.wait([
|
||||||
|
_deleteAuthenticationKeys(),
|
||||||
|
_resetSettingsStoreAuthData(),
|
||||||
|
]);
|
||||||
|
} catch (e) {
|
||||||
|
printV('An error occurred during authentication reset: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resets authentication-related data in SettingsStore to default values
|
||||||
|
Future<void> _resetSettingsStoreAuthData() async {
|
||||||
|
settingsStore.useTOTP2FA = false;
|
||||||
|
settingsStore.totpSecretKey = '';
|
||||||
|
settingsStore.shouldRequireTOTP2FAForAccessingWallet = false;
|
||||||
|
settingsStore.shouldRequireTOTP2FAForSendsToContact = false;
|
||||||
|
settingsStore.shouldRequireTOTP2FAForSendsToNonContact = false;
|
||||||
|
settingsStore.shouldRequireTOTP2FAForSendsToInternalWallets = false;
|
||||||
|
settingsStore.shouldRequireTOTP2FAForExchangesToInternalWallets = false;
|
||||||
|
settingsStore.shouldRequireTOTP2FAForExchangesToExternalWallets = false;
|
||||||
|
settingsStore.shouldRequireTOTP2FAForAddingContacts = false;
|
||||||
|
settingsStore.shouldRequireTOTP2FAForCreatingNewWallets = false;
|
||||||
|
settingsStore.shouldRequireTOTP2FAForAllSecurityAndBackupSettings = false;
|
||||||
|
settingsStore.allowBiometricalAuthentication = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes authentication keys from secure storage
|
||||||
|
Future<void> _deleteAuthenticationKeys() async {
|
||||||
|
final failedDeletions = <String>[];
|
||||||
|
|
||||||
|
final deletionFutures = _authKeys.map((key) async {
|
||||||
|
try {
|
||||||
|
await secureStorage.delete(key: key);
|
||||||
|
} catch (e) {
|
||||||
|
failedDeletions.add(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Future.wait(deletionFutures);
|
||||||
|
|
||||||
|
if (failedDeletions.isNotEmpty) {
|
||||||
|
printV(
|
||||||
|
'Warning: Failed to delete ${failedDeletions.length} auth keys: ${failedDeletions.join(', ')}',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
printV('All auth keys deleted successfully');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -280,6 +280,7 @@ import 'cake_pay/cake_pay_payment_credantials.dart';
|
||||||
import 'package:cake_wallet/view_model/dev/background_sync_logs_view_model.dart';
|
import 'package:cake_wallet/view_model/dev/background_sync_logs_view_model.dart';
|
||||||
import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
|
import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
|
||||||
import 'package:cake_wallet/core/trade_monitor.dart';
|
import 'package:cake_wallet/core/trade_monitor.dart';
|
||||||
|
import 'package:cake_wallet/core/reset_service.dart';
|
||||||
|
|
||||||
final getIt = GetIt.instance;
|
final getIt = GetIt.instance;
|
||||||
|
|
||||||
|
@ -557,6 +558,14 @@ Future<void> setup({
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
getIt.registerFactory<ResetService>(
|
||||||
|
() => ResetService(
|
||||||
|
secureStorage: getIt.get<SecureStorage>(),
|
||||||
|
authenticationStore: getIt.get<AuthenticationStore>(),
|
||||||
|
settingsStore: getIt.get<SettingsStore>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
getIt.registerFactory<AuthViewModel>(() => AuthViewModel(getIt.get<AuthService>(),
|
getIt.registerFactory<AuthViewModel>(() => AuthViewModel(getIt.get<AuthService>(),
|
||||||
getIt.get<SharedPreferences>(), getIt.get<SettingsStore>(), BiometricAuth()));
|
getIt.get<SharedPreferences>(), getIt.get<SettingsStore>(), BiometricAuth()));
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/src/screens/root/root.dart';
|
import 'package:cake_wallet/src/screens/root/root.dart';
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
import 'package:cake_wallet/store/authentication_store.dart';
|
import 'package:cake_wallet/store/authentication_store.dart';
|
||||||
import 'package:cake_wallet/themes/core/material_base_theme.dart';
|
|
||||||
import 'package:cake_wallet/themes/utils/theme_provider.dart';
|
import 'package:cake_wallet/themes/utils/theme_provider.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cake_wallet/utils/device_info.dart';
|
import 'package:cake_wallet/utils/device_info.dart';
|
||||||
|
@ -56,6 +55,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:cw_core/window_size.dart';
|
import 'package:cw_core/window_size.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:cake_wallet/core/trade_monitor.dart';
|
import 'package:cake_wallet/core/trade_monitor.dart';
|
||||||
|
import 'package:cake_wallet/core/reset_service.dart';
|
||||||
|
|
||||||
final navigatorKey = GlobalKey<NavigatorState>();
|
final navigatorKey = GlobalKey<NavigatorState>();
|
||||||
final rootKey = GlobalKey<RootState>();
|
final rootKey = GlobalKey<RootState>();
|
||||||
|
@ -202,8 +202,8 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
|
||||||
final powNodes =
|
final powNodes =
|
||||||
await CakeHive.openBox<Node>(Node.boxName + "pow"); // must be different from Node.boxName
|
await CakeHive.openBox<Node>(Node.boxName + "pow"); // must be different from Node.boxName
|
||||||
final transactionDescriptions = await CakeHive.openBox<TransactionDescription>(
|
final transactionDescriptions = await CakeHive.openBox<TransactionDescription>(
|
||||||
TransactionDescription.boxName,
|
TransactionDescription.boxName,
|
||||||
encryptionKey: transactionDescriptionsBoxKey);
|
encryptionKey: transactionDescriptionsBoxKey);
|
||||||
final trades = await CakeHive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
|
final trades = await CakeHive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
|
||||||
final orders = await CakeHive.openBox<Order>(Order.boxName, encryptionKey: ordersBoxKey);
|
final orders = await CakeHive.openBox<Order>(Order.boxName, encryptionKey: ordersBoxKey);
|
||||||
final walletInfoSource = await CakeHive.openBox<WalletInfo>(WalletInfo.boxName);
|
final walletInfoSource = await CakeHive.openBox<WalletInfo>(WalletInfo.boxName);
|
||||||
|
@ -287,6 +287,9 @@ Future<void> initialSetup({
|
||||||
navigatorKey: navigatorKey,
|
navigatorKey: navigatorKey,
|
||||||
secureStorage: secureStorage,
|
secureStorage: secureStorage,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
getIt.get<ResetService>().resetAuthDataOnNewInstall(sharedPreferences);
|
||||||
|
|
||||||
await bootstrapOffline();
|
await bootstrapOffline();
|
||||||
final settingsStore = getIt<SettingsStore>();
|
final settingsStore = getIt<SettingsStore>();
|
||||||
if (!settingsStore.currentBuiltinTor) {
|
if (!settingsStore.currentBuiltinTor) {
|
||||||
|
@ -314,7 +317,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
|
||||||
final statusBarColor = Colors.transparent;
|
final statusBarColor = Colors.transparent;
|
||||||
final authenticationStore = getIt.get<AuthenticationStore>();
|
final authenticationStore = getIt.get<AuthenticationStore>();
|
||||||
final initialRoute = authenticationStore.state == AuthenticationState.uninitialized
|
final initialRoute = authenticationStore.state == AuthenticationState.uninitialized
|
||||||
? Routes.welcome
|
? Routes.welcome
|
||||||
: settingsStore.currentBuiltinTor ? Routes.startTor : Routes.login;
|
: settingsStore.currentBuiltinTor ? Routes.startTor : Routes.login;
|
||||||
final currentTheme = appStore.themeStore.currentTheme;
|
final currentTheme = appStore.themeStore.currentTheme;
|
||||||
final statusBarBrightness = currentTheme.type == currentTheme.isDark
|
final statusBarBrightness = currentTheme.type == currentTheme.isDark
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue