This commit is contained in:
M
2021-01-13 18:43:34 +02:00
parent c20d57e9a9
commit c09a41b090
18 changed files with 826 additions and 24 deletions
+244
View File
@@ -0,0 +1,244 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:path_provider/path_provider.dart';
import 'package:cryptography/cryptography.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:archive/archive_io.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/entities/encrypt.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cake_wallet/entities/wallet_info.dart';
class BackupService {
BackupService(this._flutterSecureStorage, this._authService,
this._walletInfoSource, this._keyService, this._sharedPreferences)
: _cipher = chacha20Poly1305Aead;
final Cipher _cipher;
final FlutterSecureStorage _flutterSecureStorage;
final SharedPreferences _sharedPreferences;
final AuthService _authService;
final Box<WalletInfo> _walletInfoSource;
final KeyService _keyService;
Future<void> importBackup(Uint8List data, String password,
{@required String nonce}) async {
final appDir = await getApplicationDocumentsDirectory();
final decryptedData = await _decrypt(data, password, nonce);
final zip = ZipDecoder().decodeBytes(decryptedData);
zip.files.forEach((file) {
final filename = file.name;
if (file.isFile) {
final data = file.content as List<int>;
File('${appDir.path}/' + filename)
..createSync(recursive: true)
..writeAsBytesSync(data);
} else {
Directory('${appDir.path}/' + filename)..create(recursive: true);
}
print(filename);
});
await importKeychainDump(password, nonce: nonce);
await importPreferencesDump();
}
Future<void> importPreferencesDump() async {
final appDir = await getApplicationDocumentsDirectory();
final preferencesFile = File('${appDir.path}/~_preferences_dump');
if (!preferencesFile.existsSync()) {
return;
}
final data =
json.decode(preferencesFile.readAsStringSync()) as Map<String, Object>;
print('data $data');
await _sharedPreferences.setString(PreferencesKey.currentWalletName,
data[PreferencesKey.currentWalletName] as String);
await _sharedPreferences.setInt(PreferencesKey.currentNodeIdKey,
data[PreferencesKey.currentNodeIdKey] as int);
await _sharedPreferences.setInt(PreferencesKey.currentBalanceDisplayModeKey,
data[PreferencesKey.currentBalanceDisplayModeKey] as int);
await _sharedPreferences.setInt(PreferencesKey.currentWalletType,
data[PreferencesKey.currentWalletType] as int);
await _sharedPreferences.setString(PreferencesKey.currentFiatCurrencyKey,
data[PreferencesKey.currentFiatCurrencyKey] as String);
await _sharedPreferences.setBool(
PreferencesKey.shouldSaveRecipientAddressKey,
data[PreferencesKey.shouldSaveRecipientAddressKey] as bool);
await _sharedPreferences.setInt(
PreferencesKey.currentTransactionPriorityKey,
data[PreferencesKey.currentTransactionPriorityKey] as int);
await _sharedPreferences.setBool(
PreferencesKey.allowBiometricalAuthenticationKey,
data[PreferencesKey.allowBiometricalAuthenticationKey] as bool);
await _sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey,
data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int);
await _sharedPreferences.setInt(PreferencesKey.currentLanguageCode,
data[PreferencesKey.currentLanguageCode] as int);
await _sharedPreferences.setInt(PreferencesKey.displayActionListModeKey,
data[PreferencesKey.displayActionListModeKey] as int);
await _sharedPreferences.setInt(
'current_theme', data['current_theme'] as int);
await preferencesFile.delete();
}
Future<void> importKeychainDump(String password,
{@required String nonce}) async {
final appDir = await getApplicationDocumentsDirectory();
final keychainDumpFile = File('${appDir.path}/~_keychain_dump');
final decryptedKeychainDumpFileData =
await _decrypt(keychainDumpFile.readAsBytesSync(), password, nonce);
final keychainJSON = json.decode(utf8.decode(decryptedKeychainDumpFileData))
as Map<String, dynamic>;
final keychainWalletsInfo = keychainJSON['wallets'] as List;
final decodedPin = keychainJSON['pin'] as String;
final pinCodeKey = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
keychainWalletsInfo.forEach((dynamic rawInfo) async {
final info = rawInfo as Map<String, dynamic>;
await importWalletKeychainInfo(info);
});
await _flutterSecureStorage.write(
key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
keychainDumpFile.deleteSync();
}
Future<void> importWalletKeychainInfo(Map<String, dynamic> info) async {
final name = info['name'] as String;
final password = info['password'] as String;
await _keyService.saveWalletPassword(walletName: name, password: password);
}
Future<Uint8List> exportBackup(String password,
{@required String nonce}) async {
final zipEncoder = ZipFileEncoder();
final appDir = await getApplicationDocumentsDirectory();
final now = DateTime.now();
final tmpDir = Directory('${appDir.path}/~_BACKUP_TMP');
final archivePath = '${tmpDir.path}/backup_${now.toString()}.zip';
final fileEntities = appDir.listSync(recursive: false);
final keychainDump = await exportKeychainDump(password, nonce: nonce);
final preferencesDump = await exportPreferencesJSON();
final preferencesDumpFile = File('${tmpDir.path}/~_preferences_dump_TMP');
final keychainDumpFile = File('${tmpDir.path}/~_keychain_dump_TMP');
if (tmpDir.existsSync()) {
tmpDir.deleteSync(recursive: true);
}
tmpDir.createSync();
zipEncoder.create(archivePath);
fileEntities.forEach((entity) {
if (entity.path == archivePath || entity.path == tmpDir.path) {
return;
}
if (entity.statSync().type == FileSystemEntityType.directory) {
zipEncoder.addDirectory(Directory(entity.path));
} else {
zipEncoder.addFile(File(entity.path));
}
});
await keychainDumpFile.writeAsBytes(keychainDump.toList());
await preferencesDumpFile.writeAsString(preferencesDump);
zipEncoder.addFile(preferencesDumpFile, '~_preferences_dump');
zipEncoder.addFile(keychainDumpFile, '~_keychain_dump');
zipEncoder.close();
final content = File(archivePath).readAsBytesSync();
tmpDir.deleteSync(recursive: true);
return await _encrypt(content, password, nonce);
}
Future<Uint8List> exportKeychainDump(String password,
{@required String nonce}) async {
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
final encodedPin = await _flutterSecureStorage.read(key: key);
final decodedPin = decodedPinCode(pin: encodedPin);
final wallets =
await Future.wait(_walletInfoSource.values.map((walletInfo) async {
return {
'name': walletInfo.name,
'type': walletInfo.type.toString(),
'password':
await _keyService.getWalletPassword(walletName: walletInfo.name)
};
}));
final data =
utf8.encode(json.encode({'pin': decodedPin, 'wallets': wallets}));
final encrypted = await _encrypt(Uint8List.fromList(data), password, nonce);
return encrypted;
}
Future<String> exportPreferencesJSON() async {
final preferences = <String, Object>{
PreferencesKey.currentWalletName:
_sharedPreferences.getString(PreferencesKey.currentWalletName),
PreferencesKey.currentNodeIdKey:
_sharedPreferences.getInt(PreferencesKey.currentNodeIdKey),
PreferencesKey.currentBalanceDisplayModeKey: _sharedPreferences
.getInt(PreferencesKey.currentBalanceDisplayModeKey),
PreferencesKey.currentWalletType:
_sharedPreferences.getInt(PreferencesKey.currentWalletType),
PreferencesKey.currentFiatCurrencyKey:
_sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey),
PreferencesKey.shouldSaveRecipientAddressKey: _sharedPreferences
.getBool(PreferencesKey.shouldSaveRecipientAddressKey),
PreferencesKey.currentDarkTheme:
_sharedPreferences.getBool(PreferencesKey.currentDarkTheme),
PreferencesKey.currentPinLength:
_sharedPreferences.getInt(PreferencesKey.currentPinLength),
PreferencesKey.currentTransactionPriorityKey: _sharedPreferences
.getInt(PreferencesKey.currentTransactionPriorityKey),
PreferencesKey.allowBiometricalAuthenticationKey: _sharedPreferences
.getBool(PreferencesKey.allowBiometricalAuthenticationKey),
PreferencesKey.currentBitcoinElectrumSererIdKey: _sharedPreferences
.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey),
PreferencesKey.currentLanguageCode:
_sharedPreferences.getString(PreferencesKey.currentLanguageCode),
PreferencesKey.displayActionListModeKey:
_sharedPreferences.getInt(PreferencesKey.displayActionListModeKey),
'currentTheme': _sharedPreferences.getInt('current_theme')
// FIX-ME: Unnamed constant.
};
return json.encode(preferences);
}
Future<Uint8List> _encrypt(
Uint8List data, String secretKeySource, String nonceBase64) async {
final secretKeyHash = await sha256.hash(utf8.encode(secretKeySource));
final secretKey = SecretKey(secretKeyHash.bytes);
final nonce = Nonce(base64.decode(nonceBase64));
return await _cipher.encrypt(data, secretKey: secretKey, nonce: nonce);
}
Future<Uint8List> _decrypt(
Uint8List data, String secretKeySource, String nonceBase64) async {
final secretKeyHash = await sha256.hash(utf8.encode(secretKeySource));
final secretKey = SecretKey(secretKeyHash.bytes);
final nonce = Nonce(base64.decode(nonceBase64));
return await _cipher.decrypt(data, secretKey: secretKey, nonce: nonce);
}
}
+50 -9
View File
@@ -1,4 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
import 'package:cake_wallet/core/backup.dart';
import 'package:cake_wallet/core/wallet_service.dart';
import 'package:cake_wallet/entities/biometric_auth.dart';
import 'package:cake_wallet/entities/contact_record.dart';
@@ -10,6 +11,8 @@ import 'package:cake_wallet/entities/contact.dart';
import 'package:cake_wallet/entities/node.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_page.dart';
@@ -20,6 +23,8 @@ import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/nodes/nodes_list_page.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
import 'package:cake_wallet/src/screens/rescan/rescan_page.dart';
import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
import 'package:cake_wallet/src/screens/restore/restore_options_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
@@ -32,6 +37,7 @@ import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
import 'package:cake_wallet/store/node_list_store.dart';
import 'package:cake_wallet/store/secret_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/key_service.dart';
@@ -46,13 +52,16 @@ import 'package:cake_wallet/src/screens/send/send_page.dart';
import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
import 'package:cake_wallet/store/wallet_list_store.dart';
import 'package:cake_wallet/view_model/backup_view_model.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/rescan_view_model.dart';
import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
import 'package:cake_wallet/view_model/auth_view_model.dart';
@@ -127,6 +136,11 @@ Future setup(
getIt.registerSingleton<ExchangeTemplateStore>(
ExchangeTemplateStore(templateSource: exchangeTemplates));
final secretStore =
await SecretStoreBase.load(getIt.get<FlutterSecureStorage>());
getIt.registerSingleton<SecretStore>(secretStore);
getIt.registerFactory<KeyService>(
() => KeyService(getIt.get<FlutterSecureStorage>()));
@@ -291,17 +305,16 @@ Future setup(
getIt.get<MoneroAccountEditOrCreateViewModel>()));*/
getIt.registerFactoryParam<MoneroAccountEditOrCreateViewModel,
AccountListItem, void>(
(AccountListItem account, _) =>
MoneroAccountEditOrCreateViewModel((
getIt.get<AppStore>().wallet as MoneroWallet).accountList,
accountListItem: account));
AccountListItem, void>(
(AccountListItem account, _) => MoneroAccountEditOrCreateViewModel(
(getIt.get<AppStore>().wallet as MoneroWallet).accountList,
accountListItem: account));
getIt.registerFactoryParam<MoneroAccountEditOrCreatePage,
AccountListItem, void>((AccountListItem account, _) =>
MoneroAccountEditOrCreatePage(
getIt.registerFactoryParam<MoneroAccountEditOrCreatePage, AccountListItem,
void>(
(AccountListItem account, _) => MoneroAccountEditOrCreatePage(
moneroAccountCreationViewModel:
getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account)));
getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account)));
getIt.registerFactory(() {
final appStore = getIt.get<AppStore>();
@@ -422,4 +435,32 @@ Future setup(
transactionDescriptionBox));
getIt.registerFactory(() => PreSeedPage());
getIt.registerFactory(() => BackupService(
getIt.get<FlutterSecureStorage>(),
getIt.get<AuthService>(),
walletInfoSource,
getIt.get<KeyService>(),
getIt.get<SharedPreferences>()));
getIt.registerFactory(() => BackupViewModel(getIt.get<FlutterSecureStorage>(),
getIt.get<SecretStore>(), getIt.get<BackupService>()));
getIt.registerFactory(() => BackupPage(getIt.get<BackupViewModel>()));
getIt.registerFactory(() => EditBackupPasswordViewModel(
getIt.get<FlutterSecureStorage>(), getIt.get<SecretStore>())
..init());
getIt.registerFactory(
() => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
getIt.registerFactoryParam<RestoreOptionsPage, WalletType, void>(
(WalletType type, _) => RestoreOptionsPage(type: type));
getIt.registerFactory(
() => RestoreFromBackupViewModel(getIt.get<BackupService>()));
getIt.registerFactory(
() => RestoreFromBackupPage(getIt.get<RestoreFromBackupViewModel>()));
}
+13 -3
View File
@@ -1,9 +1,13 @@
enum SecretStoreKey { moneroWalletPassword, pinCodePassword }
enum SecretStoreKey { moneroWalletPassword, pinCodePassword, backupPassword }
const moneroWalletPassword = "MONERO_WALLET_PASSWORD";
const pinCodePassword = "PIN_CODE_PASSWORD";
const backupPassword = "BACKUP_CODE_PASSWORD";
String generateStoreKeyFor({SecretStoreKey key, String walletName = "",}) {
String generateStoreKeyFor({
SecretStoreKey key,
String walletName = "",
}) {
var _key = "";
switch (key) {
@@ -19,9 +23,15 @@ String generateStoreKeyFor({SecretStoreKey key, String walletName = "",}) {
}
break;
case SecretStoreKey.backupPassword:
{
_key = backupPassword;
}
break;
default:
{}
}
return _key;
}
}
+2
View File
@@ -1,3 +1,5 @@
import 'package:cake_wallet/core/backup.dart';
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hive/hive.dart';
+20 -8
View File
@@ -1,6 +1,9 @@
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
import 'package:cake_wallet/store/settings_store.dart';
@@ -65,15 +68,13 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => getIt.get<SetupPinCodePage>(param1:
(PinCodeState<PinCodeWidget> context, dynamic _) async {
try {
context.changeProcessText(
S.current.creating_new_wallet);
context.changeProcessText(S.current.creating_new_wallet);
final newWalletVM =
getIt.get<WalletNewVM>(param1: WalletType.monero);
await newWalletVM.create(
options: 'English'); // FIXME: Unnamed constant
context.hideProgressText();
await Navigator.of(context.context)
.pushNamed(Routes.preSeed);
await Navigator.of(context.context).pushNamed(Routes.preSeed);
} catch (e) {
context.changeProcessText('Error: ${e.toString()}');
}
@@ -115,7 +116,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.restoreOptions:
final type = settings.arguments as WalletType;
return CupertinoPageRoute<void>(
builder: (_) => RestoreOptionsPage(type: type));
builder: (_) => getIt.get<RestoreOptionsPage>(param1: type));
case Routes.restoreWalletOptions:
final type = WalletType.monero; //settings.arguments as WalletType;
@@ -264,7 +265,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.accountCreation:
return CupertinoPageRoute<String>(
builder: (_) => getIt.get<MoneroAccountEditOrCreatePage>(
param1: settings.arguments as AccountListItem));
param1: settings.arguments as AccountListItem));
case Routes.addressBook:
return MaterialPageRoute<void>(
@@ -326,8 +327,19 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => getIt.get<LanguageListPage>());
case Routes.preSeed:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<PreSeedPage>());
return MaterialPageRoute<void>(builder: (_) => getIt.get<PreSeedPage>());
case Routes.backup:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<BackupPage>());
case Routes.editBackupPassword:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<EditBackupPasswordPage>());
case Routes.restoreFromBackup:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<RestoreFromBackupPage>());
default:
return MaterialPageRoute<void>(
+3
View File
@@ -48,4 +48,7 @@ class Routes {
static const restoreWalletType = '/restore_wallet_type';
static const restoreWallet = '/restore_wallet';
static const preSeed = '/pre_seed';
static const backup = '/backup';
static const editBackupPassword = '/edit_backup_passowrd';
static const restoreFromBackup = '/restore_from_backup';
}
+93
View File
@@ -0,0 +1,93 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/trail_button.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/backup_view_model.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
class BackupPage extends BasePage {
BackupPage(this.backupViewModelBase);
final BackupViewModelBase backupViewModelBase;
@override
String get title => 'Backup';
@override
Widget trailing(BuildContext context) => TrailButton(
caption: S.of(context).edit,
onPressed: () =>
Navigator.of(context).pushNamed(Routes.editBackupPassword));
@override
Widget body(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: [
Center(
child: Container(
padding: EdgeInsets.only(left: 20, right: 20),
height: 225,
child: Column(children: [
Text(
'Backup password:',
style: TextStyle(fontSize: 30),
),
Padding(
padding: EdgeInsets.only(top: 20, bottom: 10),
child: Observer(
builder: (_) => Text(
backupViewModelBase.backupPassword,
style: TextStyle(fontSize: 26),
))),
Padding(
padding: EdgeInsets.all(20),
child: Text(
'Please write down your Backup Password. Backup Password uses for import of backup files.',
style: TextStyle(fontSize: 14, color: Colors.grey),
textAlign: TextAlign.center,
))
]))),
Positioned(
child: Observer(
builder: (_) => LoadingPrimaryButton(
isLoading: backupViewModelBase.state is IsExecutingState,
onPressed: () => onExportBackup(context),
text: 'Export backup',
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white)),
bottom: 30,
left: 20,
right: 20,
)
],
);
}
void onExportBackup(BuildContext context) {
showPopUp<void>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: 'Export backup',
alertContent:
'Please be sure that you have saved your Backup Password.You will be no available to import backup files without Backup Password.\n\nHave you written it down?',
rightButtonText: S.of(context).seed_alert_yes,
leftButtonText: S.of(context).seed_alert_back,
actionRightButton: () async {
Navigator.of(dialogContext).pop();
final backup = await backupViewModelBase.exportBackup();
await Share.file(
'Backup file', backup.name, backup.content, 'text');
},
actionLeftButton: () => Navigator.of(dialogContext).pop());
});
}
}
@@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
class EditBackupPasswordPage extends BasePage {
EditBackupPasswordPage(this.editBackupPasswordViewModel)
: textEditingController = TextEditingController() {
textEditingController.text = editBackupPasswordViewModel.backupPassword;
textEditingController.addListener(() => editBackupPasswordViewModel
.backupPassword = textEditingController.text);
}
final EditBackupPasswordViewModel editBackupPasswordViewModel;
final TextEditingController textEditingController;
@override
String get title => 'Edit Backup Password';
@override
Widget body(BuildContext context) {
return Padding(
padding: EdgeInsets.only(left: 20, right: 20),
child: Stack(
fit: StackFit.expand,
children: [
Center(
child: Observer(
builder: (_) => TextFormField(
autofocus: true,
enableSuggestions: false,
autocorrect: false,
keyboardType: TextInputType.visiblePassword,
controller: textEditingController,
style: TextStyle(fontSize: 26, color: Colors.black)))),
Positioned(
child: Observer(
builder: (_) => PrimaryButton(
onPressed: () => onSave(context),
text: 'Save',
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
isDisabled: !editBackupPasswordViewModel.canSave)),
bottom: 30,
left: 0,
right: 0)
],
));
}
void onSave(BuildContext context) {
showPopUp<void>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: 'Save backup password',
alertContent:
'Your previous backup files will be not available to import with new backup password. New backup password will be used only for new backup files. Are you sure that you want to change backup password ?',
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
actionRightButton: () async {
await editBackupPasswordViewModel.save();
Navigator.of(dialogContext)..pop()..pop();
},
actionLeftButton: () => Navigator.of(dialogContext).pop());
});
}
}
@@ -0,0 +1,88 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:file_picker/file_picker.dart';
class RestoreFromBackupPage extends BasePage {
RestoreFromBackupPage(this.restoreFromBackupViewModel)
: textEditingController = TextEditingController();
final RestoreFromBackupViewModel restoreFromBackupViewModel;
final TextEditingController textEditingController;
@override
String get title => 'Restore from backup';
@override
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.only(bottom: 30, left: 25, right: 25),
child: Column(children: [
Expanded(
child: Container(
child: Center(
child: TextFormField(
obscureText: true,
enableSuggestions: false,
autocorrect: false,
decoration: InputDecoration(
hintText: 'Enter backup password here'),
keyboardType: TextInputType.visiblePassword,
controller: textEditingController,
style: TextStyle(fontSize: 26, color: Colors.black))),
),
),
Container(
child: Row(children: [
Expanded(
child: PrimaryButton(
onPressed: () => presentFilePicker(),
text: 'Select backup file',
color: Colors.grey,
textColor: Colors.white)),
SizedBox(width: 20),
Expanded(
child: PrimaryButton(
onPressed: () => onImportHandler(context),
text: 'Import',
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white))
])),
]));
}
Future<void> presentFilePicker() async {
final result = await FilePicker.platform.pickFiles();
if (result?.files?.isEmpty ?? true) {
return;
}
restoreFromBackupViewModel.filePath = result.files.first.path;
}
Future<void> onImportHandler(BuildContext context) async {
if (textEditingController.text.isEmpty ||
(restoreFromBackupViewModel.filePath.isEmpty ?? true)) {
await showPopUp<void>(
context: context,
builder: (_) {
return AlertWithOneAction(
alertTitle: S.current.error,
alertContent:
'Please select backup file and enter backup password.',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
return;
}
await restoreFromBackupViewModel.import(textEditingController.text);
}
}
@@ -1,6 +1,5 @@
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/routes.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/src/screens/restore/widgets/restore_button.dart';
@@ -39,7 +38,8 @@ class RestoreOptionsPage extends BasePage {
Padding(
padding: EdgeInsets.only(top: 24),
child: RestoreButton(
onPressed: () {},
onPressed: () => Navigator.pushNamed(
context, Routes.restoreFromBackup),
image: imageBackup,
title: S.of(context).restore_title_from_backup,
description: S.of(context).restore_description_from_backup
+1 -1
View File
@@ -21,7 +21,7 @@ class TrailButton extends StatelessWidget {
child: Text(
caption,
style: TextStyle(
color: Theme.of(context).textTheme.subhead.decorationColor,
color: Theme.of(context).textTheme.display1.color,
fontWeight: FontWeight.w500,
fontSize: 14),
),
+28
View File
@@ -0,0 +1,28 @@
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:mobx/mobx.dart';
part 'secret_store.g.dart';
class SecretStore = SecretStoreBase with _$SecretStore;
abstract class SecretStoreBase with Store {
static Future<SecretStore> load(FlutterSecureStorage storage) async {
final secretStore = SecretStore();
final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
final backupPassword = await storage.read(key: backupPasswordKey);
secretStore.write(key: backupPasswordKey, value: backupPassword);
return secretStore;
}
SecretStoreBase() : values = ObservableMap<String, String>();
ObservableMap values;
String read(String key) => values[key] as String;
String write({@required String key, @required String value}) =>
values[key] = value;
}
+68
View File
@@ -0,0 +1,68 @@
import 'package:cake_wallet/core/backup.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cake_wallet/store/secret_store.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:mobx/mobx.dart';
part 'backup_view_model.g.dart';
class BackupExportFile {
BackupExportFile(this.content, {@required this.name});
final String name;
final List<int> content;
}
class BackupViewModel = BackupViewModelBase with _$BackupViewModel;
abstract class BackupViewModelBase with Store {
BackupViewModelBase(this.secureStorage, this.secretStore, this.backupService)
: isBackupPasswordVisible = false {
state = InitialExecutionState();
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
secretStore.values.observe((change) {
if (change.key == key) {
backupPassword = secretStore.read(key);
}
}, fireImmediately: true);
}
final FlutterSecureStorage secureStorage;
final SecretStore secretStore;
final BackupService backupService;
@observable
ExecutionState state;
@observable
bool isBackupPasswordVisible;
@observable
String backupPassword;
@action
Future<void> init() async {
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
backupPassword = await secureStorage.read(key: key);
}
@action
Future<BackupExportFile> exportBackup() async {
try {
state = IsExecutingState();
final backupContent = await backupService.exportBackup('', nonce: '');
state = ExecutedSuccessfullyState();
return BackupExportFile(backupContent.toList(),
name: 'backup_${DateTime.now().toString()}.zip');
} catch (e) {
print(e.toString());
state = FailureState(e.toString());
}
}
@action
void showMasterPassword() => isBackupPasswordVisible = true;
}
@@ -0,0 +1,44 @@
import 'package:mobx/mobx.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cake_wallet/store/secret_store.dart';
part 'edit_backup_password_view_model.g.dart';
class EditBackupPasswordViewModel = EditBackupPasswordViewModelBase
with _$EditBackupPasswordViewModel;
abstract class EditBackupPasswordViewModelBase with Store {
EditBackupPasswordViewModelBase(this.secureStorage, this.secretStore) {
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
backupPassword = secretStore.read(key);
}
final FlutterSecureStorage secureStorage;
final SecretStore secretStore;
@observable
String backupPassword;
@computed
bool get canSave {
return !(_originalPassword == backupPassword);
}
String _originalPassword;
@action
Future<void> init() async {
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
final password = await secureStorage.read(key: key);
_originalPassword = password;
backupPassword = password;
}
@action
Future<void> save() async {
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
await secureStorage.write(key: key, value: backupPassword);
secretStore.write(key: key, value: backupPassword);
}
}
@@ -0,0 +1,29 @@
import 'dart:io';
import 'package:cake_wallet/core/backup.dart';
import 'package:mobx/mobx.dart';
part 'restore_from_backup_view_model.g.dart';
class RestoreFromBackupViewModel = RestoreFromBackupViewModelBase with _$RestoreFromBackupViewModel;
abstract class RestoreFromBackupViewModelBase with Store {
RestoreFromBackupViewModelBase(this.backupService);
@observable
String filePath;
final BackupService backupService;
Future<void> import(String password) async {
if (filePath?.isEmpty ?? true) {
// FIXME: throw exception;
return;
}
final file = File(filePath);
final data = await file.readAsBytes();
await backupService.importBackup(data, password, nonce: null);
}
}